1
- use std:: env;
1
+ //! HTTP Tracker client:
2
+ //!
3
+ //! Examples:
4
+ //!
5
+ //! `Announce` request:
6
+ //!
7
+ //! ```text
8
+ //! cargo run --bin http_tracker_client announce http://127.0.0.1:7070 9c38422213e30bff212b30c360d26f9a02136422 | jq
9
+ //! ```
10
+ //!
11
+ //! `Scrape` request:
12
+ //!
13
+ //! ```text
14
+ //! cargo run --bin http_tracker_client scrape http://127.0.0.1:7070 9c38422213e30bff212b30c360d26f9a02136422 | jq
15
+ //! ```
2
16
use std:: str:: FromStr ;
3
17
18
+ use anyhow:: Context ;
19
+ use clap:: { Parser , Subcommand } ;
4
20
use reqwest:: Url ;
5
21
use torrust_tracker:: shared:: bit_torrent:: info_hash:: InfoHash ;
6
22
use torrust_tracker:: shared:: bit_torrent:: tracker:: http:: client:: requests:: announce:: QueryBuilder ;
7
23
use torrust_tracker:: shared:: bit_torrent:: tracker:: http:: client:: responses:: announce:: Announce ;
8
- use torrust_tracker:: shared:: bit_torrent:: tracker:: http:: client:: Client ;
24
+ use torrust_tracker:: shared:: bit_torrent:: tracker:: http:: client:: responses:: scrape;
25
+ use torrust_tracker:: shared:: bit_torrent:: tracker:: http:: client:: { requests, Client } ;
26
+
27
+ #[ derive( Parser , Debug ) ]
28
+ #[ command( author, version, about, long_about = None ) ]
29
+ struct Args {
30
+ #[ command( subcommand) ]
31
+ command : Command ,
32
+ }
33
+
34
+ #[ derive( Subcommand , Debug ) ]
35
+ enum Command {
36
+ Announce { tracker_url : String , info_hash : String } ,
37
+ Scrape { tracker_url : String , info_hashes : Vec < String > } ,
38
+ }
9
39
10
40
#[ tokio:: main]
11
- async fn main ( ) {
12
- let args: Vec < String > = env:: args ( ) . collect ( ) ;
13
- if args. len ( ) != 3 {
14
- eprintln ! ( "Error: invalid number of arguments!" ) ;
15
- eprintln ! ( "Usage: cargo run --bin http_tracker_client <HTTP_TRACKER_URL> <INFO_HASH>" ) ;
16
- eprintln ! ( "Example: cargo run --bin http_tracker_client https://tracker.torrust-demo.com 9c38422213e30bff212b30c360d26f9a02136422" ) ;
17
- std:: process:: exit ( 1 ) ;
41
+ async fn main ( ) -> anyhow:: Result < ( ) > {
42
+ let args = Args :: parse ( ) ;
43
+
44
+ match args. command {
45
+ Command :: Announce { tracker_url, info_hash } => {
46
+ announce_command ( tracker_url, info_hash) . await ?;
47
+ }
48
+ Command :: Scrape {
49
+ tracker_url,
50
+ info_hashes,
51
+ } => {
52
+ scrape_command ( & tracker_url, & info_hashes) . await ?;
53
+ }
18
54
}
55
+ Ok ( ( ) )
56
+ }
19
57
20
- let base_url = Url :: parse ( & args[ 1 ] ) . expect ( "arg 1 should be a valid HTTP tracker base URL" ) ;
21
- let info_hash = InfoHash :: from_str ( & args[ 2 ] ) . expect ( "arg 2 should be a valid infohash" ) ;
58
+ async fn announce_command ( tracker_url : String , info_hash : String ) -> anyhow:: Result < ( ) > {
59
+ let base_url = Url :: parse ( & tracker_url) . context ( "failed to parse HTTP tracker base URL" ) ?;
60
+ let info_hash =
61
+ InfoHash :: from_str ( & info_hash) . expect ( "Invalid infohash. Example infohash: `9c38422213e30bff212b30c360d26f9a02136422`" ) ;
22
62
23
63
let response = Client :: new ( base_url)
24
64
. announce ( & QueryBuilder :: with_default_values ( ) . with_info_hash ( & info_hash) . query ( ) )
@@ -27,9 +67,30 @@ async fn main() {
27
67
let body = response. bytes ( ) . await . unwrap ( ) ;
28
68
29
69
let announce_response: Announce = serde_bencode:: from_bytes ( & body)
30
- . unwrap_or_else ( |_| panic ! ( "response body should be a valid announce response, got \" {:#?}\" " , & body) ) ;
70
+ . unwrap_or_else ( |_| panic ! ( "response body should be a valid announce response, got: \" {:#?}\" " , & body) ) ;
71
+
72
+ let json = serde_json:: to_string ( & announce_response) . context ( "failed to serialize scrape response into JSON" ) ?;
73
+
74
+ println ! ( "{json}" ) ;
75
+
76
+ Ok ( ( ) )
77
+ }
78
+
79
+ async fn scrape_command ( tracker_url : & str , info_hashes : & [ String ] ) -> anyhow:: Result < ( ) > {
80
+ let base_url = Url :: parse ( tracker_url) . context ( "failed to parse HTTP tracker base URL" ) ?;
81
+
82
+ let query = requests:: scrape:: Query :: try_from ( info_hashes) . context ( "failed to parse infohashes" ) ?;
83
+
84
+ let response = Client :: new ( base_url) . scrape ( & query) . await ;
85
+
86
+ let body = response. bytes ( ) . await . unwrap ( ) ;
87
+
88
+ let scrape_response = scrape:: Response :: try_from_bencoded ( & body)
89
+ . unwrap_or_else ( |_| panic ! ( "response body should be a valid scrape response, got: \" {:#?}\" " , & body) ) ;
90
+
91
+ let json = serde_json:: to_string ( & scrape_response) . context ( "failed to serialize scrape response into JSON" ) ?;
31
92
32
- let json = serde_json :: to_string ( & announce_response ) . expect ( "announce response should be a valid JSON ") ;
93
+ println ! ( "{json} ") ;
33
94
34
- print ! ( "{json}" ) ;
95
+ Ok ( ( ) )
35
96
}
0 commit comments