use std::{ sync::{Arc, LazyLock}, time::Duration, }; use anyhow::Context; use reqwest::Url; use scraper::{Html, Selector}; use tokio::sync::RwLock; use tracing::instrument; use super::{ Media, cached::{MediaCache, cache_or_fetch, try_cache_or_fetch}, }; pub async fn fetch() -> anyhow::Result { static FIRST_ENTRY_SEL: LazyLock = LazyLock::new(|| Selector::parse(".journal_entry:first-child").unwrap()); static NAME_SEL: LazyLock = LazyLock::new(|| Selector::parse(".game-name a").unwrap()); static IMAGE_SEL: LazyLock = LazyLock::new(|| Selector::parse(".card-img").unwrap()); static PLATFORM_SEL: LazyLock = LazyLock::new(|| Selector::parse(".journal-platform").unwrap()); static URL_SEL: LazyLock = LazyLock::new(|| Selector::parse("a:has(.fa-arrow-right)").unwrap()); let page_url = Url::parse("https://backloggd.com/u/cherryfunk/journal") .context("wrote invalid Backloggd URL")?; let html = reqwest::get(page_url.clone()) .await .context("failed to fetch Backloggd page")? .text() .await .context("failed to get HTML text")?; let document = Html::parse_document(&html); let first_entry = document .select(&FIRST_ENTRY_SEL) .next() .context("couldn't find any journal entries")?; let name = first_entry .select(&NAME_SEL) .next() .context("couldn't find name element")? .text() .next() .context("name element didn't have any text")? .to_owned(); let image = first_entry .select(&IMAGE_SEL) .next() .context("couldn't find image element")? .attr("src") .context("image element didn't have src attribute")? .to_owned(); let platform = first_entry .select(&PLATFORM_SEL) .next() .context("couldn't find platform element")? .text() .next() .context("platform element didn't have any text")? .to_owned(); let url = first_entry .select(&URL_SEL) .next() .context("couldn't find log URL element")? .attr("href") .context("log anchor didn't have a URL")? .to_owned(); let url = page_url.join(&url).context("log URL was invalid")?; Ok(Media { name, image, context: platform, url: url.into(), }) } static CACHE: LazyLock = LazyLock::new(|| Arc::new(RwLock::new(None))); static TTL: Duration = Duration::from_secs(300); #[instrument(name = "backlogged_try_cached_fetch")] pub fn try_cached_fetch() -> Option { try_cache_or_fetch(&CACHE, TTL, fetch) } #[instrument(name = "backlogged_cached_fetch")] pub async fn cached_fetch() -> Option { cache_or_fetch(&CACHE, TTL, fetch).await }