use std::collections::HashMap; use anyhow::Context; use reqwest::get; use scraper::{Html, Selector}; use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Tracks { pub app_config: AppConfig, pub config: AppConfig, pub tags: AppConfig, pub gifs: Gifs, pub stickers: Memes, pub memes: Memes, pub universal: Universal, pub packs: Collections, pub collections: Collections, pub exploreterms: AppConfig, pub search_suggestions: AppConfig, pub profiles: AppConfig, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct AppConfig {} #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Collections { pub by_id: AppConfig, } #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Gifs { pub by_id: HashMap, pub search_by_username: AppConfig, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Item { pub results: Vec, pub promise: AppConfig, pub loaded: bool, pub pending: bool, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct PurpleResult { pub id: String, pub legacy_info: LegacyInfo, pub title: String, // media_formats: HashMap, pub media_formats: MediaFormats, pub bg_color: String, pub created: f64, pub content_description: String, pub h1_title: String, pub long_title: String, pub embed: String, pub itemurl: String, pub url: String, pub tags: Vec, pub flags: Vec>, pub user: PurpleUser, pub hasaudio: bool, pub source_id: String, pub shares: i64, pub policy_status: PolicyStatus, pub index: i64, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct LegacyInfo { pub post_id: String, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct MediaFormat { pub url: String, pub duration: f64, pub preview: String, pub dims: [i64; 2], pub size: i64, } #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum PolicyStatus { #[serde(rename = "POLICY_STATUS_UNSPECIFIED")] PolicyStatusUnspecified, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct PurpleUser { pub username: String, pub partnername: String, pub url: String, pub tagline: String, pub userid: String, pub profile_id: String, pub avatars: AppConfig, pub usertype: Usertype, pub partnerbanner: AppConfig, pub partnercategories: Vec>, pub partnerlinks: Vec>, pub flags: Vec>, } #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Usertype { User, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct MediaFormats { pub gif: MediaFormat, } #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Memes { search_by_username: AppConfig, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Universal { search: AppConfig, } pub async fn download_url( url: &str, ) -> anyhow::Result<( PurpleResult, impl FnOnce() -> impl Future>>, )> { println!("GET {url}"); let response = get(url).await.context("h")?; let html_content = response.text().await.context("ah")?; let document = Html::parse_document(&html_content); let selector = Selector::parse("#store-cache").unwrap(); let element = document .select(&selector) .next() .ok_or_else(|| anyhow::anyhow!("!#store-cache"))?; let json_str = element.inner_html(); let data = serde_json::from_str::(&json_str).context("alas")?; let id = url.rsplit('-').next().unwrap_or(""); let result = data .gifs .by_id .get(&id.parse().unwrap()) .ok_or_else(|| anyhow::anyhow!("failure to find {}", id))? .results .get(0) .ok_or_else(|| anyhow::anyhow!("not in data"))? .clone(); let gif_url = result.media_formats.gif.url.clone(); Ok((result, move || async move { Ok(get(gif_url) .await .context("no gif")? .bytes() .await .context("no gif")? .to_vec()) })) }