diff --git a/src/lib/utils/__tests__/media-utils.test.ts b/src/lib/utils/__tests__/media-utils.test.ts index ff3fd549..6c1b93c3 100644 --- a/src/lib/utils/__tests__/media-utils.test.ts +++ b/src/lib/utils/__tests__/media-utils.test.ts @@ -128,6 +128,7 @@ describe('media-utils', () => { url: 'https://ibb.co/abc123', }); expect(getLinkMediaInfo('https://example.com/file.gif')).toMatchObject({ type: 'gif' }); + expect(getLinkMediaInfo('https://external-preview.redd.it/example.gif?width=480&format=mp4')).toMatchObject({ type: 'video' }); expect(getLinkMediaInfo('https://example.com/file.png')).toMatchObject({ type: 'image' }); expect(getLinkMediaInfo('https://example.com/file.mp4')).toMatchObject({ type: 'video' }); expect(getLinkMediaInfo('https://example.com/file.mp3')).toMatchObject({ type: 'audio' }); diff --git a/src/lib/utils/media-utils.ts b/src/lib/utils/media-utils.ts index 8e9e4e17..e49c6c79 100644 --- a/src/lib/utils/media-utils.ts +++ b/src/lib/utils/media-utils.ts @@ -79,6 +79,7 @@ const getPatternThumbnailUrl = (url: URL): string | undefined => { const KNOWN_IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'tiff']; const KNOWN_VIDEO_EXTENSIONS = ['mp4', 'webm', 'mov', 'avi', 'mkv', 'flv', 'wmv', 'm4v']; const KNOWN_AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma']; +const KNOWN_MEDIA_EXTENSIONS = new Set([...KNOWN_IMAGE_EXTENSIONS, ...KNOWN_VIDEO_EXTENSIONS, ...KNOWN_AUDIO_EXTENSIONS]); // some sites don't show thumbnails, so the backend-side thumbnail fetching needs to be disabled, or it might fetch non-thumbnails such as emojis const THUMBNAIL_BLACKLISTED_DOMAINS = ['twitter.com', 'x.com']; @@ -114,6 +115,16 @@ const getAllowedThumbnailUrl = (value: string, baseUrl: string): string | undefi } }; +const getDirectMediaExtension = (url: URL): string => { + const format = url.searchParams.get('format')?.toLowerCase() ?? ''; + if (KNOWN_MEDIA_EXTENSIONS.has(format)) { + return format; + } + + const pathParts = url.pathname.toLowerCase().split('.'); + return pathParts.length > 1 ? pathParts[pathParts.length - 1] : ''; +}; + export const getLinkMediaInfo = memoize( (link: string): CommentMediaInfo | undefined => { if (!isValidURL(link)) { @@ -134,9 +145,7 @@ export const getLinkMediaInfo = memoize( } try { - // Extract file extension - const pathParts = url.pathname.toLowerCase().split('.'); - const extension = pathParts.length > 1 ? pathParts[pathParts.length - 1] : ''; + const extension = getDirectMediaExtension(url); // Only classify as media if we explicitly know the extension if (KNOWN_IMAGE_EXTENSIONS.includes(extension)) { @@ -148,10 +157,6 @@ export const getLinkMediaInfo = memoize( } // Unknown extensions remain as 'webpage' - if (!url.pathname.includes('.')) { - type = 'webpage'; - } - if (canEmbed(url) || url.host.startsWith('yt.')) { type = 'iframe'; patternThumbnailUrl = getPatternThumbnailUrl(url);