fix(self-host): unfurl media hosted by the instance itself (#2451)

This commit is contained in:
Hampus
2026-09-04 16:56:35 +02:00
committed by GitHub
parent 4c0f02d8a5
commit c01d22dc05
9 changed files with 390 additions and 20 deletions
+20 -3
View File
@@ -112,10 +112,13 @@ impl MediaProxyClient {
}
}
pub fn external_proxy_url(&self, input_url: &str) -> Option<String> {
if input_url == self.public_endpoint
pub fn is_own_url(&self, input_url: &str) -> bool {
input_url == self.public_endpoint
|| input_url.starts_with(&format!("{}/", self.public_endpoint))
{
}
pub fn external_proxy_url(&self, input_url: &str) -> Option<String> {
if self.is_own_url(input_url) {
return Some(input_url.to_owned());
}
let parsed = Url::parse(input_url).ok()?;
@@ -160,6 +163,20 @@ mod tests {
);
}
#[test]
fn own_urls_are_recognised_by_their_public_endpoint() {
let mp = MediaProxyClient::new_with_public_endpoint(
"http://media-proxy:8080",
"secret",
Some("https://chat.example.test/media/"),
reqwest::Client::new(),
);
assert!(mp.is_own_url("https://chat.example.test/media"));
assert!(mp.is_own_url("https://chat.example.test/media/attachments/1/2/cat.gif"));
assert!(!mp.is_own_url("https://chat.example.test/mediafiles/1.gif"));
assert!(!mp.is_own_url("https://static.klipy.com/ii/c8/28/HkAKKCzZ.webp"));
}
#[test]
fn external_proxy_url_uses_public_endpoint_and_plain_path() {
let client = reqwest::Client::new();
+24 -5
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use crate::direct_media::{MediaKind, detect_media_kind};
use crate::media_proxy::{MediaProxyClient, embed_media_flags};
use crate::media_proxy::{MediaMetadata, MediaProxyClient, embed_media_flags};
use crate::types::{EmbedMedia, MessageEmbed, NsfwMode};
use url::Url;
@@ -71,9 +71,28 @@ pub async fn build_direct_media_embed(
nsfw_mode: NsfwMode,
kind: MediaKind,
) -> anyhow::Result<MessageEmbed> {
let url_str = url.to_string();
let nsfw_str = MediaProxyClient::nsfw_mode_str(nsfw_mode);
let meta = media_proxy.get_metadata(&url_str, nsfw_str).await?;
let meta = media_proxy.get_metadata(url.as_str(), nsfw_str).await?;
Ok(media_embed(url, &meta, kind))
}
pub async fn build_own_media_embed(
media_proxy: &MediaProxyClient,
url: &Url,
nsfw_mode: NsfwMode,
) -> anyhow::Result<Option<MessageEmbed>> {
let nsfw_str = MediaProxyClient::nsfw_mode_str(nsfw_mode);
let meta = media_proxy.get_metadata(url.as_str(), nsfw_str).await?;
let Some(kind) =
media_kind_from_content_type(&meta.content_type).or_else(|| detect_media_kind(url))
else {
return Ok(None);
};
Ok(Some(media_embed(url, &meta, kind)))
}
fn media_embed(url: &Url, meta: &MediaMetadata, kind: MediaKind) -> MessageEmbed {
let url_str = url.to_string();
let media = EmbedMedia {
url: Some(url_str.clone()),
content_type: Some(meta.content_type.clone()),
@@ -82,7 +101,7 @@ pub async fn build_direct_media_embed(
height: meta.height,
placeholder: meta.placeholder.clone(),
duration: meta.duration.map(|duration| duration as u32),
flags: embed_media_flags(&meta),
flags: embed_media_flags(meta),
..Default::default()
};
@@ -104,7 +123,7 @@ pub async fn build_direct_media_embed(
}
};
embed.url = Some(url_str);
Ok(embed)
embed
}
#[cfg(test)]
+23
View File
@@ -121,6 +121,10 @@ impl UnfurlShard {
) -> anyhow::Result<UnfurlResult> {
let parsed = Url::parse(url_str)?;
if self.media_proxy.is_own_url(parsed.as_str()) {
return Ok(self.resolve_own_media(&parsed, nsfw_mode).await);
}
let (fetch_url, matched_resolver_idx) = self.find_transform(&parsed);
let ctx = ResolveContext {
@@ -177,6 +181,25 @@ impl UnfurlShard {
})
}
async fn resolve_own_media(&self, url: &Url, nsfw_mode: NsfwMode) -> UnfurlResult {
match resolvers::media::build_own_media_embed(&self.media_proxy, url, nsfw_mode).await {
Ok(Some(embed)) => self.finalize_result(ResolverResult {
embeds: vec![embed],
}),
Ok(None) => UnfurlResult {
embeds: Vec::new(),
cache_ttl_seconds: None,
},
Err(err) => {
tracing::warn!(error = %err, url = %url, "own media unfurl failed");
UnfurlResult {
embeds: Vec::new(),
cache_ttl_seconds: None,
}
}
}
}
fn find_transform(&self, url: &Url) -> (Url, Option<usize>) {
for (i, resolver) in self.resolvers.iter().enumerate() {
if let Some(transformed) = resolver.transform_url(url) {