diff --git a/crates/x-media/src/site/bsky/interface.rs b/crates/x-media/src/site/bsky/interface.rs index 41ff2ea..5a62c33 100644 --- a/crates/x-media/src/site/bsky/interface.rs +++ b/crates/x-media/src/site/bsky/interface.rs @@ -299,6 +299,7 @@ impl From for Fetched { title: post.text.clone(), media: post.media, sensitive: post.sensitive, + site_id: "bsky", render_data, _keep_alive: None, } diff --git a/crates/x-media/src/site/mod.rs b/crates/x-media/src/site/mod.rs index cf67d8d..48544f1 100644 --- a/crates/x-media/src/site/mod.rs +++ b/crates/x-media/src/site/mod.rs @@ -30,6 +30,10 @@ pub struct Fetched { pub media: Vec, /// Spoiler flag for all media of this post. pub sensitive: bool, + /// Site id (`"twitter"` / `"bsky"` / `"pixiv"`): the single source of + /// truth for site identity — caption-format lookup, cache-key prefix and + /// the SetFormat whitelist all derive from it. Set by the producing site. + pub site_id: &'static str, /// Raw values (pre-escaped) for user-customizable caption formats. pub(crate) render_data: Option, /// Keeps temp files (e.g. an encoded ugoira MP4) alive until the caller @@ -50,16 +54,10 @@ pub(crate) struct RenderData { impl Fetched { /// The site this post came from (used for per-site format overrides). + /// A thin alias over [`Fetched::site_id`] kept for callers that read the + /// site off a fetched post. pub fn site_name(&self) -> &'static str { - if self.source_url.contains("x.com") || self.source_url.contains("twitter.com") { - "twitter" - } else if self.source_url.contains("bsky.app") { - "bsky" - } else if self.source_url.contains("pixiv.net") { - "pixiv" - } else { - "unknown" - } + self.site_id } /// Renders a user-supplied caption format. The format string is @@ -177,6 +175,25 @@ pub fn cache_key(url: &str) -> Option { None } +/// The site id carried by a cache key (`"twitter:123"` → `"twitter"`). +/// Unknown prefixes fall back to `"unknown"`. The bot uses this on the +/// link-cache hit path, where no [`Fetched`] is available — the same value +/// a fresh fetch would read from [`Fetched::site_id`]. +pub fn site_id_from_key(key: &str) -> &'static str { + match key.split(':').next() { + Some("twitter") => "twitter", + Some("pixiv") => "pixiv", + Some("bsky") => "bsky", + _ => "unknown", + } +} + +/// Every supported site id, in dispatch order. The bot's SetFormat whitelist +/// and per-site caption-format lookup derive from this list. +pub fn site_ids() -> Vec<&'static str> { + vec!["twitter", "bsky", "pixiv"] +} + #[derive(Debug)] pub enum FetchError { Http(reqwest::Error), @@ -476,6 +493,15 @@ mod tests { assert_eq!(cache_key("https://example.com/not-a-post"), None); } + #[test] + fn site_id_from_key_parses_prefix() { + assert_eq!(site_id_from_key("twitter:123"), "twitter"); + assert_eq!(site_id_from_key("pixiv:123"), "pixiv"); + assert_eq!(site_id_from_key("bsky:handle.example/3lorem"), "bsky"); + assert_eq!(site_id_from_key("unknown:1"), "unknown"); + assert_eq!(site_id_from_key("no-colon"), "unknown"); + } + #[test] fn fetch_error_retryability_classification() { // Transient: network errors, explicit transient, pixiv 429/5xx. diff --git a/crates/x-media/src/site/pixiv/interface.rs b/crates/x-media/src/site/pixiv/interface.rs index 6a68680..bc3eada 100644 --- a/crates/x-media/src/site/pixiv/interface.rs +++ b/crates/x-media/src/site/pixiv/interface.rs @@ -142,6 +142,7 @@ impl From for Fetched { title: illustration.title.clone(), media: illustration.media, sensitive: illustration.nsfw, + site_id: "pixiv", render_data, _keep_alive: illustration._keep_alive, } diff --git a/crates/x-media/src/site/twitter/interface.rs b/crates/x-media/src/site/twitter/interface.rs index 9e73298..9c193d3 100644 --- a/crates/x-media/src/site/twitter/interface.rs +++ b/crates/x-media/src/site/twitter/interface.rs @@ -60,6 +60,7 @@ fn empty_fetched(url: &str) -> Fetched { title: String::new(), media: vec![], sensitive: true, + site_id: "twitter", render_data: None, _keep_alive: None, } @@ -301,6 +302,7 @@ impl From for Fetched { title: tweet.text.clone(), media: tweet.media, sensitive: tweet.sensitive, + site_id: "twitter", render_data, _keep_alive: None, } diff --git a/crates/xmedia-bot/src/handlers.rs b/crates/xmedia-bot/src/handlers.rs index a0431b1..63df14f 100644 --- a/crates/xmedia-bot/src/handlers.rs +++ b/crates/xmedia-bot/src/handlers.rs @@ -402,7 +402,7 @@ async fn execute_command( return Ok(()); } }; - if !["twitter", "bsky", "pixiv"].contains(&site) { + if !x_media::site::site_ids().contains(&site) { reply( bot.clone(), message.clone(), @@ -636,7 +636,9 @@ async fn url_media(bot: Bot, message: &Message, url: &str) { { log::debug!("link cache hit for {key}"); let chat_data = CHAT_STORE.get(chat_id).await; - let site = key.split(':').next().unwrap_or("unknown"); + // Cache keys are prefixed with the site id ("twitter:…"), matching + // the value a fresh fetch would read from Fetched::site_id. + let site = x_media::site::site_id_from_key(&key); let format = chat_data .message_format .get(site)