@@ -2,28 +2,44 @@ import { colors } from "@cliffy/ansi";
22import { Command } from "@cliffy/command" ;
33import {
44 Application ,
5+ Collection ,
56 CryptographicKey ,
67 type DocumentLoader ,
78 generateCryptoKeyPair ,
89 getAuthenticatedDocumentLoader ,
10+ type Link ,
911 lookupObject ,
1012 type Object ,
1113 type ResourceDescriptor ,
1214 respondWithObject ,
15+ traverseCollection ,
1316} from "@fedify/fedify" ;
17+ import { getLogger } from "@logtape/logtape" ;
1418import ora from "ora" ;
1519import { getContextLoader , getDocumentLoader } from "./docloader.ts" ;
1620import { spawnTemporaryServer , type TemporaryServer } from "./tempserver.ts" ;
1721import { printJson } from "./utils.ts" ;
1822
23+ const logger = getLogger ( [ "fedify" , "cli" , "lookup" ] ) ;
24+
1925export const command = new Command ( )
2026 . arguments ( "<...urls:string>" )
2127 . description (
2228 "Lookup an Activity Streams object by URL or the actor handle. " +
2329 "The argument can be either a URL or an actor handle " +
24- "(e.g., @username@domain)." ,
30+ "(e.g., @username@domain), and it can be multiple ." ,
2531 )
2632 . option ( "-a, --authorized-fetch" , "Sign the request with an one-time key." )
33+ . option (
34+ "-t, --traverse" ,
35+ "Traverse the given collection to fetch all items. If it is turned on, " +
36+ "the argument cannot be multiple." ,
37+ )
38+ . option (
39+ "-S, --suppress-errors" ,
40+ "Suppress partial errors while traversing the collection." ,
41+ { depends : [ "traverse" ] } ,
42+ )
2743 . option ( "-r, --raw" , "Print the fetched JSON-LD document as is." , {
2844 conflicts : [ "compact" , "expand" ] ,
2945 } )
@@ -36,12 +52,24 @@ export const command = new Command()
3652 . option ( "-u, --user-agent <string>" , "The custom User-Agent header value." )
3753 . option (
3854 "-s, --separator <string>" ,
39- "Specify the separator between adjacent output objects." ,
55+ "Specify the separator between adjacent output objects or " +
56+ "collection items." ,
4057 { default : "----" } ,
4158 )
4259 . action ( async ( options , ...urls : string [ ] ) => {
60+ if ( urls . length < 1 ) {
61+ console . error ( "At least one URL or actor handle must be provided." ) ;
62+ Deno . exit ( 1 ) ;
63+ } else if ( options . traverse && urls . length > 1 ) {
64+ console . error (
65+ "The -t/--traverse option cannot be used with multiple arguments." ,
66+ ) ;
67+ Deno . exit ( 1 ) ;
68+ }
4369 const spinner = ora ( {
44- text : "Looking up the object..." ,
70+ text : `Looking up the ${
71+ options . traverse ? "collection" : urls . length > 1 ? "objects" : "object"
72+ } ...`,
4573 discardStdin : false ,
4674 } ) . start ( ) ;
4775 let server : TemporaryServer | undefined = undefined ;
@@ -95,9 +123,84 @@ export const command = new Command()
95123 privateKey : key . privateKey ,
96124 } ) ;
97125 }
98- spinner . text = urls . length > 1
99- ? "Looking up objects..."
100- : "Looking up an object..." ;
126+ spinner . text = `Looking up the ${
127+ options . traverse ? "collection" : urls . length > 1 ? "objects" : "object"
128+ } ...`;
129+
130+ async function printObject ( object : Object | Link ) : Promise < void > {
131+ if ( options . raw ) {
132+ printJson ( await object . toJsonLd ( { contextLoader } ) ) ;
133+ } else if ( options . compact ) {
134+ printJson (
135+ await object . toJsonLd ( { format : "compact" , contextLoader } ) ,
136+ ) ;
137+ } else if ( options . expand ) {
138+ printJson (
139+ await object . toJsonLd ( { format : "expand" , contextLoader } ) ,
140+ ) ;
141+ } else {
142+ console . log ( object ) ;
143+ }
144+ }
145+
146+ if ( options . traverse ) {
147+ const url = urls [ 0 ] ;
148+ const collection = await lookupObject ( url , {
149+ documentLoader : authLoader ?? documentLoader ,
150+ contextLoader,
151+ userAgent : options . userAgent ,
152+ } ) ;
153+ if ( collection == null ) {
154+ spinner . fail ( `Failed to fetch object: ${ colors . red ( url ) } .` ) ;
155+ if ( authLoader == null ) {
156+ console . error (
157+ "It may be a private object. Try with -a/--authorized-fetch." ,
158+ ) ;
159+ }
160+ await server ?. close ( ) ;
161+ Deno . exit ( 1 ) ;
162+ }
163+ if ( ! ( collection instanceof Collection ) ) {
164+ spinner . fail (
165+ `Not a collection: ${ colors . red ( url ) } . ` +
166+ "The -t/--traverse option requires a collection." ,
167+ ) ;
168+ await server ?. close ( ) ;
169+ Deno . exit ( 1 ) ;
170+ }
171+ spinner . succeed ( `Fetched collection: ${ colors . green ( url ) } .` ) ;
172+ try {
173+ let i = 0 ;
174+ for await (
175+ const item of traverseCollection ( collection , {
176+ documentLoader : authLoader ?? documentLoader ,
177+ contextLoader,
178+ suppressError : options . suppressErrors ,
179+ } )
180+ ) {
181+ if ( i > 0 ) console . log ( options . separator ) ;
182+ printObject ( item ) ;
183+ i ++ ;
184+ }
185+ } catch ( error ) {
186+ logger . error ( "Failed to complete the traversal: {error}" , { error } ) ;
187+ spinner . fail ( "Failed to complete the traversal." ) ;
188+ if ( authLoader == null ) {
189+ console . error (
190+ "It may be a private object. Try with -a/--authorized-fetch." ,
191+ ) ;
192+ } else {
193+ console . error (
194+ "Use the -S/--suppress-errors option to suppress partial errors." ,
195+ ) ;
196+ }
197+ await server ?. close ( ) ;
198+ Deno . exit ( 1 ) ;
199+ }
200+ spinner . succeed ( "Successfully fetched all items in the collection." ) ;
201+ await server ?. close ( ) ;
202+ Deno . exit ( 0 ) ;
203+ }
101204
102205 const promises : Promise < Object | null > [ ] = [ ] ;
103206 for ( const url of urls ) {
@@ -131,19 +234,7 @@ export const command = new Command()
131234 success = false ;
132235 } else {
133236 spinner . succeed ( `Fetched object: ${ colors . green ( url ) } .` ) ;
134- if ( options . raw ) {
135- printJson ( await object . toJsonLd ( { contextLoader } ) ) ;
136- } else if ( options . compact ) {
137- printJson (
138- await object . toJsonLd ( { format : "compact" , contextLoader } ) ,
139- ) ;
140- } else if ( options . expand ) {
141- printJson (
142- await object . toJsonLd ( { format : "expand" , contextLoader } ) ,
143- ) ;
144- } else {
145- console . log ( object ) ;
146- }
237+ printObject ( object ) ;
147238 if ( i < urls . length - 1 ) {
148239 console . log ( options . separator ) ;
149240 }
0 commit comments