refactor(site): carry site_id on Fetched; unify cache-key site lookup

This commit is contained in:
2026-08-14 18:17:22 +08:00
parent 96c11becb9
commit 7ca8fd1da2
5 changed files with 43 additions and 11 deletions
@@ -299,6 +299,7 @@ impl From<Post> for Fetched {
title: post.text.clone(),
media: post.media,
sensitive: post.sensitive,
site_id: "bsky",
render_data,
_keep_alive: None,
}
+35 -9
View File
@@ -30,6 +30,10 @@ pub struct Fetched {
pub media: Vec<crate::media::Media>,
/// 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<RenderData>,
/// 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<String> {
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.
@@ -142,6 +142,7 @@ impl From<Illustration> for Fetched {
title: illustration.title.clone(),
media: illustration.media,
sensitive: illustration.nsfw,
site_id: "pixiv",
render_data,
_keep_alive: illustration._keep_alive,
}
@@ -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<Tweet> for Fetched {
title: tweet.text.clone(),
media: tweet.media,
sensitive: tweet.sensitive,
site_id: "twitter",
render_data,
_keep_alive: None,
}
+4 -2
View File
@@ -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)