Skip to content

Instantly share code, notes, and snippets.

@tateisu
Created June 3, 2023 14:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tateisu/03b0c5c9d34634bdebb31a5e64369810 to your computer and use it in GitHub Desktop.
Save tateisu/03b0c5c9d34634bdebb31a5e64369810 to your computer and use it in GitHub Desktop.
Matrix Synapse のAdmin APIで 複数の部屋を Purge History Events する
#!/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