logo

ap-client

CLI-based client / toolbox for ActivityPub Client-to-Servergit clone https://hacktivis.me/git/ap-client.git

ap-backup.pl (3392B)


  1. #!/usr/bin/env perl
  2. # AP-Client: CLI-based client / toolbox for ActivityPub
  3. # Copyright © 2020-2021 AP-Client Authors <https://hacktivis.me/git/ap-client/>
  4. # SPDX-License-Identifier: BSD-3-Clause
  5. use strict;
  6. use utf8;
  7. no warnings; # Wide Character…
  8. use Getopt::Std;
  9. use LWP::UserAgent;
  10. use MIME::Base64;
  11. use JSON::PP;
  12. my %options=();
  13. my $auth;
  14. my $ua = LWP::UserAgent->new;
  15. $ua->agent("AP-Client Backup <https://hacktivis.me/git/ap-client/>");
  16. sub save_collection {
  17. my ($items) = @_;
  18. my $filename;
  19. foreach my $item (@{$items}) {
  20. if($item->{"id"}) {
  21. $filename = $item->{"id"};
  22. # replace / in URLs with _
  23. $filename =~ tr/\//_/;
  24. } else {
  25. die "id property undefined" if not $_->{"id"};
  26. }
  27. #print "Saving ", $item->{"id"}, " to ", $filename, "\n";
  28. open (my $fh, '>', $filename) or die "couldn't open", $filename;
  29. print $fh encode_json($item);
  30. close $fh;
  31. }
  32. }
  33. sub apc2s_backup {
  34. my ($url) = @_;
  35. my $req = HTTP::Request->new(GET => $url);
  36. $req->header('Accept' => 'application/activity+json');
  37. $req->header('Authorization' => $auth);
  38. my $res = $ua->request($req);
  39. if($res->is_success) {
  40. print "Got $url\n";
  41. my $content_type = $res->header("Content-Type");
  42. my $content_match = /application\/([^+]*+)?json(; .*)?/;
  43. if($content_type =~ $content_match) {
  44. my $response = decode_json($res->content);
  45. if($response->{"type"} eq "OrderedCollection") {
  46. if(not $response->{"first"}) {
  47. die "“first” property of OrderedCollection undefined";
  48. }
  49. print "Fetching first property: ", $response->{"first"}, "\n";
  50. apc2s_backup($response->{"first"});
  51. } elsif($response->{"type"} eq "OrderedCollectionPage") {
  52. if($response->{"orderedItems"}) {
  53. save_collection($response->{"orderedItems"});
  54. print "next: ", $response->{"next"}, "\n" if $response->{"next"};
  55. print "prev: ", $response->{"prev"}, "\n" if $response->{"prev"};
  56. if($response->{"next"}) {
  57. print "Fetching next property\n";
  58. apc2s_backup($response->{"next"})
  59. } else {
  60. print "No “next” property defined, done?\n";
  61. }
  62. } else {
  63. die "OrderedCollectionPage without “orderedItems” defined is unsupported";
  64. }
  65. } elsif($response->{"type"} eq "Person") {
  66. if($response->{"outbox"}) {
  67. print "Fetching outbox property: ", $response->{"outbox"}, "\n";
  68. apc2s_backup($response->{"outbox"});
  69. } else {
  70. die "Person actor with no outbox";
  71. }
  72. } else {
  73. die "Unknown type: ", $response->{"type"};
  74. }
  75. } else {
  76. die "Got ", $content_type, " instead of ", $content_match;
  77. }
  78. } else {
  79. die "Got ", $res->status_line, " instead of 2xx";
  80. }
  81. }
  82. getopts("u:o:", \%options);
  83. if($#ARGV != 0) {
  84. print "usage: ap-backup.pl <-u user:password|-o OAuth-Bearer-Token> <url>\n";
  85. print "This tool is used to backup an account, authentication is required.\n";
  86. print " -u HTTP Basic Auth\n";
  87. print " -o OAuth2 Bearer Token\n";
  88. print " url ActivityPub user URL or outbox URL\n";
  89. print "Known to work against Pleroma.\n";
  90. print "Activities are saved in the current working directory via their id, it's recommended to launch in a dedicated directory.\n";
  91. exit 1;
  92. }
  93. if(defined $options{u}) {
  94. $auth = "Basic ".encode_base64($options{u});
  95. }
  96. if(defined $options{o}) {
  97. $auth = "Bearer ".$options{o};
  98. }
  99. print "Authorization: $auth";
  100. print "Fetching: ", $ARGV[0], "\n";
  101. apc2s_backup($ARGV[0]);