Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions backend/migrations/0022_speedapp_support.sql
Original file line number Diff line number Diff line change
@@ -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"}}');
7 changes: 6 additions & 1 deletion backend/src/models/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions backend/src/services/user_stats/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
105 changes: 105 additions & 0 deletions backend/src/services/user_stats/speedapp.rs
Original file line number Diff line number Diff line change
@@ -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<bool>,
message: Option<String>,
// id: Option<i64>,
// username: Option<String>,
// email: Option<String>,
// created_at: Option<String>,
// class: Option<i64>,
// avatar: Option<String>,
uploaded: Option<i64>,
downloaded: Option<i64>,
// title: Option<String>,
is_donor: Option<bool>,
warned: Option<bool>,
// passkey: Option<String>,
// invites: Option<i64>,
// timezone: Option<String>,
// hit_and_run_count: Option<i64>,
snatch_count: Option<i32>,
// need_seed: Option<i64>,
average_seed_time: Option<i64>,
// locale: Option,
// free_leech_tokens: Option<i64>,
// double_upload_tokens: Option<i64>,
}

impl From<SpeedappResponse> 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<UserProfileScraped> {
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::<SpeedappResponse>(&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())
}
}