import { existsSync, readFileSync } from 'node:fs'; import { sendEvent } from '../network/sse.util.js'; import { getCookieType } from '../media/video.util.js'; import { downloadCookies } from '../network/cookie.util.js'; import { isValidProxyUrl } from '../network/validation.util.js'; import { getVideoInfo } from '../../services/ytdlp.service.js'; import { getBestThumbnail, proxyThumbnailIfNeeded, } from '../../services/social.service.js'; import { VideoInfo, SpotifyMetadata, Format, SSEEvent, } from '../../types/index.js'; export async function getCookieArgs( videoURL: string, clientId: string | undefined, status = 'initializing' ): Promise { const cookieType = getCookieType(videoURL); const cookiesPath = cookieType ? await downloadCookies(cookieType) : null; if (clientId) { sendEvent(clientId, { status: (status === 'fetching_info' ? 'initializing' : status) as SSEEvent['status'], progress: 8, subStatus: 'Bypassing restricted clients...', details: 'AUTH: BYPASSING_PROTOCOL_RESTRICTIONS', }); } return cookiesPath ? ['--cookies', cookiesPath] : []; } export function initializeSession( clientId: string | undefined, status = 'initializing' ): void { if (!clientId) return; sendEvent(clientId, { status: (status === 'fetching_info' ? 'initializing' : status) as SSEEvent['status'], progress: 3, subStatus: 'Initializing Session...', details: 'SESSION: STARTING_SECURE_CONTEXT', }); } export function logExtractionSteps( clientId: string | undefined, serviceName: string, step = 1 ): void { if (!clientId) return; const steps = [ { progress: 12, subStatus: `Extracting ${serviceName} Metadata...`, details: 'ENGINE_YTDLP: INITIATING_CORE_EXTRACTION', }, { progress: 18, subStatus: 'Analyzing Server-Side Signatures...', details: 'NETWORK_HANDSHAKE: ESTABLISHING_SECURE_TUNNEL', }, { progress: 24, subStatus: `Verifying ${serviceName} Handshake...`, details: 'AUTH_GATEWAY: BYPASSING_PROTOCOL_RESTRICTIONS', }, ]; const currentStep = steps[step - 1] || steps[0]; sendEvent(clientId, { status: 'initializing', ...currentStep, }); } export function handleBrainHit( videoURL: string, targetURL: string, spotifyData: SpotifyMetadata, cookieArgs: string[], clientId: string | undefined ): void { if (!spotifyData.cover || spotifyData.cover === '/logo.webp') { (async () => { try { const info = await getVideoInfo(targetURL, cookieArgs); let finalThumbnail = getBestThumbnail(info); finalThumbnail = await proxyThumbnailIfNeeded(finalThumbnail, videoURL); if (clientId) { sendEvent(clientId, { status: 'initializing', text: JSON.stringify({ metadata_update: { cover: finalThumbnail, title: spotifyData.title, artist: spotifyData.artist, }, }), }); } // only save ISRC if (spotifyData.fromBrain) { const { saveToBrain } = await import('../../services/spotify.service.js'); saveToBrain(videoURL, { ...spotifyData, cover: finalThumbnail }); } } catch (err: unknown) { console.debug( '[ControllerUtil] Brain hit handle error:', (err as Error).message ); } })(); } } export async function resolveConvertTarget( videoURL: string, targetURL: string | undefined, cookieArgs: string[] ): Promise { if (targetURL && !isValidProxyUrl(targetURL)) { console.warn('[Security] Blocked invalid targetUrl in resolve'); return videoURL; } if ( targetURL && (targetURL.includes('youtube.com') || targetURL.includes('youtu.be')) ) return targetURL; if (videoURL.includes('spotify.com')) { let info: VideoInfo | null = await getVideoInfo(videoURL, cookieArgs).catch( () => null ); if (info?.isPartial) { // wait background resolution info = await getVideoInfo(videoURL, cookieArgs, false).catch(() => null); } if (info?.targetUrl || info?.targetUrl) { const resolved = (info.targetUrl || info.targetUrl) as string; return resolved; } } return videoURL; } export async function resolveTargetFormat( format: string, _streamURL: string, resolvedTargetURL: string, cookieArgs: string[], formatId: string | undefined, _clientId: string | undefined, videoURL: string | null = null ): Promise<{ info: VideoInfo | null; targetFormat?: VideoInfo['formats'][number]; streamURL: string; }> { const urlToUse = videoURL || resolvedTargetURL; // hit ram cache let info: VideoInfo | null = await getVideoInfo(urlToUse, cookieArgs).catch( () => null ); if (!info) { const { getInfo } = await import('../../services/extractors/index.js'); // parse cookies let rawCookie: string | null = null; if (cookieArgs?.includes('--cookies')) { const cookiePath = cookieArgs[cookieArgs.indexOf('--cookies') + 1]; if (cookiePath && existsSync(cookiePath)) { const content = readFileSync(cookiePath, 'utf8'); const lines = content.split('\n'); const pairs: string[] = []; for (const line of lines) { if (!line.trim() || line.startsWith('#')) continue; const parts = line.split('\t'); if (parts.length >= 7) pairs.push(`${parts[5].trim()}=${parts[6].trim()}`); } rawCookie = pairs.join('; '); } } info = await getInfo(urlToUse, { cookie: rawCookie || cookieArgs.join('; '), }).catch(() => null); } if (!info) return { info: null, streamURL: _streamURL }; const isAudioOnly = ['mp3', 'm4a', 'audio'].includes(format); let targetFormat = info.formats.find( (item: Format) => String(item.formatId) === String(formatId) ); if (!targetFormat) { if (isAudioOnly) { targetFormat = info.formats .filter( (item: Format) => (item.acodec !== 'none' || item.isAudio) && (item.vcodec === 'none' || !item.isVideo) ) .sort( (formatA: Format, formatB: Format) => (formatB.abr || 0) - (formatA.abr || 0) )[0]; if (!targetFormat) { targetFormat = info.formats .filter((item: Format) => item.acodec !== 'none' || item.isAudio) .sort( (formatA: Format, formatB: Format) => (formatB.abr || 0) - (formatA.abr || 0) )[0]; } } else { targetFormat = info.formats .filter( (item: Format) => item.vcodec !== 'none' && item.acodec !== 'none' ) .sort( (formatA: Format, formatB: Format) => (Number(formatB.height) || 0) - (Number(formatA.height) || 0) )[0]; if (!targetFormat) { targetFormat = info.formats .filter((item: Format) => item.vcodec !== 'none') .sort( (formatA: Format, formatB: Format) => (Number(formatB.height) || 0) - (Number(formatA.height) || 0) )[0]; } } } return { info, targetFormat, streamURL: _streamURL }; }