#!/usr/bin/env perl # requires Mojolicious installed # cpanm Mojolicious # will sort that out on most Linuxes use strict; use v5.38; use feature 'try'; no warnings 'experimental::try'; use Mojo::JSON qw/encode_json/; use Mojo::URL qw//; use Mojo::UserAgent qw//; use constant PAGE_SIZE => 100; use constant MAX_PAGES => 10_000; my ($pds, $did, $collection) = (@ARGV); my $ua = Mojo::UserAgent->new(max_redirects => 3, connect_timeout => 10, request_timeout => 60); $ua->transactor->name('totally-not-a-bad-actor/1.0'); my @records; my $cursor; for my $page (1 .. MAX_PAGES) { my $url = Mojo::URL->new($pds)->path('/xrpc/com.atproto.repo.listRecords') ->query(repo => $did, collection => $collection, limit => PAGE_SIZE, (defined($cursor) ? (cursor => $cursor) : ())); my $body = get_json($ua, $url); my $batch = ref($body->{records}) eq 'ARRAY' ? $body->{records} : []; push(@records, map { {uri => $_->{uri}, cid => $_->{cid}, value => $_->{value}} } @$batch); say STDERR 'page ', $page, ': ', scalar(@$batch), ' record(s), ', scalar(@records), ' so far'; # No cursor, or an empty page, means the end; a cursor that doesn't # move would loop forever, so that ends it too. my $next = $body->{cursor}; last unless (@$batch && defined($next) && length($next)); last if (defined($cursor) && $next eq $cursor); $cursor = $next; } print encode_json(\@records), "\n"; sub get_json ($ua, $url) { my $res = $ua->get($url)->result; die('GET ' . $url . ' failed: ' . $res->code . ' ' . ($res->message // '') . ' ' . substr($res->body // '', 0, 300) . "\n") unless $res->is_success; my $json = $res->json; die('GET ' . $url . ' did not return a JSON object' . "\n") unless ref($json) eq 'HASH'; return $json; }