UNPKG

mebox-extractor

Version:

🎬 A powerful and type-safe video metadata extractor for YouTube and Bilibili platforms with full TypeScript support

1 lines • 53.9 kB
{"version":3,"sources":["../node_modules/retry/lib/retry_operation.js","../node_modules/retry/lib/retry.js","../node_modules/retry/index.js","../src/index.ts","../src/utils/http.ts","../node_modules/p-retry/index.js","../node_modules/is-network-error/index.js","../src/utils/cookie.ts","../src/clients/wbi-sign.ts","../src/clients/bilibili.ts","../src/extractors/bilibili.ts","../src/extractors/youtube.ts","../src/utils/browser.ts","../src/utils/common.ts"],"sourcesContent":["function RetryOperation(timeouts, options) {\n // Compatibility for the old (timeouts, retryForever) signature\n if (typeof options === 'boolean') {\n options = { forever: options };\n }\n\n this._originalTimeouts = JSON.parse(JSON.stringify(timeouts));\n this._timeouts = timeouts;\n this._options = options || {};\n this._maxRetryTime = options && options.maxRetryTime || Infinity;\n this._fn = null;\n this._errors = [];\n this._attempts = 1;\n this._operationTimeout = null;\n this._operationTimeoutCb = null;\n this._timeout = null;\n this._operationStart = null;\n this._timer = null;\n\n if (this._options.forever) {\n this._cachedTimeouts = this._timeouts.slice(0);\n }\n}\nmodule.exports = RetryOperation;\n\nRetryOperation.prototype.reset = function() {\n this._attempts = 1;\n this._timeouts = this._originalTimeouts.slice(0);\n}\n\nRetryOperation.prototype.stop = function() {\n if (this._timeout) {\n clearTimeout(this._timeout);\n }\n if (this._timer) {\n clearTimeout(this._timer);\n }\n\n this._timeouts = [];\n this._cachedTimeouts = null;\n};\n\nRetryOperation.prototype.retry = function(err) {\n if (this._timeout) {\n clearTimeout(this._timeout);\n }\n\n if (!err) {\n return false;\n }\n var currentTime = new Date().getTime();\n if (err && currentTime - this._operationStart >= this._maxRetryTime) {\n this._errors.push(err);\n this._errors.unshift(new Error('RetryOperation timeout occurred'));\n return false;\n }\n\n this._errors.push(err);\n\n var timeout = this._timeouts.shift();\n if (timeout === undefined) {\n if (this._cachedTimeouts) {\n // retry forever, only keep last error\n this._errors.splice(0, this._errors.length - 1);\n timeout = this._cachedTimeouts.slice(-1);\n } else {\n return false;\n }\n }\n\n var self = this;\n this._timer = setTimeout(function() {\n self._attempts++;\n\n if (self._operationTimeoutCb) {\n self._timeout = setTimeout(function() {\n self._operationTimeoutCb(self._attempts);\n }, self._operationTimeout);\n\n if (self._options.unref) {\n self._timeout.unref();\n }\n }\n\n self._fn(self._attempts);\n }, timeout);\n\n if (this._options.unref) {\n this._timer.unref();\n }\n\n return true;\n};\n\nRetryOperation.prototype.attempt = function(fn, timeoutOps) {\n this._fn = fn;\n\n if (timeoutOps) {\n if (timeoutOps.timeout) {\n this._operationTimeout = timeoutOps.timeout;\n }\n if (timeoutOps.cb) {\n this._operationTimeoutCb = timeoutOps.cb;\n }\n }\n\n var self = this;\n if (this._operationTimeoutCb) {\n this._timeout = setTimeout(function() {\n self._operationTimeoutCb();\n }, self._operationTimeout);\n }\n\n this._operationStart = new Date().getTime();\n\n this._fn(this._attempts);\n};\n\nRetryOperation.prototype.try = function(fn) {\n console.log('Using RetryOperation.try() is deprecated');\n this.attempt(fn);\n};\n\nRetryOperation.prototype.start = function(fn) {\n console.log('Using RetryOperation.start() is deprecated');\n this.attempt(fn);\n};\n\nRetryOperation.prototype.start = RetryOperation.prototype.try;\n\nRetryOperation.prototype.errors = function() {\n return this._errors;\n};\n\nRetryOperation.prototype.attempts = function() {\n return this._attempts;\n};\n\nRetryOperation.prototype.mainError = function() {\n if (this._errors.length === 0) {\n return null;\n }\n\n var counts = {};\n var mainError = null;\n var mainErrorCount = 0;\n\n for (var i = 0; i < this._errors.length; i++) {\n var error = this._errors[i];\n var message = error.message;\n var count = (counts[message] || 0) + 1;\n\n counts[message] = count;\n\n if (count >= mainErrorCount) {\n mainError = error;\n mainErrorCount = count;\n }\n }\n\n return mainError;\n};\n","var RetryOperation = require('./retry_operation');\n\nexports.operation = function(options) {\n var timeouts = exports.timeouts(options);\n return new RetryOperation(timeouts, {\n forever: options && (options.forever || options.retries === Infinity),\n unref: options && options.unref,\n maxRetryTime: options && options.maxRetryTime\n });\n};\n\nexports.timeouts = function(options) {\n if (options instanceof Array) {\n return [].concat(options);\n }\n\n var opts = {\n retries: 10,\n factor: 2,\n minTimeout: 1 * 1000,\n maxTimeout: Infinity,\n randomize: false\n };\n for (var key in options) {\n opts[key] = options[key];\n }\n\n if (opts.minTimeout > opts.maxTimeout) {\n throw new Error('minTimeout is greater than maxTimeout');\n }\n\n var timeouts = [];\n for (var i = 0; i < opts.retries; i++) {\n timeouts.push(this.createTimeout(i, opts));\n }\n\n if (options && options.forever && !timeouts.length) {\n timeouts.push(this.createTimeout(i, opts));\n }\n\n // sort the array numerically ascending\n timeouts.sort(function(a,b) {\n return a - b;\n });\n\n return timeouts;\n};\n\nexports.createTimeout = function(attempt, opts) {\n var random = (opts.randomize)\n ? (Math.random() + 1)\n : 1;\n\n var timeout = Math.round(random * Math.max(opts.minTimeout, 1) * Math.pow(opts.factor, attempt));\n timeout = Math.min(timeout, opts.maxTimeout);\n\n return timeout;\n};\n\nexports.wrap = function(obj, options, methods) {\n if (options instanceof Array) {\n methods = options;\n options = null;\n }\n\n if (!methods) {\n methods = [];\n for (var key in obj) {\n if (typeof obj[key] === 'function') {\n methods.push(key);\n }\n }\n }\n\n for (var i = 0; i < methods.length; i++) {\n var method = methods[i];\n var original = obj[method];\n\n obj[method] = function retryWrapper(original) {\n var op = exports.operation(options);\n var args = Array.prototype.slice.call(arguments, 1);\n var callback = args.pop();\n\n args.push(function(err) {\n if (op.retry(err)) {\n return;\n }\n if (err) {\n arguments[0] = op.mainError();\n }\n callback.apply(this, arguments);\n });\n\n op.attempt(function() {\n original.apply(obj, args);\n });\n }.bind(obj, original);\n obj[method].options = options;\n }\n};\n","module.exports = require('./lib/retry');","/**\n * Main extractor module for video metadata from YouTube and Bilibili platforms\n * Provides a unified interface for extracting video information\n */\nimport { extractBilibiliVideoMetadata } from './extractors/bilibili'\nimport { extractYoutubeVideoMetadata } from './extractors/youtube'\nimport { convertURLToWebsiteKey } from './utils/browser'\nimport { VideoMetadataSchema, ExtractorOptions, VideoMetadata, Website } from './types'\nimport { getVideoIdByURL } from './utils/common'\n\ntype BilibiliExtractor = typeof extractBilibiliVideoMetadata\ntype YouTubeExtractor = typeof extractYoutubeVideoMetadata\n\n/**\n * Registry of video metadata extractors mapped by website\n */\nconst VIDEO_METADATA_EXTRACTOR: Record<Website, BilibiliExtractor | YouTubeExtractor> = {\n bilibili: extractBilibiliVideoMetadata,\n youtube: extractYoutubeVideoMetadata\n}\n\n/**\n * Extracts video metadata from supported platforms (YouTube and Bilibili)\n * @param url - The video URL to extract metadata from\n * @param options - Optional extraction options including resolution and cookies\n * @returns Promise resolving to video metadata\n * @throws Error if the website is not supported or video ID cannot be found\n */\nexport const extract = async (\n url: string,\n options: ExtractorOptions = {}\n): Promise<VideoMetadataSchema> => {\n // Determine the website platform from URL\n const website = convertURLToWebsiteKey(url)\n const extractor = VIDEO_METADATA_EXTRACTOR[website!]\n\n if (!extractor) {\n throw new Error(`The website ${website} is not supported`)\n }\n\n // Extract video ID from URL\n const videoId = getVideoIdByURL(url)\n\n if (!videoId) {\n throw new Error(`The video id is not found`)\n }\n\n let metadata: VideoMetadata\n\n // Call the appropriate extractor based on website\n if (website === 'bilibili') {\n metadata = await (extractor as BilibiliExtractor)(url, videoId, options)\n } else {\n metadata = await (extractor as YouTubeExtractor)(url, options)\n }\n\n return metadata\n}\n\n/**\n * Alias for the main extract function\n */\nexport const extractVideoMetadata = extract\n\n/**\n * Default export for the extract function\n */\nexport { extract as default }\n\nexport type {\n VideoMetadata,\n VideoMetadataSchema,\n ExtractorOptions,\n Website,\n SubTitles,\n SubTitleItem,\n DownloadFormat,\n Resolution\n} from './types'\n\nexport { convertURLToWebsiteKey } from './utils/browser'\nexport { getVideoIdByURL } from './utils/common'\nexport { BilibiliClient } from './clients/bilibili'","/**\n * HTTP client utilities with retry logic and error handling\n */\nimport { merge } from \"lodash-es\";\nimport pRetry from \"p-retry\";\n\n/**\n * API path type that must start with a slash\n */\ntype Path = `/${string}`;\n\n/**\n * Configuration options for API client\n */\nexport interface APIClientOptions<T> {\n init?: RequestInit;\n afterResponse?: (response: Response) => Promise<T>;\n}\n\n/**\n * Generic API client with retry logic and configurable response handling\n */\nexport class APIClient<\n O extends APIClientOptions<unknown> = APIClientOptions<Response>,\n> {\n /**\n * Creates a new API client\n * @param url - Base URL for the API\n * @param options - Client configuration options\n */\n constructor(\n private url: string,\n private options?: O,\n ) {}\n\n /**\n * Static method to send a simple HTTP request with retry logic\n * @param url - Full URL to send request to\n * @param init - Fetch request options\n * @returns Promise resolving to Response\n */\n static async send(url: string, init?: RequestInit): Promise<Response> {\n return pRetry(\n async () => {\n const response = await fetch(url, init);\n if (!response.ok) {\n throw new Error(`Request failed with status code ${response.status}`);\n }\n return response;\n },\n { retries: 3 },\n );\n }\n\n /**\n * Sends a request to the configured base URL with optional path\n * @param path - API endpoint path\n * @param init - Fetch request options\n * @returns Promise resolving to processed response\n */\n async send(path?: Path, init?: RequestInit) {\n const endpoint = `${this.url}${path ?? \"\"}`;\n const parsedInit = merge(init ?? {}, this.options?.init ?? {});\n\n const response = await pRetry(\n async () => {\n const res = await fetch(endpoint, parsedInit);\n if (!res.ok) {\n throw new Error(`Request failed with status code ${res.status}`);\n }\n return res;\n },\n { retries: 3 },\n );\n\n // Apply custom response processing if configured\n return (this.options?.afterResponse?.(response) ??\n response) as O extends APIClientOptions<infer T> ? T : Response;\n }\n\n /**\n * Sends a GET request\n * @param path - API endpoint path\n * @param init - Additional fetch options\n * @returns Promise resolving to response\n */\n async get(path: Path, init?: RequestInit) {\n return this.send(path, merge(init, { method: \"GET\" }));\n }\n\n /**\n * Sends a POST request with JSON body\n * @param path - API endpoint path\n * @param body - Request body data\n * @param init - Additional fetch options\n * @returns Promise resolving to response\n */\n async post(path: Path, body?: Record<string, unknown>, init?: RequestInit) {\n return this.send(\n path,\n merge(init, {\n method: \"POST\",\n body: JSON.stringify(body),\n headers: { \"Content-Type\": \"application/json\" },\n }),\n );\n }\n}\n","import retry from 'retry';\nimport isNetworkError from 'is-network-error';\n\nexport class AbortError extends Error {\n\tconstructor(message) {\n\t\tsuper();\n\n\t\tif (message instanceof Error) {\n\t\t\tthis.originalError = message;\n\t\t\t({message} = message);\n\t\t} else {\n\t\t\tthis.originalError = new Error(message);\n\t\t\tthis.originalError.stack = this.stack;\n\t\t}\n\n\t\tthis.name = 'AbortError';\n\t\tthis.message = message;\n\t}\n}\n\nconst decorateErrorWithCounts = (error, attemptNumber, options) => {\n\t// Minus 1 from attemptNumber because the first attempt does not count as a retry\n\tconst retriesLeft = options.retries - (attemptNumber - 1);\n\n\terror.attemptNumber = attemptNumber;\n\terror.retriesLeft = retriesLeft;\n\treturn error;\n};\n\nexport default async function pRetry(input, options) {\n\treturn new Promise((resolve, reject) => {\n\t\toptions = {...options};\n\t\toptions.onFailedAttempt ??= () => {};\n\t\toptions.shouldRetry ??= () => true;\n\t\toptions.retries ??= 10;\n\n\t\tconst operation = retry.operation(options);\n\n\t\tconst abortHandler = () => {\n\t\t\toperation.stop();\n\t\t\treject(options.signal?.reason);\n\t\t};\n\n\t\tif (options.signal && !options.signal.aborted) {\n\t\t\toptions.signal.addEventListener('abort', abortHandler, {once: true});\n\t\t}\n\n\t\tconst cleanUp = () => {\n\t\t\toptions.signal?.removeEventListener('abort', abortHandler);\n\t\t\toperation.stop();\n\t\t};\n\n\t\toperation.attempt(async attemptNumber => {\n\t\t\ttry {\n\t\t\t\tconst result = await input(attemptNumber);\n\t\t\t\tcleanUp();\n\t\t\t\tresolve(result);\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tif (!(error instanceof Error)) {\n\t\t\t\t\t\tthrow new TypeError(`Non-error was thrown: \"${error}\". You should only throw errors.`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (error instanceof AbortError) {\n\t\t\t\t\t\tthrow error.originalError;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (error instanceof TypeError && !isNetworkError(error)) {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\n\t\t\t\t\tdecorateErrorWithCounts(error, attemptNumber, options);\n\n\t\t\t\t\tif (!(await options.shouldRetry(error))) {\n\t\t\t\t\t\toperation.stop();\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait options.onFailedAttempt(error);\n\n\t\t\t\t\tif (!operation.retry(error)) {\n\t\t\t\t\t\tthrow operation.mainError();\n\t\t\t\t\t}\n\t\t\t\t} catch (finalError) {\n\t\t\t\t\tdecorateErrorWithCounts(finalError, attemptNumber, options);\n\t\t\t\t\tcleanUp();\n\t\t\t\t\treject(finalError);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n","const objectToString = Object.prototype.toString;\n\nconst isError = value => objectToString.call(value) === '[object Error]';\n\nconst errorMessages = new Set([\n\t'network error', // Chrome\n\t'Failed to fetch', // Chrome\n\t'NetworkError when attempting to fetch resource.', // Firefox\n\t'The Internet connection appears to be offline.', // Safari 16\n\t'Load failed', // Safari 17+\n\t'Network request failed', // `cross-fetch`\n\t'fetch failed', // Undici (Node.js)\n\t'terminated', // Undici (Node.js)\n]);\n\nexport default function isNetworkError(error) {\n\tconst isValid = error\n\t\t&& isError(error)\n\t\t&& error.name === 'TypeError'\n\t\t&& typeof error.message === 'string';\n\n\tif (!isValid) {\n\t\treturn false;\n\t}\n\n\t// We do an extra check for Safari 17+ as it has a very generic error message.\n\t// Network errors in Safari have no stack.\n\tif (error.message === 'Load failed') {\n\t\treturn error.stack === undefined;\n\t}\n\n\treturn errorMessages.has(error.message);\n}\n","/**\n * Cookie parsing and formatting utilities\n */\n\n/**\n * Parses a cookie string into a key-value object\n * @param cookieString - Cookie string to parse (format: \"name1=value1; name2=value2\")\n * @returns Object with cookie names as keys and values as values\n */\nexport const parseCookiesString = (cookieString: string): Record<string, string> =>\n cookieString\n .split(';')\n .map((pair) => {\n const [name, value] = pair.trim().split('=')\n return [name?.trim(), value?.trim()]\n })\n .filter(([name, value]) => name && value)\n .reduce<Record<string, string>>(\n (acc, [name, value]) => ({ ...acc, [name]: value }),\n {}\n )\n\n/**\n * Formats a cookie object into a cookie string\n * @param cookies - Object with cookie names and values\n * @returns Cookie string in format \"name1=value1;name2=value2\"\n */\nexport const formatCookies = (cookies: Record<string, string>): string =>\n Object.entries(cookies)\n .map(([name, value]) => `${name}=${value}`)\n .join(';')","/**\n * Bilibili WBI (Web Broadcast Interface) signature implementation\n * Used for signing API requests to Bilibili\n */\nimport md5 from 'md5'\n\n/**\n * Encoding table for mixin key generation\n * This is part of Bilibili's WBI signing algorithm\n */\nconst MIXIN_KEY_ENCODING_TAB = [\n 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,\n 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61,\n 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36,\n 20, 34, 44, 52\n]\n\n/**\n * Generates mixin key from original string using encoding table\n * @param orig - Original string to encode\n * @returns Encoded mixin key (first 32 characters)\n */\nconst getMixinKey = (orig: string) =>\n MIXIN_KEY_ENCODING_TAB.map((n) => orig[n])\n .join('')\n .slice(0, 32)\n\n/**\n * Encodes parameters using Bilibili's WBI signing algorithm\n * @param originParams - Original parameters to sign\n * @param img_key - Image key from Bilibili WBI endpoint\n * @param sub_key - Sub key from Bilibili WBI endpoint\n * @returns Signed query string with w_rid parameter\n */\nexport const encodeWbi = (\n originParams: Record<string, string | number>,\n img_key: string,\n sub_key: string\n) => {\n // Generate mixin key from img_key and sub_key\n const mixin_key = getMixinKey(img_key + sub_key)\n const curr_time = Math.round(Date.now() / 1000)\n const chr_filter = /[!'()*]/g\n \n // Add timestamp to parameters\n const params = Object.assign({}, originParams, { wts: curr_time })\n \n // Create sorted query string with filtered values\n const query = Object.keys(params)\n .sort()\n .map((key) => {\n const value = params[key].toString().replace(chr_filter, '')\n\n return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`\n })\n .join('&')\n \n // Generate WBI signature\n const wbi_sign = md5(query + mixin_key)\n\n return query + '&w_rid=' + wbi_sign\n}","/**\n * Bilibili API client for video metadata extraction\n * Handles authentication, WBI signing, and API requests\n */\nimport { APIClient } from '../utils/http'\nimport { Resolution } from '../types'\nimport { parseCookiesString, formatCookies } from '../utils/cookie'\nimport { encodeWbi } from './wbi-sign'\n\n/**\n * Bilibili video view information structure\n */\ninterface VideoViewInfo {\n aid: number\n cid: number\n desc: string\n title: string\n duration: number\n pic: string\n}\n\n/**\n * Bilibili subtitle information structure\n */\ninterface Subtitle {\n lan_doc: string\n subtitle_url: string\n}\n\n/**\n * Mapping of resolution strings to Bilibili quality codes\n */\nconst RESOLUTION_CODE: Record<Resolution, number> = {\n '4k': 120,\n '1080p': 80,\n '720p': 64,\n '360p': 16\n}\n\n/**\n * Bilibili API client for video operations\n */\nexport class BilibiliClient {\n private apiClient: APIClient\n\n /**\n * Creates a new Bilibili client\n * @param cookies - Authentication cookies\n */\n constructor(private cookies: Record<string, string> = {}) {\n const cookieString = formatCookies(cookies)\n\n this.apiClient = new APIClient('https://api.bilibili.com', {\n init: {\n headers: { cookie: cookieString }\n }\n })\n }\n\n /**\n * Checks if user is logged in based on SESSDATA cookie\n */\n get isLogin() {\n return !!this.cookies.SESSDATA\n }\n\n /**\n * Fetches video URL based on login status\n * @param bvid - Bilibili video ID\n * @param cid - Video CID\n * @param resolution - Desired resolution\n * @param isPreview - Whether this is for preview\n * @returns Video URL and quality info\n */\n private fetchVideo(\n bvid: string,\n cid: number,\n resolution: Resolution,\n isPreview = false\n ) {\n if (this.isLogin) {\n return this.getHighResolutionDownloadURL(bvid, cid, resolution, isPreview)\n }\n\n return this.getLowResolutionDownloadURL(bvid, cid)\n }\n\n /**\n * Gets WBI signing keys from Bilibili API\n * @returns Object containing img_key and sub_key for WBI signing\n */\n async getWbiKeys() {\n const response = await this.apiClient.send('/x/web-interface/nav')\n const {\n data: {\n wbi_img: { img_url, sub_url }\n }\n } = await response.json()\n\n return {\n img_key: img_url.slice(\n img_url.lastIndexOf('/') + 1,\n img_url.lastIndexOf('.')\n ),\n sub_key: sub_url.slice(\n sub_url.lastIndexOf('/') + 1,\n sub_url.lastIndexOf('.')\n )\n }\n }\n\n /**\n * Signs API parameters using WBI algorithm\n * @param params - Parameters to sign\n * @returns Signed query string\n */\n async signParams(params: Record<string, string | number>) {\n const { img_key, sub_key } = await this.getWbiKeys()\n\n return encodeWbi(params, img_key, sub_key)\n }\n\n /**\n * Gets basic video information\n * @param bvid - Bilibili video ID\n * @returns Video view information\n */\n async getInfo(bvid: string): Promise<VideoViewInfo> {\n const response = await this.apiClient.send(\n `/x/web-interface/view?bvid=${bvid}`\n )\n const { data } = await response.json()\n\n return data\n }\n\n /**\n * Gets available subtitles for a video\n * @param aid - Video AID\n * @param cid - Video CID\n * @returns Array of available subtitles\n */\n async getSubtitles(aid: number, cid: number): Promise<Subtitle[]> {\n const response = await this.apiClient.send(\n `/x/player/wbi/v2?aid=${aid}&cid=${cid}`\n )\n const { data } = await response.json()\n\n return data.subtitle?.subtitles ?? []\n }\n\n /**\n * Gets preview URL for a video\n * @param bvid - Bilibili video ID\n * @param cid - Video CID\n * @returns Preview URL and quality info\n */\n async getPreviewURL(bvid: string, cid: number) {\n return this.fetchVideo(bvid, cid, '1080p', true)\n }\n\n async getLowResolutionDownloadURL(bvid: string, cid: number) {\n const signedParams = await this.signParams({\n bvid,\n cid,\n otype: 'json',\n platform: 'html5'\n })\n const response = await this.apiClient.send(\n `/x/player/wbi/playurl?${signedParams}`\n )\n\n const { data } = await response.json()\n\n return {\n url: data.durl?.[0]?.url ?? data.durl?.[0]?.backup_url?.[0],\n quality: data.quality\n }\n }\n\n async getHighResolutionDownloadURL(\n bvid: string,\n cid: number,\n resolution: Resolution,\n isPreview?: boolean\n ) {\n const signedParams = await this.signParams({\n bvid,\n cid,\n otype: 'json',\n qn: RESOLUTION_CODE[resolution].toString() ?? '80',\n high_quality: ['4k', '1080p'].includes(resolution) ? '1' : '0'\n })\n const resolution4KParams =\n resolution === '4k' ? '&fnval&128=128&fourk=1' : ''\n const platform = isPreview ? '&platform=html5' : ''\n const response = await this.apiClient.send(\n `/x/player/wbi/playurl?${signedParams}${resolution4KParams}${platform}`\n )\n const { data } = await response.json()\n\n return {\n url: data.durl?.[0]?.url,\n quality: data.quality\n }\n }\n\n /**\n * Gets download URL for a video with quality validation\n * @param bvid - Bilibili video ID\n * @param cid - Video CID\n * @param resolution - Desired resolution\n * @returns Download URL\n */\n async getDownloadURL(bvid: string, cid: number, resolution: Resolution) {\n const { url, quality } = await this.fetchVideo(bvid, cid, resolution)\n const expectedQuality = RESOLUTION_CODE[resolution].toString()\n\n // Warn if requested quality is not available\n if (expectedQuality && expectedQuality !== quality.toString()) {\n const label = Object.keys(RESOLUTION_CODE).find(\n (key) => RESOLUTION_CODE[key as Resolution] === quality\n )\n\n console.warn(\n `The requested resolution is not available, using ${label ?? 'lowest quality'} instead.`\n )\n }\n\n return url\n }\n}","/**\n * Bilibili video metadata extractor\n * Handles video information retrieval from Bilibili platform\n */\nimport { BilibiliClient } from \"../clients/bilibili\";\nimport { SubTitles, VideoMetadata, ExtractorOptions } from \"../types\";\nimport { APIClient } from \"../utils/http\";\n\n/**\n * Bilibili subtitle item structure\n */\ninterface BilibiliSubTitleItem {\n from: number;\n to: number;\n content: string;\n}\n\n/**\n * Bilibili subtitle response structure\n */\ninterface SubTitleRes {\n body: BilibiliSubTitleItem[];\n}\n\n/**\n * Parses Bilibili subtitle data from URL\n * @param subtitleURL - URL to fetch subtitle data from\n * @returns Promise resolving to parsed subtitles\n */\nconst parseSubtitles = async (subtitleURL: string): Promise<SubTitles> => {\n // Ensure subtitle URL has proper protocol\n const parsedSubtitleURL = subtitleURL.startsWith(\"http\")\n ? subtitleURL\n : `http:${subtitleURL}`;\n const response = await APIClient.send(parsedSubtitleURL);\n const data: SubTitleRes = await response.json();\n\n return data.body.map(({ from, to, content }: BilibiliSubTitleItem) => ({\n start: from,\n end: to,\n text: content,\n }));\n};\n\n/**\n * Extracts video metadata from Bilibili\n * @param url - Original video URL\n * @param bvid - Bilibili video ID\n * @param options - Extraction options including resolution and cookies\n * @returns Promise resolving to video metadata\n */\nexport const extractBilibiliVideoMetadata = async (\n url: string,\n bvid: string,\n options: ExtractorOptions = {},\n): Promise<VideoMetadata> => {\n const { resolution = \"720p\", cookies = {} } = options;\n const bilibiliClient = new BilibiliClient(cookies);\n \n // Get basic video information\n const { aid, cid, duration, desc, title, pic } =\n await bilibiliClient.getInfo(bvid);\n \n // Get subtitle information\n const [subtitle] = await bilibiliClient.getSubtitles(aid, cid);\n const subtitleContent = subtitle\n ? await parseSubtitles(subtitle.subtitle_url)\n : undefined;\n \n // Get download and preview URLs in parallel\n const [downloadURL, { url: previewURL }] = await Promise.all([\n bilibiliClient.getDownloadURL(bvid, cid, resolution),\n bilibiliClient.getPreviewURL(bvid, cid),\n ]);\n\n return {\n title,\n website: \"bilibili\",\n subtitles: subtitleContent,\n duration,\n videoId: bvid,\n url,\n thumbnail: pic,\n description: desc,\n previewURL,\n downloadURL,\n fps: 25,\n };\n};\n","/**\n * YouTube video metadata extractor\n * Handles video information retrieval from YouTube platform using youtubei.js\n */\nimport { ClientType, Innertube, UniversalCache } from \"youtubei.js\";\nimport { getVideoIdByURL } from \"../utils/common\";\nimport { APIClient } from \"../utils/http\";\nimport { XMLParser } from \"fast-xml-parser\";\nimport {\n DownloadFormat,\n SubTitles,\n VideoMetadata,\n ExtractorOptions,\n} from \"../types\";\nimport { formatCookies } from \"../utils/cookie\";\nimport pRetry from \"p-retry\";\nimport { isNodeJS } from \"@/utils/browser\";\n\n/**\n * YouTube subtitle XML item structure\n */\ninterface SubTitleXMLItem {\n content: string;\n start: string;\n dur: string;\n}\n\n/**\n * Creates a custom fetch function for YouTube requests\n * Modifies requests to work with YouTube API\n */\nconst createFetchFn = () => (input: RequestInfo | URL, init?: RequestInit) => {\n const url =\n typeof input === \"string\"\n ? new URL(input)\n : input instanceof URL\n ? input\n : new URL(input.url);\n\n url.searchParams.set(\"__host\", url.host);\n url.protocol = \"http\";\n\n const headers = init?.headers\n ? new Headers(init.headers)\n : input instanceof Request\n ? input.headers\n : new Headers();\n\n url.searchParams.set(\"__headers\", JSON.stringify([...headers]));\n\n const request = new Request(\n url,\n input instanceof Request ? input : undefined,\n );\n\n headers.delete(\"user-agent\");\n\n return fetch(request, {\n ...(init ?? {}),\n headers,\n });\n};\n\n/**\n * Parses YouTube subtitle XML data\n * @param subtitleURL - URL to fetch subtitle XML from\n * @returns Promise resolving to parsed subtitles\n */\nconst parseSubtitles = async (subtitleURL: string): Promise<SubTitles> => {\n const response = await APIClient.send(subtitleURL);\n const rawXMLData = await response.text();\n\n if (!rawXMLData) {\n return [];\n }\n\n const parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: \"\",\n textNodeName: \"content\",\n });\n const data: SubTitleXMLItem[] = parser.parse(rawXMLData).transcript.text;\n\n return data.map(({ content, start, dur }) => ({\n start: parseFloat(start),\n end: parseFloat(start) + parseFloat(dur),\n text: content,\n }));\n};\n\n/**\n * Gets video information for high quality video formats\n * @param yt - Innertube instance\n * @param videoId - YouTube video ID\n * @param defaultResolution - Preferred resolution\n * @returns Video info with separate audio/video streams or undefined\n */\nconst getVideoInfoForHighQualityVideo = async (\n yt: Innertube,\n videoId: string,\n defaultResolution: string,\n) => {\n const info = await pRetry(() => yt.getInfo(videoId), { retries: 3 });\n const { streaming_data } = info;\n // Helper function to choose format by type with fallback\n const chooseFormatByType = (type: \"audio\" | \"video\") => {\n try {\n return info.chooseFormat({\n type,\n quality: type === \"audio\" ? \"best\" : defaultResolution,\n });\n } catch {\n console.warn(`Failed to get ${type} format, fallback to best quality`);\n\n return info.chooseFormat({\n type,\n quality: \"best\",\n });\n }\n };\n\n if (!streaming_data?.adaptive_formats.length) {\n return;\n }\n\n const resourceType = [\"video\", \"audio\"] as const;\n const downloadURL = resourceType.map((type) => {\n const format = chooseFormatByType(type);\n\n return {\n url: format.decipher(yt.session.player),\n mimeType: format.mime_type,\n };\n }) as [DownloadFormat, DownloadFormat];\n\n return {\n downloadURL,\n info,\n };\n};\n\n/**\n * Gets video information for low quality video formats (single stream)\n * @param yt - Innertube instance\n * @param videoId - YouTube video ID\n * @returns Video info with combined audio/video stream or undefined\n */\nconst getVideoInfoForLowQualityVideo = async (\n yt: Innertube,\n videoId: string,\n) => {\n const info = await yt.getInfo(videoId);\n const { streaming_data } = info;\n const [format] = streaming_data?.formats ?? [];\n\n if (!format) {\n return;\n }\n\n return {\n downloadURL: format.decipher(yt.session.player),\n info,\n };\n};\n\n/**\n * Extracts video metadata from YouTube\n * @param url - Original video URL\n * @param options - Extraction options including resolution and cookies\n * @returns Promise resolving to video metadata\n */\nexport const extractYoutubeVideoMetadata = async (\n url: string,\n options: ExtractorOptions = {},\n): Promise<VideoMetadata> => {\n const { resolution = \"720p\", cookies = {} } = options;\n const cookieString = formatCookies(cookies);\n const videoId = getVideoIdByURL(url);\n\n // Create Innertube instance with custom configuration\n const createInnrTubeByClient = (clientType?: ClientType) =>\n Innertube.create({\n fetch: isNodeJS() ? undefined : createFetchFn(),\n cookie: cookieString,\n generate_session_locally: true,\n enable_session_cache: false,\n retrieve_player: true,\n cache: new UniversalCache(true),\n client_type: clientType,\n });\n // Try to get high quality video first, fallback to low quality\n const videoInfo =\n (await getVideoInfoForHighQualityVideo(\n await createInnrTubeByClient(ClientType.WEB_EMBEDDED),\n videoId,\n resolution,\n )) ??\n (await getVideoInfoForLowQualityVideo(\n await createInnrTubeByClient(),\n videoId,\n ));\n\n if (!videoInfo) {\n throw new Error(\"Could not get correct video format\");\n }\n\n // Extract basic info and captions\n const { basic_info, captions } = videoInfo.info;\n const [caption] = captions?.caption_tracks ?? [];\n const subtitles = caption?.base_url\n ? await parseSubtitles(caption.base_url)\n : undefined;\n\n // Determine preview URL based on download format type\n const previewURL =\n typeof videoInfo.downloadURL === \"string\"\n ? videoInfo.downloadURL\n : videoInfo.downloadURL[0].url;\n\n return {\n previewURL,\n title: basic_info.title ?? \"\",\n website: \"youtube\",\n subtitles,\n duration: basic_info.duration ?? 0,\n videoId,\n url,\n thumbnail: basic_info.thumbnail?.[0].url ?? \"\",\n description: basic_info.short_description ?? \"\",\n downloadURL: videoInfo.downloadURL,\n fps: 0,\n };\n};\n","/**\n * Browser-related utilities for URL parsing and website detection\n */\nimport { Website } from \"../types\";\n\n/**\n * Regular expressions for validating supported website URLs\n */\nconst SUPPORTED_WEBSITES_VALIDATORS = {\n youtube: /(youtube|youtu)/,\n bilibili: /bilibili/,\n} as const;\n\n/**\n * Converts a URL to a website key by matching hostname patterns\n * @param url - The URL to analyze\n * @returns The website key if supported, undefined otherwise\n */\nexport const convertURLToWebsiteKey = (url?: string): Website | undefined => {\n if (!url) {\n return;\n }\n\n const { hostname } = new URL(url);\n const websites = Object.keys(SUPPORTED_WEBSITES_VALIDATORS) as Website[];\n\n // Find matching website by testing hostname against regex patterns\n return websites.find((website) => {\n const regex = SUPPORTED_WEBSITES_VALIDATORS[website];\n return regex?.test(hostname);\n });\n};\n\nexport const isNodeJS = () =>\n typeof process !== \"undefined\" && process.versions?.node;\n","/**\n * Common utilities for video ID extraction from URLs\n */\nimport { convertURLToWebsiteKey } from './browser'\n\n/**\n * Website-specific functions for extracting video IDs from URLs\n */\nconst WEBSITES_ID_GETTERS = {\n bilibili: (url: URL) => url.pathname.split('/')[2],\n youtube: (url: URL) => {\n // Handle different YouTube URL formats\n // youtu.be/VIDEO_ID\n if (url.hostname.includes('youtu.be')) {\n return url.pathname.slice(1) // Remove leading slash\n }\n // youtube.com/watch?v=VIDEO_ID\n if (url.searchParams.get('v')) {\n return url.searchParams.get('v')\n }\n // youtube.com/shorts/VIDEO_ID\n const shortsMatch = url.pathname.split('shorts/')[1]\n return shortsMatch\n }\n}\n\n/**\n * Extracts video ID from a video URL\n * @param url - The video URL to extract ID from\n * @returns The video ID\n * @throws Error if website is not supported or video ID cannot be found\n */\nexport const getVideoIdByURL = (url: string) => {\n const website = convertURLToWebsiteKey(url)\n\n if (!website) {\n throw new Error(`The website is not supported ${url}`)\n }\n\n // Use website-specific getter to extract video ID\n const videoId = WEBSITES_ID_GETTERS[website](new URL(url))\n\n if (!videoId) {\n throw new Error(`The video id is not found, please check the URL ${url}`)\n }\n\n return videoId\n}"],"mappings":"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