1
1
use anyhow:: { Result , anyhow} ;
2
- use clap:: { ArgGroup , Parser , Subcommand } ;
2
+ use clap:: { ArgGroup , Args , Parser , Subcommand } ;
3
3
use colored:: Colorize ;
4
4
use csv:: Writer ;
5
5
use futures:: stream:: StreamExt ;
@@ -68,7 +68,7 @@ async fn resolve_post_id(client: &WpApiClient, args: &FetchPostArgs) -> Result<P
68
68
if let Some ( id) = args. post_id {
69
69
return Ok ( id) ;
70
70
}
71
- let Some ( post_url) = & args. post_url else {
71
+ let Some ( post_url) = & args. url else {
72
72
return Err ( anyhow ! ( "Either --post-id or --post-url must be provided" ) ) ;
73
73
} ;
74
74
@@ -179,40 +179,25 @@ fn build_login_client() -> WpLoginClient {
179
179
#[ derive( Debug , Parser ) ]
180
180
#[ command( group(
181
181
ArgGroup :: new( "target" )
182
- . args( [ "wpcom_site" , "api_root" ] ) ,
182
+ . args( [ "wpcom_site" , "api_root" , "url" ] ) ,
183
183
) , group(
184
184
ArgGroup :: new( "post_ref" )
185
185
. required( true )
186
- . args( [ "post_id" , "post_url " ] ) ,
186
+ . args( [ "post_id" , "url " ] ) ,
187
187
) ) ]
188
188
struct FetchPostArgs {
189
- /// The post ID to fetch
190
- #[ arg ( long , value_parser = parse_post_id ) ]
191
- post_id : Option < PostId > ,
189
+ /// Common authentication and target parameters
190
+ #[ command ( flatten ) ]
191
+ auth : AuthArgs ,
192
192
193
193
/// Full post URL (alternative to --post-id)
194
+ /// When provided, this URL is used to infer the site (wp.com) or autodiscover the API root (wp.org/Jetpack).
194
195
#[ arg( long) ]
195
- post_url : Option < String > ,
196
-
197
- /// For WordPress.com: site identifier (e.g. example.wordpress.com or numeric site id)
198
- #[ arg( long) ]
199
- wpcom_site : Option < String > ,
200
-
201
- /// For WordPress.org/Jetpack: full API root URL (must end with /wp-json)
202
- #[ arg( long) ]
203
- api_root : Option < String > ,
204
-
205
- /// Bearer token for WordPress.com (fallback env: WP_BEARER_TOKEN)
206
- #[ arg( long) ]
207
- bearer : Option < String > ,
208
-
209
- /// Application Password username for wp.org/Jetpack (fallback env: WP_USERNAME)
210
- #[ arg( long) ]
211
- username : Option < String > ,
196
+ url : Option < String > ,
212
197
213
- /// Application Password for wp.org/Jetpack (fallback env: WP_APP_PASSWORD)
214
- #[ arg( long) ]
215
- password : Option < String > ,
198
+ /// The post ID to fetch
199
+ #[ arg( long, value_parser = parse_post_id ) ]
200
+ post_id : Option < PostId > ,
216
201
217
202
/// Password for the post if it is password-protected
218
203
#[ arg( long) ]
@@ -239,7 +224,30 @@ enum TargetSiteResolver {
239
224
WpOrg { api_root : Arc < ParsedUrl > } ,
240
225
}
241
226
242
- async fn build_api_client ( args : & FetchPostArgs ) -> Result < WpApiClient > {
227
+ #[ derive( Debug , Args , Clone ) ]
228
+ struct AuthArgs {
229
+ /// WordPress.com site (e.g. example.wordpress.com or numeric ID)
230
+ #[ arg( long) ]
231
+ wpcom_site : Option < String > ,
232
+
233
+ /// WordPress.org/Jetpack API root (must end with /wp-json)
234
+ #[ arg( long) ]
235
+ api_root : Option < String > ,
236
+
237
+ /// Bearer token for WordPress.com (fallback env: WP_BEARER_TOKEN)
238
+ #[ arg( long) ]
239
+ bearer : Option < String > ,
240
+
241
+ /// Application Password username for wp.org/Jetpack (fallback env: WP_USERNAME)
242
+ #[ arg( long) ]
243
+ username : Option < String > ,
244
+
245
+ /// Application Password for wp.org/Jetpack (fallback env: WP_APP_PASSWORD)
246
+ #[ arg( long) ]
247
+ password : Option < String > ,
248
+ }
249
+
250
+ async fn build_api_client ( args : & AuthArgs , url : & Option < String > ) -> Result < WpApiClient > {
243
251
let request_executor = Arc :: new ( ReqwestRequestExecutor :: new ( false , Duration :: from_secs ( 60 ) ) ) ;
244
252
let middleware_pipeline = Arc :: new ( WpApiMiddlewarePipeline :: default ( ) ) ;
245
253
// Determine target and auth
@@ -248,38 +256,46 @@ async fn build_api_client(args: &FetchPostArgs) -> Result<WpApiClient> {
248
256
TargetSiteResolver :: WpCom { site : site. clone ( ) }
249
257
} else if let Some ( api_root) = & args. api_root {
250
258
// Explicit api_root takes priority for wp.org/Jetpack
251
- let parsed = ParsedUrl :: try_from ( api_root. as_str ( ) )
252
- . map_err ( |_| anyhow ! ( "Invalid api_root URL: must be a valid URL ending with /wp-json" ) ) ?;
253
- TargetSiteResolver :: WpOrg { api_root : Arc :: new ( parsed) }
254
- } else if let Some ( post_url) = & args. post_url {
255
- // Derive from post_url if possible
256
- if let Ok ( u) = Url :: parse ( post_url) {
259
+ let parsed = ParsedUrl :: try_from ( api_root. as_str ( ) ) . map_err ( |_| {
260
+ anyhow ! ( "Invalid api_root URL: must be a valid URL ending with /wp-json" )
261
+ } ) ?;
262
+ TargetSiteResolver :: WpOrg {
263
+ api_root : Arc :: new ( parsed) ,
264
+ }
265
+ } else if let Some ( url) = url {
266
+ // Derive from URL if possible
267
+ if let Ok ( u) = Url :: parse ( url. as_str ( ) ) {
257
268
let host = u. host_str ( ) . unwrap_or ( "" ) ;
258
269
if host. ends_with ( ".wordpress.com" ) {
259
- TargetSiteResolver :: WpCom { site : host. to_string ( ) }
270
+ TargetSiteResolver :: WpCom {
271
+ site : host. to_string ( ) ,
272
+ }
260
273
} else {
261
- // Attempt autodiscovery of API root from post URL
262
- let login_client = WpLoginClient :: new_with_default_middleware_pipeline ( request_executor. clone ( ) ) ;
274
+ // Attempt autodiscovery of API root from URL
275
+ let login_client =
276
+ WpLoginClient :: new_with_default_middleware_pipeline ( request_executor. clone ( ) ) ;
263
277
match login_client
264
- . api_discovery ( post_url . clone ( ) )
278
+ . api_discovery ( url . clone ( ) )
265
279
. await
266
280
. combined_result ( )
267
281
. cloned ( )
268
282
{
269
- Ok ( success) => TargetSiteResolver :: WpOrg { api_root : success. api_root_url } ,
283
+ Ok ( success) => TargetSiteResolver :: WpOrg {
284
+ api_root : success. api_root_url ,
285
+ } ,
270
286
Err ( _) => {
271
287
return Err ( anyhow ! (
272
- "Could not autodiscover API root from --post-url . Please provide --api-root explicitly."
288
+ "Could not autodiscover API root from URL . Please provide --api-root explicitly."
273
289
) ) ;
274
290
}
275
291
}
276
292
}
277
293
} else {
278
- return Err ( anyhow ! ( "Invalid --post-url ; could not parse URL " ) ) ;
294
+ return Err ( anyhow ! ( "Invalid URL ; could not parse" ) ) ;
279
295
}
280
296
} else {
281
297
return Err ( anyhow ! (
282
- "Provide either --wpcom-site, or --api-root, or a wordpress.com --post-url "
298
+ "Provide either --wpcom-site, or --api-root, or a wordpress.com URL "
283
299
) ) ;
284
300
} ;
285
301
@@ -339,7 +355,7 @@ async fn build_api_client(args: &FetchPostArgs) -> Result<WpApiClient> {
339
355
}
340
356
341
357
async fn fetch_post_and_comments ( args : FetchPostArgs ) -> Result < ( ) > {
342
- let client = build_api_client ( & args) . await ?;
358
+ let client = build_api_client ( & args. auth , & args . url ) . await ?;
343
359
344
360
let post_id = resolve_post_id ( & client, & args) . await ?;
345
361
0 commit comments