My personal site
cherry.computer
htmx
tailwind
axum
askama
1use std::{
2 sync::{Arc, LazyLock},
3 time::Duration,
4};
5
6use anyhow::Context;
7use reqwest::Url;
8use scraper::{Html, Selector};
9use tokio::sync::RwLock;
10use tracing::instrument;
11
12use super::{
13 Media,
14 cached::{MediaCache, cache_or_fetch, try_cache_or_fetch},
15};
16
17pub async fn fetch() -> anyhow::Result<Media> {
18 static FIRST_ENTRY_SEL: LazyLock<Selector> =
19 LazyLock::new(|| Selector::parse(".journal_entry:first-child").unwrap());
20 static NAME_SEL: LazyLock<Selector> =
21 LazyLock::new(|| Selector::parse(".game-name a").unwrap());
22 static IMAGE_SEL: LazyLock<Selector> = LazyLock::new(|| Selector::parse(".card-img").unwrap());
23 static PLATFORM_SEL: LazyLock<Selector> =
24 LazyLock::new(|| Selector::parse(".journal-platform").unwrap());
25 static URL_SEL: LazyLock<Selector> =
26 LazyLock::new(|| Selector::parse("a:has(.fa-arrow-right)").unwrap());
27
28 let page_url = Url::parse("https://backloggd.com/u/cherryfunk/journal")
29 .context("wrote invalid Backloggd URL")?;
30 let html = reqwest::get(page_url.clone())
31 .await
32 .context("failed to fetch Backloggd page")?
33 .text()
34 .await
35 .context("failed to get HTML text")?;
36 let document = Html::parse_document(&html);
37
38 let first_entry = document
39 .select(&FIRST_ENTRY_SEL)
40 .next()
41 .context("couldn't find any journal entries")?;
42 let name = first_entry
43 .select(&NAME_SEL)
44 .next()
45 .context("couldn't find name element")?
46 .text()
47 .next()
48 .context("name element didn't have any text")?
49 .to_owned();
50 let image = first_entry
51 .select(&IMAGE_SEL)
52 .next()
53 .context("couldn't find image element")?
54 .attr("src")
55 .context("image element didn't have src attribute")?
56 .to_owned();
57 let platform = first_entry
58 .select(&PLATFORM_SEL)
59 .next()
60 .context("couldn't find platform element")?
61 .text()
62 .next()
63 .context("platform element didn't have any text")?
64 .to_owned();
65 let url = first_entry
66 .select(&URL_SEL)
67 .next()
68 .context("couldn't find log URL element")?
69 .attr("href")
70 .context("log anchor didn't have a URL")?
71 .to_owned();
72 let url = page_url.join(&url).context("log URL was invalid")?;
73
74 Ok(Media {
75 name,
76 image,
77 context: platform,
78 url: url.into(),
79 })
80}
81
82static CACHE: LazyLock<MediaCache> = LazyLock::new(|| Arc::new(RwLock::new(None)));
83static TTL: Duration = Duration::from_secs(300);
84#[instrument(name = "backlogged_try_cached_fetch")]
85pub fn try_cached_fetch() -> Option<Media> {
86 try_cache_or_fetch(&CACHE, TTL, fetch)
87}
88#[instrument(name = "backlogged_cached_fetch")]
89pub async fn cached_fetch() -> Option<Media> {
90 cache_or_fetch(&CACHE, TTL, fetch).await
91}