feat: add TikTok embed support

This commit is contained in:
2026-03-24 00:14:53 +11:00
parent 4f1179d5cd
commit a8eb404ff3
10 changed files with 589 additions and 36 deletions

View File

@@ -23,8 +23,9 @@ import {
ThumbnailContent,
UnsupportedContent,
VideoContent,
VideoFrameThumbnail,
} from './message';
import { UrlPreviewCard, UrlPreviewHolder, YouTubeEmbed, isYouTubeUrl } from './url-preview';
import { UrlPreviewCard, UrlPreviewHolder, YouTubeEmbed, isYouTubeUrl, TikTokEmbed, isTikTokUrl } from './url-preview';
import { Image, MediaControl, Video } from './media';
import { ImageViewer } from './image-viewer';
import { PdfViewer } from './Pdf-viewer';
@@ -66,16 +67,21 @@ export function RenderMessageContent({
filteredUrls = filterDisabledUrls(filteredUrls, disabledEmbedPatterns ?? []);
if (filteredUrls.length === 0) return undefined;
// Separate YouTube URLs from other URLs
// Separate special URLs from generic ones
const youtubeUrls = filteredUrls.filter(isYouTubeUrl);
const otherUrls = filteredUrls.filter((url) => !isYouTubeUrl(url));
const tiktokUrls = filteredUrls.filter(isTikTokUrl);
const otherUrls = filteredUrls.filter((url) => !isYouTubeUrl(url) && !isTikTokUrl(url));
return (
<>
{/* Render YouTube embeds (ad-free via Piped) */}
{/* Render YouTube embeds (ad-free via yt-dlp) */}
{youtubeUrls.map((url) => (
<YouTubeEmbed key={url} url={url} />
))}
{/* Render TikTok embeds */}
{tiktokUrls.map((url) => (
<TikTokEmbed key={url} url={url} />
))}
{/* Render standard URL previews for other links */}
{otherUrls.length > 0 && (
<UrlPreviewHolder>
@@ -227,26 +233,47 @@ export function RenderMessageContent({
<MVideo
content={getContent()}
renderAsFile={renderFile}
renderVideoContent={({ body, info, ...props }) => (
<VideoContent
body={body}
info={info}
{...props}
renderThumbnail={
mediaAutoLoad
? () => (
<ThumbnailContent
info={info}
renderImage={(src) => (
<Image alt={body} title={body} src={src} loading="lazy" />
)}
/>
)
: undefined
}
renderVideo={(p) => <Video {...p} />}
/>
)}
renderVideoContent={({ body, info, mimeType, url, encInfo, ...props }) => {
// Check if there's valid thumbnail metadata
const hasValidThumbnail =
(typeof info.thumbnail_file?.url === 'string' || typeof info.thumbnail_url === 'string') &&
typeof info.thumbnail_info?.mimetype === 'string';
return (
<VideoContent
body={body}
info={info}
mimeType={mimeType}
url={url}
encInfo={encInfo}
{...props}
renderThumbnail={
mediaAutoLoad
? () => hasValidThumbnail ? (
// Try to load the provided thumbnail first
<ThumbnailContent
info={info}
renderImage={(src) => (
<Image alt={body} title={body} src={src} loading="lazy" />
)}
/>
) : (
// Fallback: Extract first frame from the video itself
<VideoFrameThumbnail
mimeType={mimeType}
url={url}
encInfo={encInfo}
renderImage={(src) => (
<Image alt={body} title={body} src={src} loading="lazy" />
)}
/>
)
: undefined
}
renderVideo={(p) => <Video {...p} />}
/>
);
}}
outlined={outlineAttachment}
/>
{renderCaption()}