diff --git a/README.md b/README.md index 13f43b4..7bac870 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ You can then visit the frontend at `http://localhost:3000` and the swagger at `h * RED * ReelFlix * SP +* SpeedApp * ULCX * YOiNKED * YUS diff --git a/backend/migrations/0022_speedapp_support.sql b/backend/migrations/0022_speedapp_support.sql new file mode 100644 index 0000000..12132a4 --- /dev/null +++ b/backend/migrations/0022_speedapp_support.sql @@ -0,0 +1,2 @@ +INSERT INTO indexers (name, auth_data) VALUES +('SpeedApp', '{"api_key": {"value": "", "explanation": "Generate one in My Profile > API Tokens, with all permissions"}}'); diff --git a/backend/src/models/indexer.rs b/backend/src/models/indexer.rs index ae1433f..da4883d 100644 --- a/backend/src/models/indexer.rs +++ b/backend/src/models/indexer.rs @@ -16,7 +16,8 @@ use crate::{ oldtoons::OldToonsScraper, only_encodes::OnlyEncodesScraper, orpheus::OrpheusScraper, phoenix_project::PhoenixProjectScraper, rastastugan::RastastuganScraper, redacted::RedactedScraper, reel_flix::ReelFlixScraper, seed_pool::SeedPoolScraper, - upload_cx::UploadCXScraper, yoinked::YoinkedScraper, yu_scene::YuSceneScraper, + speedapp::SpeedappScraper, upload_cx::UploadCXScraper, yoinked::YoinkedScraper, + yu_scene::YuSceneScraper, }, }; @@ -157,6 +158,10 @@ impl Indexer { static HOMIE_HELP_DESK_SCRAPER: HomieHelpDeskScraper = HomieHelpDeskScraper; &HOMIE_HELP_DESK_SCRAPER } + "SpeedApp" => { + static SPEED_APP_SCRAPER: SpeedappScraper = SpeedappScraper; + &SPEED_APP_SCRAPER + } _ => { return Err(Error::CouldNotScrapeIndexer( "indexer has no scraper".into(), diff --git a/backend/src/services/user_stats/mod.rs b/backend/src/services/user_stats/mod.rs index 5b3db98..01aaa07 100644 --- a/backend/src/services/user_stats/mod.rs +++ b/backend/src/services/user_stats/mod.rs @@ -20,6 +20,7 @@ pub mod redacted; pub mod reel_flix; pub mod scrape_indexers; pub mod seed_pool; +pub mod speedapp; pub mod upload_cx; pub mod yoinked; pub mod yu_scene; diff --git a/backend/src/services/user_stats/speedapp.rs b/backend/src/services/user_stats/speedapp.rs new file mode 100644 index 0000000..5d9819c --- /dev/null +++ b/backend/src/services/user_stats/speedapp.rs @@ -0,0 +1,105 @@ +use serde::Deserialize; + +use async_trait::async_trait; + +use crate::{ + error::{Error, Result}, + models::{ + indexer::{Indexer, Scraper}, + user_stats::UserProfileScraped, + }, +}; + +#[derive(Debug, Deserialize)] +struct SpeedappResponse { + error: Option, + message: Option, + // id: Option, + // username: Option, + // email: Option, + // created_at: Option, + // class: Option, + // avatar: Option, + uploaded: Option, + downloaded: Option, + // title: Option, + is_donor: Option, + warned: Option, + // passkey: Option, + // invites: Option, + // timezone: Option, + // hit_and_run_count: Option, + snatch_count: Option, + // need_seed: Option, + average_seed_time: Option, + // locale: Option, + // free_leech_tokens: Option, + // double_upload_tokens: Option, +} + +impl From for UserProfileScraped { + fn from(wrapper: SpeedappResponse) -> Self { + let uploaded = wrapper.uploaded.unwrap_or(0); + let downloaded = wrapper.downloaded.unwrap_or(0); + let ratio = if downloaded == 0 && uploaded > 0 { + f32::MAX + } else { + uploaded as f32 / downloaded as f32 + }; + + UserProfileScraped { + uploaded, + downloaded, + ratio, + // class: wrapper.class.unwrap_or(0).to_string(), + donor: wrapper.is_donor, + warned: wrapper.warned, + average_seed_time: wrapper.average_seed_time, + snatched: wrapper.snatch_count, + ..Default::default() + } + } +} + +pub struct SpeedappScraper; + +#[async_trait] +impl Scraper for SpeedappScraper { + async fn scrape( + &self, + indexer: Indexer, + client: &reqwest::Client, + ) -> Result { + let res = client + .get("https://speedapp.io/api/me") + .header( + "Authorization", + format!( + "Bearer {}", + indexer + .auth_data + .get("api_key") + .ok_or("Speedapp api key not found.") + .map_err(|e| Error::CouldNotScrapeIndexer(e.into()))? + .get("value") + .ok_or("Speedapp api key value not found") + .map_err(|e| Error::CouldNotScrapeIndexer(e.into()))? + .as_str() + .unwrap() + ), + ) + .send() + .await + .map_err(|e| Error::CouldNotScrapeIndexer(e.to_string()))?; + + let body = res.text().await.unwrap(); + let response = serde_json::from_str::(&body) + .map_err(|e| Error::CouldNotScrapeIndexer(e.to_string()))?; + + if response.error.unwrap_or(false) { + return Err(Error::CouldNotScrapeIndexer(response.message.unwrap())); + } + + Ok(response.into()) + } +}