commit: 1f6251fe2a81cea9dc75e903ee4f34edd5b31f9e
parent 04b5573d848bc599a03f055e30547da4dee20dc3
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Mon, 27 Mar 2023 23:54:41 +0200
Modularize
Diffstat:
M | Makefile.PL | 28 | +++++++++++++++++++--------- |
D | ap-backup.pl | 119 | ------------------------------------------------------------------------------- |
D | ap-fetch.pl | 59 | ----------------------------------------------------------- |
D | ap-represent.pl | 67 | ------------------------------------------------------------------- |
A | lib/ActivityPub/PrettyPrint.pm | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | script/ap-backup | 127 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | script/ap-fetch | 97 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | script/ap-represent | 36 | ++++++++++++++++++++++++++++++++++++ |
8 files changed, 351 insertions(+), 254 deletions(-)
diff --git a/Makefile.PL b/Makefile.PL
@@ -1,16 +1,26 @@
+#!/usr/bin/env perl
+# AP-Client: CLI-based client / toolbox for ActivityPub
+# Copyright © 2020-2023 AP-Client Authors <https://hacktivis.me/git/ap-client/>
+# SPDX-License-Identifier: BSD-3-Clause
+use strict;
+use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
- NAME => 'App::ApClient',
- ABSTRACT => 'CLI-based client / toolbox for ActivityPub Client-to-Server',
- AUTHOR => 'Haelwenn (lanodan) Monnier <contact+ap-client@hacktivis.me>',
- LICENSE => 'bsd',
- VERSION => '0.1',
- EXE_FILES => ['ap-fetch.pl', 'ap-represent.pl', 'ap-backup.pl'],
+ NAME => 'App::ApClient',
+ ABSTRACT => 'CLI-based client / toolbox for ActivityPub Client-to-Server',
+ AUTHOR => 'Haelwenn (lanodan) Monnier <contact+ap-client@hacktivis.me>',
+ LICENSE => 'bsd',
+ VERSION => 'v0.1.0',
+ EXE_FILES =>
+ [ 'script/ap-fetch', 'script/ap-represent', 'script/ap-backup' ],
+ PREREQ_PM => {
+ 'JSON' => 0,
+ },
META_MERGE => {
- 'meta-spec' => { version => 2 },
+ 'meta-spec' => {version => 2},
release_status => 'stable',
- resources => {
+ resources => {
repository => {
type => 'git',
url => 'https://hacktivis.me/git/ap-client.git/',
@@ -18,4 +28,4 @@ WriteMakefile(
},
},
},
-)
+);
diff --git a/ap-backup.pl b/ap-backup.pl
@@ -1,119 +0,0 @@
-#!/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]);
diff --git a/ap-fetch.pl b/ap-fetch.pl
@@ -1,59 +0,0 @@
-#!/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 HTTP::Request::Common;
-
-my %options=();
-my $ua = LWP::UserAgent->new;
-
-
-getopts("rju:", \%options);
-
-if($#ARGV != 0) {
- print "usage: ap-fetch.pl [-r|-j|-u user:pass] <url>\n";
- print " -j Pipe into jq(1)\n";
- print " -r Raw output\n";
- print " -u user:pass HTTP Basic Auth credentials\n";
- print "By default, when -j and -r are absent it pipes the data into ap-represent.pl.\n";
- exit 1;
-}
-
-$ua->agent("AP-Client fetch <https://hacktivis.me/git/ap-client/>");
-my $req = HTTP::Request->new(GET => $ARGV[0]);
-$req->header('Accept' => 'application/activity+json');
-
-if(defined $options{u}) {
- my ($user, $password) = split(/:/, $options{u});
- $req->authorization_basic($user, $password);
-}
-
-my $res = $ua->request($req);
-
-if($res->is_success) {
- my $content_type = $res->header("Content-Type");
- my $content_match = /application\/([^+]*+)?json(; .*)?/;
-
- if($content_type =~ $content_match) {
- if(defined $options{r}) {
- print $res->content;
- } elsif(defined $options{j}) {
- open(my $pipe_out, '|-', 'jq .') or die "Couldn't open a pipe into jq: $!";
- print $pipe_out $res->content;
- } else {
- open(my $pipe_out, '|-', 'ap-represent.pl') or die "Couldn't open a pipe into ap-represent.pl: $!";
- print $pipe_out $res->content;
- }
- } else {
- print "Got $content_type instead of $content_match";
- }
-} else {
- print "Got ", $res->status_line, " instead of 2xx\n";
-}
diff --git a/ap-represent.pl b/ap-represent.pl
@@ -1,67 +0,0 @@
-#!/usr/bin/env perl
-# AP-Client: CLI-based client / toolbox for ActivityPub
-# Copyright © 2020 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 JSON::MaybeXS;
-use Data::Dumper;
-
-my $json = JSON::MaybeXS->new(utf8 => 1);
-
-sub print_object_key {
- my ($indent, $object, $key) = @_;
-
- if($object->{$key}) {
- print_ref($indent, $object->{$key}, $key);
- }
-}
-
-sub print_object {
- my ($indent, $object) = @_;
-
- printf "%*s %s", $indent, '⇒', $object->{"type"};
- printf ' id:<%s>', $object->{"id"} if $object->{"id"};
- printf ' href:<%s>', $object->{"href"} if $object->{"href"};
- printf ' “%s”', $object->{"name"} if $object->{"name"};
- printf ' @%s', $object->{"preferredUsername"} if $object->{"preferredUsername"};
- printf ' ⚠' if ($object->{"sensitive"} eq JSON->true);
- foreach("url", "subtitleLanguage", "context", "inbox", "outbox", "prev", "next", "published", "updated", "summary", "content", "bcc", "bto", "to", "cc", "object", "attachment", "tag", "orderedItems", "mediaType") {
- print_object_key($indent, $object, $_);
- }
-}
-
-sub print_ref {
- my ($indent, $object, $name) = @_;
-
- my $ref_type = reftype($object);
-
- if($ref_type eq 'HASH') {
- printf "\n%*s%s: \n", $indent, ' ', $name;
- print_object($indent+4, $object);
- } elsif($ref_type eq 'ARRAY') {
- printf "\n%*s%s: ", $indent, ' ', $name if @{$object};
- foreach(@{$object}) {
- if(reftype($_) eq 'HASH') {
- print "\n";
- print_object($indent+4, $_);
- } else {
- printf "%s ; ", $_;
- }
- }
- } else {
- printf "\n%*s%s: %s", $indent, ' ', $name, $object;
- }
-}
-
-my $blob;
-{
- undef $/;
- $blob = $json->decode(<STDIN>);
-}
-
-print_object(1, $blob);
-print "\n";
diff --git a/lib/ActivityPub/PrettyPrint.pm b/lib/ActivityPub/PrettyPrint.pm
@@ -0,0 +1,72 @@
+#!/usr/bin/env perl
+# AP-Client: CLI-based client / toolbox for ActivityPub
+# Copyright © 2020-2023 AP-Client Authors <https://hacktivis.me/git/ap-client/>
+# SPDX-License-Identifier: BSD-3-Clause
+package ActivityPub::PrettyPrint;
+our $VERSION = '0.1.0';
+use strict;
+use utf8;
+use open ":std", ":encoding(UTF-8)";
+
+use Scalar::Util qw(reftype);
+
+use Exporter 'import';
+
+our @EXPORT_OK = qw(print_object);
+
+sub print_object_key {
+ my ($indent, $object, $key) = @_;
+
+ if ($object->{$key}) {
+ print_ref($indent, $object->{$key}, $key);
+ }
+}
+
+sub print_object {
+ my ($indent, $object) = @_;
+
+ printf "%*s %s", $indent, '⇒', $object->{"type"};
+ printf ' id:<%s>', $object->{"id"} if $object->{"id"};
+ printf ' href:<%s>', $object->{"href"} if $object->{"href"};
+ printf ' “%s”', $object->{"name"} if $object->{"name"};
+ printf ' @%s', $object->{"preferredUsername"}
+ if $object->{"preferredUsername"};
+ printf ' ⚠' if ($object->{"sensitive"} eq JSON->true);
+ foreach (
+ "url", "subtitleLanguage", "context",
+ "inbox", "outbox", "prev",
+ "next", "published", "updated",
+ "summary", "content", "bcc",
+ "bto", "to", "cc",
+ "object", "attachment", "tag",
+ "orderedItems", "mediaType"
+ )
+ {
+ print_object_key($indent, $object, $_);
+ }
+}
+
+sub print_ref {
+ my ($indent, $object, $name) = @_;
+
+ my $ref_type = reftype($object);
+
+ if ($ref_type eq 'HASH') {
+ printf "\n%*s%s: \n", $indent, ' ', $name;
+ print_object($indent + 4, $object);
+ } elsif ($ref_type eq 'ARRAY') {
+ printf "\n%*s%s: ", $indent, ' ', $name if @{$object};
+ foreach (@{$object}) {
+ if (reftype($_) eq 'HASH') {
+ print "\n";
+ print_object($indent + 4, $_);
+ } else {
+ printf "%s ; ", $_;
+ }
+ }
+ } else {
+ printf "\n%*s%s: %s", $indent, ' ', $name, $object;
+ }
+}
+
+1;
diff --git a/script/ap-backup b/script/ap-backup
@@ -0,0 +1,127 @@
+#!/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]);
diff --git a/script/ap-fetch b/script/ap-fetch
@@ -0,0 +1,97 @@
+#!/usr/bin/env perl
+# AP-Client: CLI-based client / toolbox for ActivityPub
+# Copyright © 2020-2023 AP-Client Authors <https://hacktivis.me/git/ap-client/>
+# SPDX-License-Identifier: BSD-3-Clause
+package App::ApClient;
+
+use strict;
+use utf8;
+no warnings; # Wide Character…
+
+use Getopt::Std;
+use LWP::UserAgent;
+use HTTP::Request::Common;
+use JSON;
+use ActivityPub::PrettyPrint qw(print_object);
+
+=head1 NAME
+
+ap-fetch - Fetch ActivityStream object, optionally pretty printing it
+
+=head1 SYNOPSIS
+
+B<ap-fetch> [-r|-j|-u <user:pass>] <URI>
+
+=head1 DESCRIPTION
+
+ap-fetch fetches an URI, decodes it as an ActivityStream object.
+
+=item -j
+
+Pipe into jq(1)
+
+=item -r
+
+Raw output, print server's output without any decoding
+
+=item -u user:pass
+
+Pass username and password for HTTP Basic Auth.
+
+=head1 LICENSE
+
+BSD-3-Clause
+
+=cut
+
+my %options = ();
+my $ua = LWP::UserAgent->new;
+
+getopts("rju:", \%options);
+
+if ($#ARGV != 0) {
+ print "usage: ap-fetch.pl [-r|-j|-u user:pass] <url>\n";
+ print " -j Pipe into jq(1)\n";
+ print " -r Raw output\n";
+ print " -u user:pass HTTP Basic Auth credentials\n";
+ print
+"By default, when -j and -r are absent it pipes the data into ap-represent.pl.\n";
+ exit 1;
+}
+
+$ua->agent("AP-Client fetch <https://hacktivis.me/git/ap-client/>");
+my $req = HTTP::Request->new(GET => $ARGV[0]);
+$req->header('Accept' => 'application/activity+json');
+
+if (defined $options{u}) {
+ my ($user, $password) = split(/:/, $options{u});
+ $req->authorization_basic($user, $password);
+}
+
+my $res = $ua->request($req);
+
+if ($res->is_success) {
+ my $content_type = $res->header("Content-Type");
+ my $content_match = /application\/([^+]*+)?json(; .*)?/;
+
+ if ($content_type =~ $content_match) {
+ if (defined $options{r}) {
+ print $res->content;
+ } elsif (defined $options{j}) {
+ open(my $pipe_out, '|-', 'jq .')
+ or die "Couldn't open a pipe into jq: $!";
+ print $pipe_out $res->content;
+ close($pipe_out);
+ } else {
+ my $object = decode_json($res->content);
+ print_object(1, $object);
+ print "\n";
+ }
+ } else {
+ print STDERR "Got $content_type instead of $content_match\n";
+ exit 1;
+ }
+} else {
+ print STDERR "Got $res->status_line instead of 2xx\n";
+ exit 1;
+}
diff --git a/script/ap-represent b/script/ap-represent
@@ -0,0 +1,36 @@
+#!/usr/bin/env perl
+# AP-Client: CLI-based client / toolbox for ActivityPub
+# Copyright © 2020-2023 AP-Client Authors <https://hacktivis.me/git/ap-client/>
+# SPDX-License-Identifier: BSD-3-Clause
+use strict;
+use utf8;
+no warnings; # Wide Character…
+
+use JSON;
+use ActivityPub::PrettyPrint qw(print_object);
+
+=head1 NAME
+
+ap-represent - Pretty-print ActivityStreams data
+
+=head1 SYNOPSIS
+
+B<ap-represent>
+
+=head1 DESCRIPTION
+
+ap-represent takes JSON-formatted ActivityStreams data from standard input and
+pretty prints it to stdout.
+
+Said output isn't made to be readable by machines, only humans.
+
+=head1 LICENSE
+
+BSD-3-Clause
+
+=cut
+
+undef $/;
+my $blob = decode_json(<STDIN>);
+print_object(1, $blob);
+print "\n";