62 lines
1.7 KiB
TypeScript
62 lines
1.7 KiB
TypeScript
import type { RichContent } from "../../model/interfaces.ts";
|
|
import type { RichContentProvider } from "../rich-content-service.ts";
|
|
import { fetchWithTimeout } from "../rich-content-service.ts";
|
|
|
|
function extractVideoId(url: string): string | null {
|
|
try {
|
|
const u = new URL(url);
|
|
if (u.hostname === "youtu.be") {
|
|
return u.pathname.slice(1).split("/")[0] || null;
|
|
}
|
|
if (u.hostname === "youtube.com" || u.hostname === "www.youtube.com") {
|
|
if (u.pathname === "/watch" || u.pathname.startsWith("/watch?")) {
|
|
return u.searchParams.get("v");
|
|
}
|
|
if (
|
|
u.pathname.startsWith("/embed/") || u.pathname.startsWith("/shorts/")
|
|
) {
|
|
return u.pathname.split("/")[2] || null;
|
|
}
|
|
}
|
|
} catch {
|
|
// invalid URL
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export const youtubeProvider: RichContentProvider = {
|
|
name: "youtube",
|
|
|
|
matches(url: string): boolean {
|
|
return extractVideoId(url) !== null;
|
|
},
|
|
|
|
async fetch(url: string): Promise<RichContent> {
|
|
const videoId = extractVideoId(url)!;
|
|
const thumbnailUrl = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
|
|
let title: string | undefined;
|
|
|
|
try {
|
|
const oembedUrl =
|
|
`https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
|
|
const res = await fetchWithTimeout(oembedUrl);
|
|
if (res.ok) {
|
|
const data = await res.json() as { title?: string };
|
|
title = data.title;
|
|
}
|
|
} catch {
|
|
// oembed failed — thumbnail still works
|
|
}
|
|
|
|
return {
|
|
type: "youtube",
|
|
siteName: "YouTube",
|
|
url,
|
|
videoId,
|
|
title,
|
|
thumbnailUrl,
|
|
embedUrl: `https://www.youtube.com/embed/${videoId}?rel=0`,
|
|
};
|
|
},
|
|
};
|