jackett_async/
lib.rs

1#![allow(unused_parens)]
2
3#[cfg(feature = "require-parse-names")]
4use std::convert::TryFrom;
5
6use itertools::Itertools;
7use tracing::instrument;
8
9mod error;
10pub use error::Error;
11mod model;
12use model::QueryResult;
13
14#[cfg(any(feature = "parse-names", feature = "require-parse-names"))]
15/// Re-exported from [`torrent-common`](torrent_common::Metadata)
16pub use torrent_common::Metadata;
17
18#[cfg(feature = "require-parse-names")]
19/// Re-exported from [`torrent-common`](torrent_common::ParseError)
20pub use torrent_common::ParseError;
21
22/// Re-exported from [`torrent-common`](torrent_common::Torrent)
23pub use torrent_common::Torrent;
24
25static MOVIE_CATEGORIES: [&str; 9] = ["2000", "2010", "2030", "2040", "2045", "2050", "2060", "2070", "2080"];
26static TV_CATEGORIES: [&str; 8] = ["5000", "5010", "5020", "5030", "5040", "5060", "5070", "5080"];
27static AUDIO_CATEGORIES: [&str; 6] = ["3000", "3010", "3020", "3030", "3040", "3050"];
28
29#[derive(Clone)]
30pub struct Client {
31	http: reqwest::Client,
32	base_url: String,
33	apikey: String
34}
35
36fn category_parameters(categories: &[&str]) -> String {
37	std::iter::repeat("&Category[]=").interleave_shortest(categories.iter().copied()).collect()
38}
39
40fn build_url(base_url: &str, apikey: &str, query: &str, categories: Option<&[&str]>) -> String {
41	format!(
42		"{}?apikey={}&Query={}{}",
43		base_url,
44		apikey,
45		urlencoding::encode(query),
46		categories.map(category_parameters).unwrap_or_default()
47	)
48}
49
50impl Client {
51	#[instrument(err, level = "info", skip(base_url, apikey), fields(base_url = %base_url.to_string(), apikey = %apikey.to_string()))]
52	pub fn new(base_url: impl ToString, apikey: impl ToString) -> Result<Self, reqwest::Error> {
53		let this = Self{
54			http: reqwest::Client::builder()
55				.gzip(true)
56				.build()?,
57			base_url: base_url.to_string(),
58			apikey: apikey.to_string()
59		};
60		Ok(this)
61	}
62
63	#[instrument(err, level = "debug", skip(self))]
64	async fn _get(&self, query: &str, categories: Option<&[&str]>) -> Result<reqwest::Response, reqwest::Error> {
65		let url = build_url(&self.base_url, &self.apikey, query, categories);
66		self.http.get(&url).send().await?.error_for_status()
67	}
68
69	#[cfg(not(feature = "require-parse-names"))]
70	#[instrument(err, level = "debug", skip(self))]
71	async fn get(&self, query: &str, categories: Option<&[&str]>) -> Result<Vec<Torrent>, Error> {
72		let response = self._get(query, categories).await?;
73		Ok(response.json::<QueryResult>().await?.results.into_iter().map(Torrent::from).collect())
74	}
75
76	#[cfg(feature = "require-parse-names")]
77	#[instrument(err, level = "debug", skip(self))]
78	async fn get(&self, query: &str, categories: Option<&[&str]>) -> Result<Vec<Result<Torrent, ParseError>>, Error> {
79		Ok(self._get(query, categories).await?.json::<QueryResult>().await?.results.into_iter().map(Torrent::try_from).collect())
80	}
81
82	#[cfg(not(feature = "require-parse-names"))]
83	#[instrument(err, level = "info", skip(self))]
84	pub async fn search(&self, query: &str, categories: Option<&[&str]>) -> Result<Vec<Torrent>, Error> {
85		if let Some(categories) = categories {
86			let categories = categories.iter().copied().map(urlencoding::encode).collect::<Vec<_>>();
87			self.get(query, Some(&categories.iter().map(AsRef::as_ref).collect::<Vec<_>>())).await
88		} else {
89			self.get(query, None).await
90		}
91	}
92
93	#[cfg(feature = "require-parse-names")]
94	#[instrument(err, level = "info", skip(self))]
95	pub async fn search(&self, query: &str, categories: Option<&[&str]>) -> Result<Vec<Result<Torrent, ParseError>>, Error> {
96		if let Some(categories) = categories {
97			let categories = categories.iter().copied().map(urlencoding::encode).collect::<Vec<_>>();
98			self.get(query, Some(&categories.iter().map(AsRef::as_ref).collect::<Vec<_>>())).await
99		} else {
100			self.get(query, None).await
101		}
102	}
103
104	#[cfg(not(feature = "require-parse-names"))]
105	#[instrument(err, level = "info", skip(self))]
106	pub async fn movie_search(&self, query: &str) -> Result<Vec<Torrent>, Error> {
107		self.get(query, Some(&MOVIE_CATEGORIES)).await
108	}
109
110	#[cfg(feature = "require-parse-names")]
111	#[instrument(err, level = "info", skip(self))]
112	pub async fn movie_search(&self, query: &str) -> Result<Vec<Result<Torrent, ParseError>>, Error> {
113		self.get(query, Some(&MOVIE_CATEGORIES)).await
114	}
115
116	#[cfg(not(feature = "require-parse-names"))]
117	#[instrument(err, level = "info", skip(self))]
118	pub async fn tv_search(&self, query: &str) -> Result<Vec<Torrent>, Error> {
119		self.get(query, Some(&TV_CATEGORIES)).await
120	}
121
122	#[cfg(feature = "require-parse-names")]
123	#[instrument(err, level = "info", skip(self))]
124	pub async fn tv_search(&self, query: &str) -> Result<Vec<Result<Torrent, ParseError>>, Error> {
125		self.get(query, Some(&TV_CATEGORIES)).await
126	}
127
128	#[cfg(not(feature = "require-parse-names"))]
129	#[instrument(err, level = "info", skip(self))]
130	pub async fn audio_search(&self, query: &str) -> Result<Vec<Torrent>, Error> {
131		self.get(query, Some(&AUDIO_CATEGORIES)).await
132	}
133
134	#[cfg(feature = "require-parse-names")]
135	#[instrument(err, level = "info", skip(self))]
136	pub async fn audio_search(&self, query: &str) -> Result<Vec<Result<Torrent, ParseError>>, Error> {
137		self.get(query, Some(&AUDIO_CATEGORIES)).await
138	}
139}
140
141#[cfg(test)]
142mod tests {
143	use super::*;
144
145	#[cfg(not(feature = "require-parse-names"))]
146	fn torrent(name: &str, size: u64, categories: Vec<u32>, link: &str, seeders: u16, leechers: u16, minimum_ratio: f32, minimum_seedtime: u64) -> Torrent {
147		Torrent::new(
148			name.into(),
149			size,
150			categories,
151			link.into(),
152			Some(seeders),
153			Some(leechers),
154			Some(minimum_ratio),
155			Some(std::time::Duration::from_secs(minimum_seedtime))
156		)
157	}
158
159	#[cfg(feature = "require-parse-names")]
160	fn torrent(name: &str, size: u64, categories: Vec<u32>, link: &str, seeders: u16, leechers: u16, minimum_ratio: f32, minimum_seedtime: u64) -> Torrent {
161		Torrent::new(
162			name.into(),
163			size,
164			categories,
165			link.into(),
166			Some(seeders),
167			Some(leechers),
168			Some(minimum_ratio),
169			Some(std::time::Duration::from_secs(minimum_seedtime))
170		).unwrap()
171	}
172
173	#[test]
174	fn simple_worst_cooks_results() {
175		let s = /* {{{ */ r#"
176			{
177				"Results": [
178					{
179						"FirstSeen": "0001-01-01T00:00:00",
180						"Tracker": "IPTorrents",
181						"TrackerId": "iptorrents",
182						"CategoryDesc": "TV/WEB-DL",
183						"BlackholeLink": null,
184						"Title": "Worst Cooks in America S22E00 Halloween Redemption 2 720p WEBRip x264-KOMPOST",
185						"Guid": "https://iptorrents.com/t/4492053",
186						"Link": "http://jackett.cronce.io:/dl/iptorrents/?jackett_apikey=ncc9usyrou9scugt8fddjsa8k2701ubs&path=Q2ZESjhBRDZKaGpva2RwQ3ZaZzZha19UUTdRVlFraVdVUzM4ZUVWTWFlNHhjZEFBVncwTHhFcFlJYVRrRTVucXdOM2czclNMdjdudUd5WHQyUjBNVmNLejFaeENIWXgtMFI1RVVSckpGa0M2RTJwZDFrRVduaURhYmtpS2tfZEl2Qlp3WWV6WndjVWVnRWlOOHdZS1M0c3lIaEt5X01tbUEwa1cyNVRZcEM5WlAzbjg2dWJ2cy1ULW9UN215a1NrY01WZm0wUTZJX1JtYTBvdG9IMUV3VnN3anZmV3dRQmZ6TlF0QTNlTXUwN2V2WEwwQ1psOEJzM1RMdGlKS1ZQd1Z1TFhjYU4wZUctSE1DX2VJRF8tMTNYdVdLUUxxZ0EyUEJXTmxBRGZiU2REcnhDeg&file=Worst+Cooks+in+America+S22E00+Halloween+Redemption+2+720p+WEBRip+x264-KOMPOST",
187						"Details": "https://iptorrents.com/t/4492053",
188						"PublishDate": "2021-09-27T05:57:24.8597709+00:00",
189						"Category": [
190						5010,
191						100022
192						],
193						"Size": 930086912,
194						"Files": null,
195						"Grabs": 51,
196						"Description": "Tags: 6.4 2010 Comedy Game-Show Reality-TV 720p Uploaded by: TvTeam",
197						"RageID": null,
198						"TVDBId": null,
199						"Imdb": null,
200						"TMDb": null,
201						"Author": null,
202						"BookTitle": null,
203						"Seeders": 5,
204						"Peers": 0,
205						"Poster": null,
206						"InfoHash": null,
207						"MagnetUri": null,
208						"MinimumRatio": 1,
209						"MinimumSeedTime": 1209600,
210						"DownloadVolumeFactor": 1,
211						"UploadVolumeFactor": 1,
212						"Gain": 4.3310546875
213					},
214					{
215						"FirstSeen": "0001-01-01T00:00:00",
216						"Tracker": "IPTorrents",
217						"TrackerId": "iptorrents",
218						"CategoryDesc": "TV/SD",
219						"BlackholeLink": null,
220						"Title": "Worst Cooks in America S22E00 Halloween Redemption 2 XviD-AFG",
221						"Guid": "https://iptorrents.com/t/4492061",
222						"Link": "http://jackett.cronce.io:/dl/iptorrents/?jackett_apikey=ncc9usyrou9scugt8fddjsa8k2701ubs&path=Q2ZESjhBRDZKaGpva2RwQ3ZaZzZha19UUTdSMDBlSHg5YlpMZUw0NlNHUGx6cVd2cnpoekx3V2NwVFZRanlZXzdxUV90YTlZc1Zxdy1SR3dVeVZUd1NMTXpYeWVpZ09vcm52UXNzc2dSaEtCX1U5R3czZFNTTmlzVUh3VHkwelR3ZW13ckFxVEFLckdKWEpaY3V0Wm9KV19INlBzV05aNEUwREVyM3hfYnhMejhRTU1wNXdJYnhlUXNMNlNNU2tZekRwQkxCSHlvbUhWcm1hVGVWOVFyTUpqWVFScm42eVd3Zk0tOTVKV1pyOXg5SVJVVnZXWTNJb3BvSFZZVlNsT3V4VzVTM2d4QzhkMXQwUmpoVGpYOUdKV0RSNA&file=Worst+Cooks+in+America+S22E00+Halloween+Redemption+2+XviD-AFG",
223						"Details": "https://iptorrents.com/t/4492061",
224						"PublishDate": "2021-09-27T05:57:24.8596901+00:00",
225						"Category": [
226						5030,
227						100004
228						],
229						"Size": 707788800,
230						"Files": null,
231						"Grabs": 20,
232						"Description": "Tags: 6.4 2010 Comedy Game-Show Reality-TV Uploaded by: TvTeam",
233						"RageID": null,
234						"TVDBId": null,
235						"Imdb": null,
236						"TMDb": null,
237						"Author": null,
238						"BookTitle": null,
239						"Seeders": 2,
240						"Peers": 0,
241						"Poster": null,
242						"InfoHash": null,
243						"MagnetUri": null,
244						"MinimumRatio": 1,
245						"MinimumSeedTime": 1209600,
246						"DownloadVolumeFactor": 1,
247						"UploadVolumeFactor": 1,
248						"Gain": 1.318359375
249					},
250					{
251						"FirstSeen": "0001-01-01T00:00:00",
252						"Tracker": "IPTorrents",
253						"TrackerId": "iptorrents",
254						"CategoryDesc": "TV/SD",
255						"BlackholeLink": null,
256						"Title": "Worst Cooks in America S22E00 Halloween Redemption 2 480p x264-mSD",
257						"Guid": "https://iptorrents.com/t/4492066",
258						"Link": "http://jackett.cronce.io:/dl/iptorrents/?jackett_apikey=ncc9usyrou9scugt8fddjsa8k2701ubs&path=Q2ZESjhBRDZKaGpva2RwQ3ZaZzZha19UUTdTM0p5ZHhYWXAtcUl3YWhZT3BUOGZnX2tzWFZJSXVaNTAxbUw1emg2TWUtTFhFRl9rSlhEc1ZyQ0RjRlk5Q3d3OU9WSHNxanRKR2dLNXZzUVFaSFVNb0FMcERtYmYyT2ZzUW5oMTcwRTFuTXJ2RWl2ampPeVRJRXdtNU1FLWJ1VUZJMUF2aW1xbUNVY1BkUnlMYkhwUDFPQ2V6djRLVkQ5MU1BUkExZjNBbHlWM2xLOEpycTl5b0pvMTY2dUcwYk5YQ1B1Z08zNUR4d2VaZmQ5T0Q0akdDbGcwNXhCXzQ5U0tRSnhpQi1lU1Z3YTZ1UzdLS0ZucVVleFYtQzN2d3dFZw&file=Worst+Cooks+in+America+S22E00+Halloween+Redemption+2+480p+x264-mSD",
259						"Details": "https://iptorrents.com/t/4492066",
260						"PublishDate": "2021-09-27T06:09:39.9005952+00:00",
261						"Category": [
262							5030,
263							100078
264						],
265						"Size": 248512512,
266						"Files": null,
267						"Grabs": 25,
268						"Description": "Tags: 6.4 2010 Comedy Game-Show Reality-TV 480p Uploaded by: TvTeam",
269						"RageID": null,
270						"TVDBId": null,
271						"Imdb": null,
272						"TMDb": null,
273						"Author": null,
274						"BookTitle": null,
275						"Seeders": 1,
276						"Peers": 0,
277						"Poster": null,
278						"InfoHash": null,
279						"MagnetUri": null,
280						"MinimumRatio": 1,
281						"MinimumSeedTime": 1209600,
282						"DownloadVolumeFactor": 1,
283						"UploadVolumeFactor": 1,
284						"Gain": 0.2314453125
285					}
286				],
287				"Indexers": [
288					{
289						"ID": "iptorrents",
290						"Name": "IPTorrents",
291						"Status": 2,
292						"Results": 255,
293						"Error": null
294					}
295				]
296			}
297		"#; // }}}
298		let result: QueryResult = serde_json::from_str(s).unwrap();
299		#[cfg(not(feature = "require-parse-names"))]
300		let torrents = result.results.into_iter().map(Torrent::from).collect::<Vec<_>>();
301		#[cfg(feature = "require-parse-names")]
302		let torrents = result.results.into_iter().map(Torrent::try_from).collect::<Result<Vec<_>, _>>().unwrap();
303		assert_eq!(torrents, vec![
304			torrent(
305				"Worst Cooks in America S22E00 Halloween Redemption 2 720p WEBRip x264-KOMPOST",
306				930086912,
307				vec![5010, 100022],
308				"http://jackett.cronce.io:/dl/iptorrents/?jackett_apikey=ncc9usyrou9scugt8fddjsa8k2701ubs&path=Q2ZESjhBRDZKaGpva2RwQ3ZaZzZha19UUTdRVlFraVdVUzM4ZUVWTWFlNHhjZEFBVncwTHhFcFlJYVRrRTVucXdOM2czclNMdjdudUd5WHQyUjBNVmNLejFaeENIWXgtMFI1RVVSckpGa0M2RTJwZDFrRVduaURhYmtpS2tfZEl2Qlp3WWV6WndjVWVnRWlOOHdZS1M0c3lIaEt5X01tbUEwa1cyNVRZcEM5WlAzbjg2dWJ2cy1ULW9UN215a1NrY01WZm0wUTZJX1JtYTBvdG9IMUV3VnN3anZmV3dRQmZ6TlF0QTNlTXUwN2V2WEwwQ1psOEJzM1RMdGlKS1ZQd1Z1TFhjYU4wZUctSE1DX2VJRF8tMTNYdVdLUUxxZ0EyUEJXTmxBRGZiU2REcnhDeg&file=Worst+Cooks+in+America+S22E00+Halloween+Redemption+2+720p+WEBRip+x264-KOMPOST",
309				5,
310				0,
311				1.0,
312				1209600
313			),
314			torrent(
315				"Worst Cooks in America S22E00 Halloween Redemption 2 XviD-AFG",
316				707788800,
317				vec![5030, 100004],
318				"http://jackett.cronce.io:/dl/iptorrents/?jackett_apikey=ncc9usyrou9scugt8fddjsa8k2701ubs&path=Q2ZESjhBRDZKaGpva2RwQ3ZaZzZha19UUTdSMDBlSHg5YlpMZUw0NlNHUGx6cVd2cnpoekx3V2NwVFZRanlZXzdxUV90YTlZc1Zxdy1SR3dVeVZUd1NMTXpYeWVpZ09vcm52UXNzc2dSaEtCX1U5R3czZFNTTmlzVUh3VHkwelR3ZW13ckFxVEFLckdKWEpaY3V0Wm9KV19INlBzV05aNEUwREVyM3hfYnhMejhRTU1wNXdJYnhlUXNMNlNNU2tZekRwQkxCSHlvbUhWcm1hVGVWOVFyTUpqWVFScm42eVd3Zk0tOTVKV1pyOXg5SVJVVnZXWTNJb3BvSFZZVlNsT3V4VzVTM2d4QzhkMXQwUmpoVGpYOUdKV0RSNA&file=Worst+Cooks+in+America+S22E00+Halloween+Redemption+2+XviD-AFG",
319				2,
320				0,
321				1.0,
322				1209600
323			),
324			torrent(
325				"Worst Cooks in America S22E00 Halloween Redemption 2 480p x264-mSD",
326				248512512,
327				vec![5030, 100078],
328				"http://jackett.cronce.io:/dl/iptorrents/?jackett_apikey=ncc9usyrou9scugt8fddjsa8k2701ubs&path=Q2ZESjhBRDZKaGpva2RwQ3ZaZzZha19UUTdTM0p5ZHhYWXAtcUl3YWhZT3BUOGZnX2tzWFZJSXVaNTAxbUw1emg2TWUtTFhFRl9rSlhEc1ZyQ0RjRlk5Q3d3OU9WSHNxanRKR2dLNXZzUVFaSFVNb0FMcERtYmYyT2ZzUW5oMTcwRTFuTXJ2RWl2ampPeVRJRXdtNU1FLWJ1VUZJMUF2aW1xbUNVY1BkUnlMYkhwUDFPQ2V6djRLVkQ5MU1BUkExZjNBbHlWM2xLOEpycTl5b0pvMTY2dUcwYk5YQ1B1Z08zNUR4d2VaZmQ5T0Q0akdDbGcwNXhCXzQ5U0tRSnhpQi1lU1Z3YTZ1UzdLS0ZucVVleFYtQzN2d3dFZw&file=Worst+Cooks+in+America+S22E00+Halloween+Redemption+2+480p+x264-mSD",
329				1,
330				0,
331				1.0,
332				1209600
333			)
334		]);
335	}
336}
337