#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
mod deser;
mod scraped;
mod types;
pub use scraped::{CATEGORIES, TRACKERS};
pub use types::*;
const API: &str = "https://apibay.org";
type Result<T> = std::result::Result<T, reqwest::Error>;
thread_local! {
static CLIENT: reqwest::Client = reqwest::Client::new();
}
pub async fn search(query: &str, category: Option<Category>) -> Result<Vec<PartialTorrent>> {
let cat = category.map(|cat| cat.0.to_string()).unwrap_or_default();
let torrents = CLIENT
.with(|client| {
client
.get(format!("{API}/q.php"))
.query(&[("q", query), ("cat", &cat)])
.send()
})
.await?
.json()
.await?;
Ok(torrents)
}
pub async fn top100(category: Category, last_48h: bool) -> Result<Vec<PartialTorrent>> {
let specifier = if last_48h { "_48h" } else { "" };
let torrents = CLIENT
.with(|client| {
client
.get(format!(
"{API}/precompiled/data_top100{spec}_{cat}.json",
API = API,
spec = specifier,
cat = category.0,
))
.send()
})
.await?
.json()
.await?;
Ok(torrents)
}
pub async fn torrent(id: u64) -> Result<Torrent> {
let torrent = CLIENT
.with(|client| {
client
.get(format!("{API}/t.php"))
.query(&[("id", id.to_string())])
.send()
})
.await?
.json()
.await?;
Ok(torrent)
}
pub async fn torrent_files(id: u64) -> Result<Vec<TorrentFile>> {
let files = CLIENT
.with(|client| {
client
.get(format!("{API}/f.php"))
.query(&[("id", id.to_string())])
.send()
})
.await?
.json()
.await?;
Ok(files)
}
impl PartialTorrent {
#[must_use]
pub fn magnet(&self) -> String {
format!("magnet:?xt=urn:btih:{}", self.info_hash)
.parse::<reqwest::Url>()
.expect("magnet link failed to parse - invalid info hash?")
.query_pairs_mut()
.append_pair("dn", &self.name)
.extend_pairs(TRACKERS.iter().map(|tracker| ("tr", tracker)))
.finish()
.to_string()
}
}
impl std::ops::Deref for Torrent {
type Target = PartialTorrent;
fn deref(&self) -> &Self::Target {
&self.partial
}
}
impl Category {
#[must_use]
pub fn new(id: u16) -> Option<Self> {
if CATEGORIES.iter().any(|(category_id, _)| category_id == &id) {
Some(Self(id))
} else {
None
}
}
pub fn all() -> impl Iterator<Item = Self> {
CATEGORIES.iter().map(|(id, _)| Self(*id))
}
#[must_use]
pub fn name(&self) -> &'static str {
fn lookup_id(id: u16) -> Option<&'static str> {
CATEGORIES
.iter()
.find(|(category_id, _)| category_id == &id)
.map(|(_, name)| *name)
}
lookup_id(self.0)
.or_else(|| lookup_id(self.0 / 100))
.unwrap_or("Unknown")
}
#[must_use]
pub const fn code(&self) -> u16 {
self.0
}
}