const ID = '[\\w-]{11}' const URL_PATTERNS = [ // watch?v=ID, also inside & escaped html and attribution links new RegExp(`youtube(?:-nocookie)?\\.com/[^\\s"'<>]*?[?&;]v=(${ID})(?![\\w-])`, 'gi'), // /embed/ID /shorts/ID /live/ID /v/ID /e/ID and youtu.be/ID new RegExp(`(?:youtube(?:-nocookie)?\\.com/(?:embed|shorts|live|v|e)/|youtu\\.be/)(${ID})(?![\\w-])`, 'gi') ] // ids straight out of youtube urls (any form) export function extractUrlIds(text: string): string[] { const decoded = text.replace(/%3D/gi, '=').replace(/%3F/gi, '?').replace(/%26/gi, '&').replace(/%2F/gi, '/') const ids = new Set() for (const pattern of URL_PATTERNS) { for (const match of decoded.matchAll(pattern)) ids.add(match[1]!) } return [...ids] } // loose 11-char tokens that look like an id but were pasted without a url. // prose words are 11 chars too ("information"), so real ids must not look like a plain word, // and the caller verifies each candidate against youtube before trusting it. export function extractBareIds(text: string, exclude: Set): string[] { const withoutUrls = text.replace(/https?:\/\/[^\s"'<>]+/gi, ' ') const ids = new Set() for (const match of withoutUrls.matchAll(/(?