diff --git a/changelog.d/url-preview-fix.feature b/changelog.d/url-preview-fix.feature new file mode 100644 index 00000000..04fc3cf8 --- /dev/null +++ b/changelog.d/url-preview-fix.feature @@ -0,0 +1 @@ +Improved URL preview fetching with a more compatible user agent for sites like YouTube Music. Added `!admin media delete-url-preview ` command to clear cached URL previews that were stuck and broken. diff --git a/docs/reference/admin/media.md b/docs/reference/admin/media.md index 11da0eb1..4fa70aea 100644 --- a/docs/reference/admin/media.md +++ b/docs/reference/admin/media.md @@ -36,3 +36,7 @@ Deletes all the local media from a local user on our server. This will always ig ## `!admin media delete-all-from-server` Deletes all remote media from the specified remote server. This will always ignore errors by default + +## `!admin media delete-url-preview` + +Deletes a cached URL preview, forcing it to be re-fetched. Use --all to purge all cached URL previews diff --git a/src/admin/media/commands.rs b/src/admin/media/commands.rs index 04269555..e7703b8d 100644 --- a/src/admin/media/commands.rs +++ b/src/admin/media/commands.rs @@ -388,3 +388,19 @@ pub(super) async fn get_remote_thumbnail( self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```")) .await } + +#[admin_command] +pub(super) async fn delete_url_preview(&self, url: Option, all: bool) -> Result { + if all { + self.services.media.clear_url_previews().await; + + return self.write_str("Deleted all cached URL previews.").await; + } + + let url = url.expect("clap enforces url is required unless --all"); + + self.services.media.remove_url_preview(&url).await?; + + self.write_str(&format!("Deleted cached URL preview for: {url}")) + .await +} diff --git a/src/admin/media/mod.rs b/src/admin/media/mod.rs index 47a0e539..a8ce0cd3 100644 --- a/src/admin/media/mod.rs +++ b/src/admin/media/mod.rs @@ -108,4 +108,16 @@ pub enum MediaCommand { #[arg(long, default_value("800"))] height: u32, }, + + /// Deletes a cached URL preview, forcing it to be re-fetched. + /// Use --all to purge all cached URL previews. + DeleteUrlPreview { + /// The URL to clear from the saved preview data + #[arg(required_unless_present = "all")] + url: Option, + + /// Purge all cached URL previews + #[arg(long, conflicts_with = "url")] + all: bool, + }, } diff --git a/src/admin/query/resolver.rs b/src/admin/query/resolver.rs index f263a297..5880d449 100644 --- a/src/admin/query/resolver.rs +++ b/src/admin/query/resolver.rs @@ -20,7 +20,17 @@ pub enum ResolverCommand { name: Option, }, - /// Flush a specific server from the resolver caches or everything + /// Flush a given server from the resolver caches or flush them completely + /// + /// * Examples: + /// * Flush a specific server: + /// + /// `!admin query resolver flush-cache matrix.example.com` + /// + /// * Flush all resolver caches completely: + /// + /// `!admin query resolver flush-cache --all` + #[command(verbatim_doc_comment)] FlushCache { name: Option, diff --git a/src/core/info/version.rs b/src/core/info/version.rs index acdd65f4..868323e9 100644 --- a/src/core/info/version.rs +++ b/src/core/info/version.rs @@ -14,6 +14,7 @@ static SEMANTIC: &str = env!("CARGO_PKG_VERSION"); static VERSION: OnceLock = OnceLock::new(); static VERSION_UA: OnceLock = OnceLock::new(); static USER_AGENT: OnceLock = OnceLock::new(); +static USER_AGENT_MEDIA: OnceLock = OnceLock::new(); #[inline] #[must_use] @@ -21,14 +22,22 @@ pub fn name() -> &'static str { BRANDING } #[inline] pub fn version() -> &'static str { VERSION.get_or_init(init_version) } + #[inline] pub fn version_ua() -> &'static str { VERSION_UA.get_or_init(init_version_ua) } #[inline] pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) } +#[inline] +pub fn user_agent_media() -> &'static str { USER_AGENT_MEDIA.get_or_init(init_user_agent_media) } + fn init_user_agent() -> String { format!("{}/{} (bot; +{WEBSITE})", name(), version_ua()) } +fn init_user_agent_media() -> String { + format!("{}/{} (embedbot; facebookexternalhit/1.1; +{WEBSITE})", name(), version_ua()) +} + fn init_version_ua() -> String { conduwuit_build_metadata::version_tag() .map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC}+{extra}")) diff --git a/src/service/client/mod.rs b/src/service/client/mod.rs index bd5853c9..4792bd7a 100644 --- a/src/service/client/mod.rs +++ b/src/service/client/mod.rs @@ -39,7 +39,7 @@ impl crate::Service for Service { let url_preview_user_agent = config .url_preview_user_agent .clone() - .unwrap_or_else(|| conduwuit::version::user_agent().to_owned()); + .unwrap_or_else(|| conduwuit::version::user_agent_media().to_owned()); Ok(Arc::new(Self { default: base(config)? diff --git a/src/service/media/data.rs b/src/service/media/data.rs index a5d667ee..0703a476 100644 --- a/src/service/media/data.rs +++ b/src/service/media/data.rs @@ -170,6 +170,8 @@ impl Data { Ok(()) } + pub(super) async fn clear_url_previews(&self) { self.url_previews.clear().await; } + pub(super) fn set_url_preview( &self, url: &str, diff --git a/src/service/media/preview.rs b/src/service/media/preview.rs index 662b2804..514bc2d5 100644 --- a/src/service/media/preview.rs +++ b/src/service/media/preview.rs @@ -37,6 +37,9 @@ pub async fn remove_url_preview(&self, url: &str) -> Result<()> { self.db.remove_url_preview(url) } +#[implement(Service)] +pub async fn clear_url_previews(&self) { self.db.clear_url_previews().await; } + #[implement(Service)] pub async fn set_url_preview(&self, url: &str, data: &UrlPreviewData) -> Result<()> { let now = SystemTime::now()