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<u64, Item>,
pub search_by_username: AppConfig,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Item {
pub results: Vec<PurpleResult>,
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<String, MediaFormat>,
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<String>,
pub flags: Vec<Option<serde_json::Value>>,
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<Option<serde_json::Value>>,
pub partnerlinks: Vec<Option<serde_json::Value>>,
pub flags: Vec<Option<serde_json::Value>>,
}
#[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<Output = anyhow::Result<Vec<u8>>>,
)> {
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::<Tracks>(&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())
}))
}