47 lines
1.2 KiB
TypeScript
47 lines
1.2 KiB
TypeScript
const YOUTUBE_VIDEO_ID = /^[A-Za-z0-9_-]{11}$/;
|
|
const YOUTUBE_HOSTS = new Set([
|
|
"youtube.com",
|
|
"www.youtube.com",
|
|
"m.youtube.com",
|
|
"music.youtube.com",
|
|
]);
|
|
|
|
export interface YouTubeVideo {
|
|
videoId: string;
|
|
watchUrl: string;
|
|
embedUrl: string;
|
|
}
|
|
|
|
export function parseYouTubeUrl(value: string): YouTubeVideo | null {
|
|
let url: URL;
|
|
try {
|
|
url = new URL(value);
|
|
} catch {
|
|
return null;
|
|
}
|
|
|
|
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
|
|
const hostname = url.hostname.toLowerCase();
|
|
let videoId: string | null = null;
|
|
|
|
if (hostname === "youtu.be") {
|
|
videoId = url.pathname.split("/").filter(Boolean)[0] ?? null;
|
|
} else if (YOUTUBE_HOSTS.has(hostname)) {
|
|
if (url.pathname === "/watch") {
|
|
videoId = url.searchParams.get("v");
|
|
} else {
|
|
const [kind, id] = url.pathname.split("/").filter(Boolean);
|
|
if (["embed", "live", "shorts"].includes(kind)) videoId = id ?? null;
|
|
}
|
|
}
|
|
|
|
if (!videoId || !YOUTUBE_VIDEO_ID.test(videoId)) return null;
|
|
|
|
return {
|
|
videoId,
|
|
watchUrl: `https://www.youtube.com/watch?v=${videoId}`,
|
|
embedUrl: `https://www.youtube-nocookie.com/embed/${videoId}`,
|
|
};
|
|
}
|