@@ -4,6 +4,7 @@ use colored::Colorize;
4
4
use csv:: Writer ;
5
5
use futures:: stream:: StreamExt ;
6
6
use std:: { fmt:: Display , fs:: File , sync:: Arc , time:: Duration } ;
7
+ use url:: Url ;
7
8
use wp_api:: {
8
9
comments:: CommentListParams ,
9
10
parsed_url:: ParsedUrl ,
@@ -63,6 +64,46 @@ async fn main() -> Result<()> {
63
64
Ok ( ( ) )
64
65
}
65
66
67
+ async fn resolve_post_id ( client : & WpApiClient , args : & FetchPostArgs ) -> Result < PostId > {
68
+ if let Some ( id) = args. post_id {
69
+ return Ok ( id) ;
70
+ }
71
+ let Some ( post_url) = & args. post_url else {
72
+ return Err ( anyhow ! ( "Either --post-id or --post-url must be provided" ) ) ;
73
+ } ;
74
+
75
+ // Strategy: retrieve by slug via posts list API when possible.
76
+ // For wp.com, the resolver requires site context; for wp.org, api_root is given.
77
+ // We'll try to parse the URL and extract a last path segment as potential slug.
78
+ let url = Url :: parse ( post_url) . map_err ( |e| anyhow ! ( "Invalid --post-url: {e}" ) ) ?;
79
+ let slug_candidate = url
80
+ . path_segments ( )
81
+ . and_then ( |segs| segs. filter ( |s| !s. is_empty ( ) ) . last ( ) )
82
+ . map ( |s| s. trim_end_matches ( '/' ) )
83
+ . unwrap_or ( "" )
84
+ . to_string ( ) ;
85
+
86
+ if slug_candidate. is_empty ( ) {
87
+ return Err ( anyhow ! ( "Could not parse a slug from --post-url" ) ) ;
88
+ }
89
+
90
+ // Query posts by slug; returns an array, take first match.
91
+ // Using view context to ensure public content shape.
92
+ let mut params = wp_api:: posts:: PostListParams :: default ( ) ;
93
+ params. slug = vec ! [ slug_candidate. clone( ) ] ;
94
+ params. per_page = Some ( 1 ) ;
95
+ let resp = client. posts ( ) . list_with_view_context ( & params) . await ?;
96
+ if let Some ( p) = resp. data . into_iter ( ) . find_map ( |sp| Some ( sp. id ) ) {
97
+ return Ok ( p) ;
98
+ }
99
+
100
+ Err ( anyhow ! (
101
+ "No post found for slug '{slug}' parsed from URL '{url}'" ,
102
+ slug = slug_candidate,
103
+ url = post_url
104
+ ) )
105
+ }
106
+
66
107
async fn discover_login_url ( login_client : & WpLoginClient , site : String ) {
67
108
let intro = format ! ( "Discovering login URL for {site}" ) . blue ( ) ;
68
109
println ! ( "{intro}" ) ;
@@ -140,11 +181,19 @@ fn build_login_client() -> WpLoginClient {
140
181
ArgGroup :: new( "target" )
141
182
. required( true )
142
183
. args( [ "wpcom_site" , "api_root" ] ) ,
184
+ ) , group(
185
+ ArgGroup :: new( "post_ref" )
186
+ . required( true )
187
+ . args( [ "post_id" , "post_url" ] ) ,
143
188
) ) ]
144
189
struct FetchPostArgs {
145
190
/// The post ID to fetch
146
191
#[ arg( long, value_parser = parse_post_id) ]
147
- post_id : PostId ,
192
+ post_id : Option < PostId > ,
193
+
194
+ /// Full post URL (alternative to --post-id)
195
+ #[ arg( long) ]
196
+ post_url : Option < String > ,
148
197
149
198
/// For WordPress.com: site identifier (e.g. example.wordpress.com or numeric site id)
150
199
#[ arg( long) ]
@@ -269,10 +318,12 @@ fn build_api_client(args: &FetchPostArgs) -> Result<WpApiClient> {
269
318
async fn fetch_post_and_comments ( args : FetchPostArgs ) -> Result < ( ) > {
270
319
let client = build_api_client ( & args) ?;
271
320
321
+ let post_id = resolve_post_id ( & client, & args) . await ?;
322
+
272
323
let post = client
273
324
. posts ( )
274
325
. retrieve_with_view_context (
275
- & args . post_id ,
326
+ & post_id,
276
327
& PostRetrieveParams {
277
328
password : args. post_password . clone ( ) ,
278
329
} ,
@@ -283,7 +334,7 @@ async fn fetch_post_and_comments(args: FetchPostArgs) -> Result<()> {
283
334
let mut page = client
284
335
. comments ( )
285
336
. list_with_view_context ( & CommentListParams {
286
- post : vec ! [ args . post_id] ,
337
+ post : vec ! [ post_id] ,
287
338
per_page : Some ( args. per_page ) ,
288
339
..Default :: default ( )
289
340
} )
0 commit comments