ap-backup.pl (3392B)
- #!/usr/bin/env perl
- # AP-Client: CLI-based client / toolbox for ActivityPub
- # Copyright © 2020-2021 AP-Client Authors <https://hacktivis.me/git/ap-client/>
- # SPDX-License-Identifier: BSD-3-Clause
- use strict;
- use utf8;
- no warnings; # Wide Character…
- use Getopt::Std;
- use LWP::UserAgent;
- use MIME::Base64;
- use JSON::PP;
- my %options=();
- my $auth;
- my $ua = LWP::UserAgent->new;
- $ua->agent("AP-Client Backup <https://hacktivis.me/git/ap-client/>");
- sub save_collection {
- my ($items) = @_;
- my $filename;
- foreach my $item (@{$items}) {
- if($item->{"id"}) {
- $filename = $item->{"id"};
- # replace / in URLs with _
- $filename =~ tr/\//_/;
- } else {
- die "id property undefined" if not $_->{"id"};
- }
- #print "Saving ", $item->{"id"}, " to ", $filename, "\n";
- open (my $fh, '>', $filename) or die "couldn't open", $filename;
- print $fh encode_json($item);
- close $fh;
- }
- }
- sub apc2s_backup {
- my ($url) = @_;
- my $req = HTTP::Request->new(GET => $url);
- $req->header('Accept' => 'application/activity+json');
- $req->header('Authorization' => $auth);
- my $res = $ua->request($req);
- if($res->is_success) {
- print "Got $url\n";
- my $content_type = $res->header("Content-Type");
- my $content_match = /application\/([^+]*+)?json(; .*)?/;
- if($content_type =~ $content_match) {
- my $response = decode_json($res->content);
- if($response->{"type"} eq "OrderedCollection") {
- if(not $response->{"first"}) {
- die "“first” property of OrderedCollection undefined";
- }
- print "Fetching first property: ", $response->{"first"}, "\n";
- apc2s_backup($response->{"first"});
- } elsif($response->{"type"} eq "OrderedCollectionPage") {
- if($response->{"orderedItems"}) {
- save_collection($response->{"orderedItems"});
- print "next: ", $response->{"next"}, "\n" if $response->{"next"};
- print "prev: ", $response->{"prev"}, "\n" if $response->{"prev"};
- if($response->{"next"}) {
- print "Fetching next property\n";
- apc2s_backup($response->{"next"})
- } else {
- print "No “next” property defined, done?\n";
- }
- } else {
- die "OrderedCollectionPage without “orderedItems” defined is unsupported";
- }
- } elsif($response->{"type"} eq "Person") {
- if($response->{"outbox"}) {
- print "Fetching outbox property: ", $response->{"outbox"}, "\n";
- apc2s_backup($response->{"outbox"});
- } else {
- die "Person actor with no outbox";
- }
- } else {
- die "Unknown type: ", $response->{"type"};
- }
- } else {
- die "Got ", $content_type, " instead of ", $content_match;
- }
- } else {
- die "Got ", $res->status_line, " instead of 2xx";
- }
- }
- getopts("u:o:", \%options);
- if($#ARGV != 0) {
- print "usage: ap-backup.pl <-u user:password|-o OAuth-Bearer-Token> <url>\n";
- print "This tool is used to backup an account, authentication is required.\n";
- print " -u HTTP Basic Auth\n";
- print " -o OAuth2 Bearer Token\n";
- print " url ActivityPub user URL or outbox URL\n";
- print "Known to work against Pleroma.\n";
- print "Activities are saved in the current working directory via their id, it's recommended to launch in a dedicated directory.\n";
- exit 1;
- }
- if(defined $options{u}) {
- $auth = "Basic ".encode_base64($options{u});
- }
- if(defined $options{o}) {
- $auth = "Bearer ".$options{o};
- }
- print "Authorization: $auth";
- print "Fetching: ", $ARGV[0], "\n";
- apc2s_backup($ARGV[0]);