commit: c12d170689dbc2ca54550a3322ef2105d0086a1d
parent 89bf8376b90cc33d52264f92b8a456792b91654d
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sat, 14 Aug 2021 11:15:35 +0200
ap-backup.pl: New
Diffstat:
A | ap-backup.pl | 121 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 121 insertions(+), 0 deletions(-)
diff --git a/ap-backup.pl b/ap-backup.pl
@@ -0,0 +1,121 @@
+#!/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 Scalar::Util qw(reftype);
+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";
+ }
+
+ my $ref_type = reftype($response->{"first"});
+
+ if($ref_type eq 'STRING') {
+ print "Fetching first property: ", $response->{"first"}, "\n";
+ apc2s_backup($response->{"first"});
+ } else {
+ die "Type ", $ref_type, "for “first” property is unsupported";
+ }
+ } 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 "Known to work against Pleroma.\n";
+ exit 1;
+}
+
+if(defined $options{u}) {
+ $auth = "Basic ", encode_base64($options{u});
+}
+if(defined $options{o}) {
+ $auth = "Bearer ", $options{o};
+}
+
+print "Fetching: ", $ARGV[0], "\n";
+apc2s_backup($ARGV[0]);