#!/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]);
