Created
June 3, 2023 14:58
-
-
Save tateisu/03b0c5c9d34634bdebb31a5e64369810 to your computer and use it in GitHub Desktop.
Matrix Synapse のAdmin APIで 複数の部屋を Purge History Events する
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl -- | |
use v5.34.0; | |
use strict; | |
use warnings; | |
use LWP::UserAgent; | |
use JSON5; | |
use JSON::XS; | |
use Data::Dump qw(dump); | |
use URI::Escape; | |
use Fcntl ':mode'; | |
# (in)admin API secret file. | |
# - properties: serverPrefix, user, password | |
my $matrixAdminApiSecretsFile = "matrixAdminApiSecrets.json"; | |
# (in,out)admin API token file. | |
# - properties: access_token | |
my $matrixAdminApiTokenFile = "matrixAdminApiToken.json"; | |
################################### | |
# save/load JSON5 to a file. | |
sub checkSecretFilePermission($){ | |
my($file)=@_; | |
my @st = stat($file) or return; | |
my $mode = $st[2]; | |
if( $mode & (S_IXUSR | S_IXGRP | S_IXOTH ) ){ | |
die "$file :file permission is bad. executable by user/group/other."; | |
}elsif( $mode & (S_IRGRP | S_IROTH ) ){ | |
die "$file :file permission is bad. readable by group/other."; | |
}elsif( $mode & (S_IWGRP | S_IWOTH ) ){ | |
die "$file :file permission is bad. writable by group/other."; | |
} | |
} | |
sub saveJson($$){ | |
my($file,$data)=@_; | |
open(my $fh,">:raw",$file) or die "$file $!"; | |
print $fh encode_json($data); | |
close($fh) or die "$file $!"; | |
chmod 0600, $file; | |
checkSecretFilePermission($file); | |
} | |
sub loadJson($){ | |
my($file)=@_; | |
-f $file or return; | |
local $/ = undef; | |
open(my $fh,"<:raw",$file) or die "$file $!"; | |
my $data = <$fh>; | |
close($fh) or die "$file $!"; | |
checkSecretFilePermission($file); | |
return decode_json5($data); | |
} | |
############################################################################ | |
# matrix admin API | |
my $apiSecrets = loadJson($matrixAdminApiSecretsFile); | |
my $apiToken = (loadJson($matrixAdminApiTokenFile)//{})->{access_token}; | |
my $apiPrefix = $apiSecrets->{serverPrefix}; | |
my $ua = LWP::UserAgent->new; | |
$ua->timeout(10); | |
$ua->env_proxy; | |
sub jsonRequest($$;%){ | |
my($method, $path, %params)=@_; | |
my $url = "$apiPrefix$path"; | |
$method = uc( $method or "GET"); | |
my $req = HTTP::Request->new($method => $url); | |
if(keys %params){ | |
$req->content_type('application/json'); | |
$req->content(encode_json(\%params)); | |
} | |
if($apiToken){ | |
$req->header("Authorization","Bearer $apiToken") | |
} | |
my $res = $ua->request($req); | |
$res->is_success or die join " " | |
, $res->status_line | |
, $path | |
, $res->decoded_content | |
; | |
return decode_json $res->content; | |
} | |
sub login{ | |
$apiToken and return; | |
my $result = jsonRequest( | |
POST => "/_matrix/client/r0/login" | |
,type => "m.login.password" | |
,user => $apiSecrets->{user} | |
,password => $apiSecrets->{password} | |
); | |
saveJson($matrixAdminApiTokenFile,$result); | |
$apiToken = $result->{access_token}; | |
} | |
# だいたい 500 Internal Server Error で使えない | |
sub largestRooms{ | |
jsonRequest( | |
GET=>"/_synapse/admin/v1/statistics/database/rooms" | |
)->{rooms}; | |
} | |
# クエリを指定してルームを列挙する | |
sub listRooms($&){ | |
my($query,$block)=@_; | |
my $from; | |
while(1){ | |
my $path = "/_synapse/admin/v1/rooms?$query"; | |
$from and $path = "$path&from=$from"; | |
my $result = jsonRequest(GET => $path); | |
say "listRooms: $result->{offset}/$result->{total_rooms}"; | |
for my $room(@{$result->{rooms}}){ | |
$block->($room); | |
} | |
$from = $result->{next_token} or last; | |
} | |
} | |
############################################################################ | |
login(); | |
listRooms( | |
# state_events が多い順 | |
"order_by=state_events", | |
sub{ | |
my $timeStart = time; | |
my($room)=@_; | |
$room->{canonical_alias} //= "null"; | |
say join ", " | |
,"state_events=$room->{state_events}" | |
,"members=$room->{joined_local_members}/$room->{joined_members}" | |
,"$room->{room_id}" | |
,"$room->{canonical_alias}" | |
,"creator=$room->{creator}" | |
; | |
my $roomId = $room->{room_id}; | |
my $roomIdEncoded = uri_escape( $roomId ); | |
my $purgeId = eval{ | |
jsonRequest( | |
POST=>"/_synapse/admin/v1/purge_history/$roomIdEncoded" | |
,purge_up_to_ts => (time - 86400*30)*1000 | |
)->{purge_id}; | |
}; | |
if($@){ | |
if( $@ =~ /History purge already in progress/ ){ | |
warn $@; | |
return; | |
}elsif( $@ =~ /there is no event to be purged/ ){ | |
return; | |
}else{ | |
die $@; | |
} | |
} | |
while(1){ | |
sleep 1; | |
my $status = jsonRequest( | |
GET=>"/_synapse/admin/v1/purge_history_status/$purgeId" | |
)->{status}; | |
if( $status ne "active"){ | |
if($status ne "complete"){ | |
say "roomId=$roomId, purgeId=$purgeId, status=$status"; | |
} | |
last; | |
} | |
} | |
my $duration = time - $timeStart; | |
if($duration >= 5){ | |
say "roomId=$roomId, purge takes $duration seconds."; | |
} | |
}, | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment