use async_trait::async_trait;
use reqwest::Client;
use scraper::{Html, Selector};
use crate::{
Magnet, MagneteaseError, MagneteaseErrorKind, MagneteaseResult, Provider, ProviderCategory,
WhichProvider,
};
pub struct Nyaa;
const URL: &str = "https://nyaa.si/";
#[async_trait]
impl Provider for Nyaa {
fn name(&self) -> &'static str {
"Nyaa"
}
fn display_url(&self) -> &'static str {
"nyaa.si"
}
fn category(&self) -> ProviderCategory {
ProviderCategory::Anime
}
async fn search(
&self,
client: &Client,
phrase: &str,
) -> Result<MagneteaseResult, MagneteaseError> {
let params = [("q", phrase)];
let html = client
.get(URL)
.query(¶ms)
.send()
.await
.map_err(|e| MagneteaseError {
kind: e.into(),
provider: WhichProvider::Nyaa,
})?
.text()
.await
.map_err(|e| MagneteaseError {
kind: e.into(),
provider: WhichProvider::Nyaa,
})?;
if html.contains("No results found") {
return Ok(MagneteaseResult {
provider: WhichProvider::Nyaa,
magnets: vec![],
});
}
let doc = Html::parse_document(&html);
let torrent_table_selector = Selector::parse("tbody").unwrap();
let torrent_table = doc
.select(&torrent_table_selector)
.next()
.ok_or(MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?;
let anchor_selector = Selector::parse("a").unwrap();
let torrent_selector = Selector::parse("tr").unwrap();
let mut magnets = vec![];
for torrent in torrent_table.select(&torrent_selector) {
let torrent_info: Vec<_> = torrent.child_elements().collect();
let name = torrent_info
.get(1)
.ok_or(MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?
.select(&anchor_selector)
.last()
.ok_or(MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?
.inner_html();
let url = torrent_info
.get(2)
.ok_or(MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?
.select(&anchor_selector)
.last()
.ok_or(MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?
.attr("href")
.ok_or(MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?
.to_string();
let bytes = size_to_bytes(
&torrent_info
.get(3)
.ok_or(MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?
.inner_html(),
);
let seeders: u32 = torrent_info
.get(5)
.ok_or(MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?
.inner_html()
.parse()
.map_err(|_| MagneteaseError {
kind: MagneteaseErrorKind::HtmlError,
provider: WhichProvider::Nyaa,
})?;
let magnet = NyaaMagnet {
url,
name,
seeders,
bytes,
};
magnets.push(magnet);
}
let magnets = magnets
.into_iter()
.map(|nyaa_magnet| nyaa_magnet.into())
.collect();
Ok(MagneteaseResult {
provider: WhichProvider::Nyaa,
magnets,
})
}
}
pub struct NyaaMagnet {
url: String,
name: String,
seeders: u32,
bytes: u64,
}
impl From<NyaaMagnet> for Magnet {
fn from(value: NyaaMagnet) -> Self {
Magnet {
title: value.name,
url: value.url,
seeders: value.seeders,
bytes: value.bytes,
}
}
}
fn size_to_bytes(size: &str) -> u64 {
let kb = 1000.0;
let mb = kb * 1000.0;
let gb = mb * 1000.0;
let (size, suffix) = size.split_once(' ').unwrap();
let size: f64 = size.parse().unwrap();
if suffix == "GiB" {
(size * gb) as u64
} else if suffix == "MiB" {
(size * mb) as u64
} else if suffix == "KiB" {
(size * kb) as u64
} else {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn nyaa_search() {
let client = Client::new();
let response = Nyaa.search(&client, "lain").await.unwrap();
assert_eq!(response.magnets.len(), 75);
assert!(!response.magnets[15].url.is_empty());
}
#[tokio::test]
async fn nyaa_search_no_results() {
let client = Client::new();
let response = Nyaa.search(&client, "re 398 30 hfob q389by").await.unwrap();
assert_eq!(response.magnets.len(), 0);
}
}