logo

ap-client

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

ap-backup (3689B)


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