youtube-sr
Version:
Simple package to make YouTube search.
1,211 lines (1,204 loc) • 53.9 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
var __await = function(promise, isYieldStar) {
this[0] = promise;
this[1] = isYieldStar;
};
var __yieldStar = (value) => {
var obj = value[__knownSymbol("asyncIterator")], isAwait = false, method, it = {};
if (obj == null) {
obj = value[__knownSymbol("iterator")]();
method = (k) => it[k] = (x) => obj[k](x);
} else {
obj = obj.call(value);
method = (k) => it[k] = (v) => {
if (isAwait) {
isAwait = false;
if (k === "throw") throw v;
return v;
}
isAwait = true;
return {
done: false,
value: new __await(new Promise((resolve) => {
var x = obj[k](v);
if (!(x instanceof Object)) __typeError("Object expected");
resolve(x);
}), 1)
};
};
}
return it[__knownSymbol("iterator")] = () => it, method("next"), "throw" in obj ? method("throw") : it.throw = (x) => {
throw x;
}, "return" in obj && method("return"), it;
};
// src/formatter.ts
var _Formatter = class _Formatter {
constructor() {
return _Formatter;
}
static formatSearchResult(details, options = {
limit: 100,
type: "all"
}) {
const results = [];
for (let i = 0; i < details.length; i++) {
if (typeof options.limit === "number" && options.limit > 0 && results.length >= options.limit) break;
let data = details[i];
let res;
if (options.type === "all") {
if (!!data.videoRenderer) options.type = "video";
else if (!!data.channelRenderer) options.type = "channel";
else if (!!data.playlistRenderer) options.type = "playlist";
else continue;
}
if (options.type === "video" || options.type === "film") {
const parsed = Util_default.parseVideo(data);
if (!parsed) continue;
res = parsed;
} else if (options.type === "channel") {
const parsed = Util_default.parseChannel(data);
if (!parsed) continue;
res = parsed;
} else if (options.type === "playlist") {
const parsed = Util_default.parsePlaylist(data);
if (!parsed) continue;
res = parsed;
}
results.push(res);
}
return results;
}
};
__name(_Formatter, "Formatter");
var Formatter = _Formatter;
// src/Structures/Channel.ts
var _Channel = class _Channel {
constructor(data) {
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
this._patch(data);
}
/**
* Patch raw data
* @private
* @ignore
*/
_patch(data) {
var _a;
if (!data) data = {};
this.name = data.name || null;
this.verified = !!data.verified || false;
this.id = data.id || null;
this.url = data.url || null;
this.icon = data.icon || { url: null, width: 0, height: 0 };
this.subscribers = data.subscribers || null;
if ((_a = this.icon.url) == null ? void 0 : _a.startsWith("//")) this.icon.url = `https:${this.icon.url}`;
}
/**
* Returns channel icon url
* @param {object} options Icon options
* @param {number} [options.size=0] Icon size. **Default is 0**
*/
iconURL(options = { size: 0 }) {
if (typeof options.size !== "number" || options.size < 0) throw new Error("invalid icon size");
if (!this.icon.url) return null;
const def = this.icon.url.split("=s")[1].split("-c")[0];
return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`);
}
get type() {
return "channel";
}
toString() {
return this.name || "";
}
toJSON() {
return {
name: this.name,
verified: this.verified,
id: this.id,
url: this.url,
iconURL: this.iconURL(),
type: this.type,
subscribers: this.subscribers
};
}
};
__name(_Channel, "Channel");
var Channel = _Channel;
// src/Structures/Thumbnail.ts
var _Thumbnail = class _Thumbnail {
/**
* Thumbnail constructor
* @param data Thumbnail constructor params
*/
constructor(data) {
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
this._patch(data);
}
/**
* Patch raw data
* @param data Raw Data
* @private
* @ignore
*/
_patch(data) {
if (!data) data = {};
this.id = data.id || null;
this.width = data.width || 0;
this.height = data.height || 0;
this.url = data.url || null;
}
/**
* Returns thumbnail url
* @param {"default"|"hqdefault"|"mqdefault"|"sddefault"|"maxresdefault"|"ultrares"} thumbnailType Thumbnail type
*/
displayThumbnailURL(thumbnailType = "ultrares") {
if (!["default", "hqdefault", "mqdefault", "sddefault", "maxresdefault", "ultrares"].includes(thumbnailType)) throw new Error(`Invalid thumbnail type "${thumbnailType}"!`);
if (thumbnailType === "ultrares") return this.url;
return `https://i3.ytimg.com/vi/${this.id}/${thumbnailType}.jpg`;
}
/**
* Returns default thumbnail
* @param {"0"|"1"|"2"|"3"|"4"} id Thumbnail id. **4 returns thumbnail placeholder.**
*/
defaultThumbnailURL(id) {
if (!id) id = "0";
if (!["0", "1", "2", "3", "4"].includes(id)) throw new Error(`Invalid thumbnail id "${id}"!`);
return `https://i3.ytimg.com/vi/${this.id}/${id}.jpg`;
}
toString() {
return this.url ? `${this.url}` : "";
}
toJSON() {
return {
id: this.id,
width: this.width,
height: this.height,
url: this.url
};
}
};
__name(_Thumbnail, "Thumbnail");
var Thumbnail = _Thumbnail;
// src/Structures/Playlist.ts
var BASE_API = "https://www.youtube.com/youtubei/v1/browse?key=";
var _Playlist = class _Playlist {
constructor(data = {}, searchResult = false) {
this.fake = false;
this._continuation = {};
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
Object.defineProperty(this, "_continuation", { enumerable: false, configurable: true, writable: true });
if (!!searchResult) this._patchSearch(data);
else this._patch(data);
}
_patch(data) {
var _a, _b, _c, _d, _e, _f;
this.id = data.id || null;
this.title = data.title || null;
this.videoCount = data.videoCount || 0;
this.lastUpdate = data.lastUpdate || null;
this.views = data.views || 0;
this.url = data.url || data.link || this.id ? `https://www.youtube.com/playlist?list=${this.id}` : null;
this.link = data.link || data.url || null;
this.channel = data.author || null;
this.thumbnail = new Thumbnail(data.thumbnail || {});
this.videos = data.videos || [];
this._continuation.api = (_b = (_a = data.continuation) == null ? void 0 : _a.api) != null ? _b : null;
this._continuation.token = (_d = (_c = data.continuation) == null ? void 0 : _c.token) != null ? _d : null;
this._continuation.clientVersion = (_f = (_e = data.continuation) == null ? void 0 : _e.clientVersion) != null ? _f : "<important data>";
this.mix = data.mix || false;
this.fake = Boolean(data.fake);
}
_patchSearch(data) {
var _a;
this.id = data.id || null;
this.title = data.title || null;
this.thumbnail = new Thumbnail(data.thumbnail || {});
this.channel = data.channel || null;
this.videos = data.videos || [];
this.videoCount = ((_a = data.videos) == null ? void 0 : _a.length) || 0;
this.url = data.url || data.link || this.id ? `https://www.youtube.com/playlist?list=${this.id}` : null;
this.link = data.link || data.url || null;
this.lastUpdate = null;
this.views = 0;
this.mix = data.mix || false;
this.fake = Boolean(data.fake);
}
/**
* @param limit Max items to parse from current chunk
*/
next(limit = Infinity) {
return __async(this, null, function* () {
var _a, _b, _c;
if (!this._continuation || !this._continuation.token) return [];
const nextPage = yield Util_default.getHTML(`${BASE_API}${this._continuation.api}`, {
method: "POST",
body: JSON.stringify({
continuation: this._continuation.token,
context: {
client: {
utcOffsetMinutes: 0,
gl: "US",
hl: "en",
clientName: "WEB",
clientVersion: this._continuation.clientVersion
},
user: {},
request: {}
}
})
});
const contents = (_c = (_b = (_a = Util_default.json(nextPage)) == null ? void 0 : _a.onResponseReceivedActions[0]) == null ? void 0 : _b.appendContinuationItemsAction) == null ? void 0 : _c.continuationItems;
if (!contents) return [];
const partial = Util_default.getPlaylistVideos(contents, limit);
this._continuation.token = Util_default.getContinuationToken(contents);
this.videos = [...this.videos, ...partial];
return partial;
});
}
fetch(max = Infinity) {
return __async(this, null, function* () {
const ctn = this._continuation.token;
if (!ctn) return this;
if (max < 1) max = Infinity;
while (typeof this._continuation.token === "string" && this._continuation.token.length) {
if (this.videos.length >= max) break;
const res = yield this.next();
if (!res.length) break;
}
return this;
});
}
get type() {
return "playlist";
}
*[Symbol.iterator]() {
yield* __yieldStar(this.videos);
}
toJSON() {
var _a, _b, _c;
return {
id: this.id,
title: this.title,
thumbnail: ((_a = this.thumbnail) == null ? void 0 : _a.toJSON()) || null,
channel: {
name: this.channel.name,
id: this.channel.id,
icon: (_c = (_b = this.channel) == null ? void 0 : _b.iconURL) == null ? void 0 : _c.call(_b)
},
mix: this.mix,
url: this.url,
videos: this.videos
};
}
};
__name(_Playlist, "Playlist");
var Playlist = _Playlist;
// src/Structures/Video.ts
var _Video = class _Video {
constructor(data) {
this.nsfw = false;
this.shorts = false;
this.unlisted = false;
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
this._patch(data);
}
/**
* Patch raw data
* @private
* @ignore
*/
_patch(data) {
var _a, _b;
if (!data) data = {};
this.id = data.id || null;
this.title = data.title || null;
this.description = data.description || null;
this.durationFormatted = data.duration_raw || "0:00";
this.duration = (data.duration < 0 ? 0 : data.duration) || 0;
this.uploadedAt = data.uploadedAt || null;
this.views = parseInt(data.views) || 0;
this.thumbnail = new Thumbnail(data.thumbnail || {});
this.channel = new Channel(data.channel || {});
this.likes = ((_a = data.ratings) == null ? void 0 : _a.likes) || 0;
this.dislikes = ((_b = data.ratings) == null ? void 0 : _b.dislikes) || 0;
this.live = !!data.live;
this.private = !!data.private;
this.tags = data.tags || [];
this.nsfw = Boolean(data.nsfw);
this.unlisted = Boolean(data.unlisted);
this.shorts = Boolean(data.shorts);
this.music = data.music;
Object.defineProperty(this, "streamingData", {
enumerable: false,
configurable: true,
writable: true,
value: data.streamingData || null
});
Object.defineProperty(this, "videos", {
enumerable: false,
configurable: true,
writable: true,
value: data.videos || []
});
}
get formats() {
var _a;
return ((_a = this.streamingData) == null ? void 0 : _a.formats) || [];
}
get adaptiveFormats() {
var _a;
return ((_a = this.streamingData) == null ? void 0 : _a.adaptiveFormats) || [];
}
get url() {
if (!this.id) return null;
return `https://www.youtube.com/watch?v=${this.id}`;
}
get shortsURL() {
if (!this.shorts) return this.url;
return `https://www.youtube.com/shorts/${this.id}`;
}
/**
* YouTube video embed html
* @param {object} options Options
* @param {string} [options.id] DOM element id
* @param {number} [options.width] Iframe width
* @param {number} [options.height] Iframe height
*/
embedHTML(options = { id: "ytplayer", width: 640, height: 360 }) {
if (!this.id) return null;
return `<iframe title="__youtube_sr_frame__" id="${options.id || "ytplayer"}" type="text/html" width="${options.width || 640}" height="${options.height || 360}" src="${this.embedURL}" frameborder="0"></iframe>`;
}
/**
* Creates mix playlist url from this video
*/
createMixURL() {
return `${this.url}&list=RD${this.id}`;
}
/**
* YouTube video embed url
*/
get embedURL() {
if (!this.id) return null;
return `https://www.youtube.com/embed/${this.id}`;
}
get type() {
return "video";
}
toString() {
return this.url || "";
}
toJSON() {
const res = {
id: this.id,
url: this.url,
shorts_url: this.shortsURL,
title: this.title,
description: this.description,
duration: this.duration,
duration_formatted: this.durationFormatted,
uploadedAt: this.uploadedAt,
unlisted: this.unlisted,
nsfw: this.nsfw,
thumbnail: this.thumbnail.toJSON(),
channel: {
name: this.channel.name,
id: this.channel.id,
icon: this.channel.iconURL()
},
views: this.views,
type: this.type,
tags: this.tags,
ratings: {
likes: this.likes,
dislikes: this.dislikes
},
shorts: this.shorts,
live: this.live,
private: this.private,
music: this.music
};
return res;
}
};
__name(_Video, "Video");
var Video = _Video;
// src/Util.ts
var PLAYLIST_REGEX = /^https?:\/\/(www.)?youtube.com\/playlist\?list=((PL|FL|UU|LL|RD|OL)[a-zA-Z0-9-_]{16,41})$/;
var PLAYLIST_ID = /(PL|FL|UU|LL|RD|OL)[a-zA-Z0-9-_]{11,41}/;
var ALBUM_REGEX = /(RDC|O)LAK5uy_[a-zA-Z0-9-_]{33}/;
var VIDEO_URL = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/;
var VIDEO_ID = /^[a-zA-Z0-9-_]{11}$/;
var DEFAULT_INNERTUBE_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
var innertubeCache = null;
var __fetch;
var isNode = typeof process !== "undefined" && "node" in (process.versions || {});
var FETCH_LIBS = ["node-fetch", "cross-fetch", "undici"];
function getFetch() {
return __async(this, null, function* () {
if (typeof __fetch === "function") return __fetch;
if (typeof window !== "undefined" && "fetch" in window) return window.fetch;
if ("fetch" in globalThis) return globalThis.fetch;
for (const fetchLib of FETCH_LIBS) {
try {
const pkg = yield import(fetchLib);
const mod = pkg.fetch || pkg.default || pkg;
if (mod) return __fetch = mod;
} catch (e) {
}
}
if (isNode) throw new Error(`Could not resolve fetch library. Install one of ${FETCH_LIBS.map((m) => `"${m}"`).join(", ")} or define "fetch" in global scope!`);
throw new Error("Could not resolve fetch in global scope");
});
}
__name(getFetch, "getFetch");
var _Util = class _Util {
constructor() {
return _Util;
}
static innertubeKey() {
return __async(this, null, function* () {
if (innertubeCache) return innertubeCache;
return yield _Util.fetchInnerTubeKey();
});
}
static get VideoRegex() {
return VIDEO_URL;
}
static get VideoIDRegex() {
return VIDEO_ID;
}
static get AlbumRegex() {
return ALBUM_REGEX;
}
/**
* YouTube playlist URL Regex
* @type {RegExp}
*/
static get PlaylistURLRegex() {
return PLAYLIST_REGEX;
}
/**
* YouTube Playlist ID regex
* @type {RegExp}
*/
static get PlaylistIDRegex() {
return PLAYLIST_ID;
}
static fetchInnerTubeKey() {
return __async(this, null, function* () {
var _a, _b, _c;
const html = yield _Util.getHTML("https://www.youtube.com?hl=en");
const key = (_c = (_a = html.split('INNERTUBE_API_KEY":"')[1]) == null ? void 0 : _a.split('"')[0]) != null ? _c : (_b = html.split('innertubeApiKey":"')[1]) == null ? void 0 : _b.split('"')[0];
if (key) innertubeCache = key;
return key != null ? key : DEFAULT_INNERTUBE_KEY;
});
}
/**
* Parse HTML
* @param {string} url Website URL
* @param {RequestInit} [requestOptions] Request Options
* @returns {Promise<string>}
*/
static getHTML(url, requestOptions = {}) {
requestOptions = Object.assign(
{},
{
headers: Object.assign(
{},
{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:140.0) Gecko/20100101 Firefox/140.0"
},
(requestOptions == null ? void 0 : requestOptions.headers) || {}
)
},
requestOptions || {}
);
return new Promise((resolve, reject) => __async(null, null, function* () {
if (!__fetch) __fetch = yield getFetch();
__fetch(url, requestOptions).then((res) => {
if (!res.ok) throw new Error(`Rejected with status code: ${res.status}`);
return res.text();
}).then((html) => resolve(html)).catch((e) => reject(e));
}));
}
/**
* Returns duration in ms
* @param {string} duration Duration to parse
*/
static parseDuration(duration) {
duration != null ? duration : duration = "0:00";
const args = duration.split(":");
let dur = 0;
switch (args.length) {
case 3:
dur = parseInt(args[0]) * 60 * 60 * 1e3 + parseInt(args[1]) * 60 * 1e3 + parseInt(args[2]) * 1e3;
break;
case 2:
dur = parseInt(args[0]) * 60 * 1e3 + parseInt(args[1]) * 1e3;
break;
default:
dur = parseInt(args[0]) * 1e3;
}
return dur;
}
/**
* Parse items from html
* @param {string} html HTML
* @param options Options
*/
static parseSearchResult(html, options) {
if (!html) throw new Error("Invalid raw data");
if (!options) options = { type: "video", limit: 0 };
if (!options.type) options.type = "video";
let details = [];
let fetched = false;
try {
let data = html.split("ytInitialData = JSON.parse('")[1].split("');</script>")[0];
html = data.replace(/\\x([0-9A-F]{2})/gi, (...items) => {
return String.fromCharCode(parseInt(items[1], 16));
});
} catch (e) {
}
try {
details = JSON.parse(html.split('{"itemSectionRenderer":{"contents":')[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(',"continuations":[{')[0]);
fetched = true;
} catch (e) {
}
if (!fetched) {
try {
details = JSON.parse(html.split('{"itemSectionRenderer":')[html.split('{"itemSectionRenderer":').length - 1].split('},{"continuationItemRenderer":{')[0]).contents;
fetched = true;
} catch (e) {
}
}
if (!fetched) return [];
return Formatter.formatSearchResult(details, options);
}
/**
* Parse channel from raw data
* @param {object} data Raw data to parse video from
*/
static parseChannel(data) {
if (!data || !data.channelRenderer) return;
const badges = data.channelRenderer.ownerBadges;
let url = `https://www.youtube.com${data.channelRenderer.navigationEndpoint.browseEndpoint.canonicalBaseUrl || data.channelRenderer.navigationEndpoint.commandMetadata.webCommandMetadata.url}`;
let res = new Channel({
id: data.channelRenderer.channelId,
name: data.channelRenderer.title.simpleText,
icon: data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1],
url,
verified: !(badges == null ? void 0 : badges.length) ? false : badges.some((badge) => {
var _a, _b;
return badge["verifiedBadge"] || ((_b = (_a = badge == null ? void 0 : badge.metadataBadgeRenderer) == null ? void 0 : _a.style) == null ? void 0 : _b.toLowerCase().includes("verified"));
}),
subscribers: data.channelRenderer.subscriberCountText.simpleText
});
return res;
}
/**
* Parse video from raw data
* @param {object} data Raw data to parse video from
*/
static parseVideo(data) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E;
if (!data || !data.videoRenderer) return;
const badge = data.videoRenderer.ownerBadges && data.videoRenderer.ownerBadges[0];
let res = new Video({
id: data.videoRenderer.videoId,
url: `https://www.youtube.com/watch?v=${data.videoRenderer.videoId}`,
title: data.videoRenderer.title.runs[0].text,
description: data.videoRenderer.descriptionSnippet && data.videoRenderer.descriptionSnippet.runs[0] ? data.videoRenderer.descriptionSnippet.runs[0].text : "",
duration: data.videoRenderer.lengthText ? _Util.parseDuration(data.videoRenderer.lengthText.simpleText) : 0,
duration_raw: data.videoRenderer.lengthText ? data.videoRenderer.lengthText.simpleText : null,
thumbnail: {
id: data.videoRenderer.videoId,
url: data.videoRenderer.thumbnail.thumbnails[data.videoRenderer.thumbnail.thumbnails.length - 1].url,
height: data.videoRenderer.thumbnail.thumbnails[data.videoRenderer.thumbnail.thumbnails.length - 1].height,
width: data.videoRenderer.thumbnail.thumbnails[data.videoRenderer.thumbnail.thumbnails.length - 1].width
},
channel: {
id: data.videoRenderer.ownerText.runs[0].navigationEndpoint.browseEndpoint.browseId || null,
name: data.videoRenderer.ownerText.runs[0].text || null,
url: `https://www.youtube.com${data.videoRenderer.ownerText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl || data.videoRenderer.ownerText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`,
icon: {
url: ((_c = (_b = (_a = data.videoRenderer.channelThumbnail) == null ? void 0 : _a.thumbnails) == null ? void 0 : _b[0]) == null ? void 0 : _c.url) || ((_h = (_g = (_f = (_e = (_d = data.videoRenderer.channelThumbnailSupportedRenderers) == null ? void 0 : _d.channelThumbnailWithLinkRenderer) == null ? void 0 : _e.thumbnail) == null ? void 0 : _f.thumbnails) == null ? void 0 : _g[0]) == null ? void 0 : _h.url),
width: ((_k = (_j = (_i = data.videoRenderer.channelThumbnail) == null ? void 0 : _i.thumbnails) == null ? void 0 : _j[0]) == null ? void 0 : _k.width) || ((_p = (_o = (_n = (_m = (_l = data.videoRenderer.channelThumbnailSupportedRenderers) == null ? void 0 : _l.channelThumbnailWithLinkRenderer) == null ? void 0 : _m.thumbnail) == null ? void 0 : _n.thumbnails) == null ? void 0 : _o[0]) == null ? void 0 : _p.width),
height: ((_s = (_r = (_q = data.videoRenderer.channelThumbnail) == null ? void 0 : _q.thumbnails) == null ? void 0 : _r[0]) == null ? void 0 : _s.height) || ((_x = (_w = (_v = (_u = (_t = data.videoRenderer.channelThumbnailSupportedRenderers) == null ? void 0 : _t.channelThumbnailWithLinkRenderer) == null ? void 0 : _u.thumbnail) == null ? void 0 : _v.thumbnails) == null ? void 0 : _w[0]) == null ? void 0 : _x.height)
},
verified: Boolean((_z = (_y = badge == null ? void 0 : badge.metadataBadgeRenderer) == null ? void 0 : _y.style) == null ? void 0 : _z.toLowerCase().includes("verified"))
},
uploadedAt: (_B = (_A = data.videoRenderer.publishedTimeText) == null ? void 0 : _A.simpleText) != null ? _B : null,
views: (_E = (_D = (_C = data.videoRenderer.viewCountText) == null ? void 0 : _C.simpleText) == null ? void 0 : _D.replace(/[^0-9]/g, "")) != null ? _E : 0
});
return res;
}
static parsePlaylist(data) {
var _a, _b, _c;
if (!data.playlistRenderer) return;
const res = new Playlist(
{
id: data.playlistRenderer.playlistId,
title: data.playlistRenderer.title.simpleText,
thumbnail: {
id: data.playlistRenderer.playlistId,
url: data.playlistRenderer.thumbnails[0].thumbnails[data.playlistRenderer.thumbnails[0].thumbnails.length - 1].url,
height: data.playlistRenderer.thumbnails[0].thumbnails[data.playlistRenderer.thumbnails[0].thumbnails.length - 1].height,
width: data.playlistRenderer.thumbnails[0].thumbnails[data.playlistRenderer.thumbnails[0].thumbnails.length - 1].width
},
channel: {
id: data.playlistRenderer.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId,
name: data.playlistRenderer.shortBylineText.runs[0].text,
url: `https://www.youtube.com${((_a = data.playlistRenderer.shortBylineText.runs[0].navigationEndpoint.browseEndpoint) == null ? void 0 : _a.canonicalBaseUrl) || ((_c = (_b = data.playlistRenderer.shortBylineText.runs[0].navigationEndpoint.commandMetadata) == null ? void 0 : _b.webCommandMetadata) == null ? void 0 : _c.url)}`
},
videos: parseInt(data.playlistRenderer.videoCount.replace(/[^0-9]/g, ""))
},
true
);
return res;
}
static getPlaylistVideos(data, limit = Infinity) {
var _a, _b, _c, _d;
const videos = [];
for (let i = 0; i < data.length; i++) {
if (limit === videos.length) break;
const info = data[i].playlistVideoRenderer;
if (!info || !info.shortBylineText) continue;
videos.push(
new Video({
id: info.videoId,
index: parseInt((_a = info.index) == null ? void 0 : _a.simpleText) || 0,
duration: _Util.parseDuration((_b = info.lengthText) == null ? void 0 : _b.simpleText) || 0,
duration_raw: (_d = (_c = info.lengthText) == null ? void 0 : _c.simpleText) != null ? _d : "0:00",
thumbnail: {
id: info.videoId,
url: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].url,
height: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].height,
width: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].width
},
title: info.title.runs[0].text,
channel: {
id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || null,
name: info.shortBylineText.runs[0].text || null,
url: `https://www.youtube.com${info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl || info.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`,
icon: null
}
})
);
}
return videos;
}
static getPlaylist(html, limit) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
if (!limit || typeof limit !== "number") limit = 100;
if (limit <= 0) limit = Infinity;
let parsed;
let playlistDetails;
try {
const rawJSON = `${html.split('{"playlistVideoListRenderer":{"contents":')[1].split('}],"playlistId"')[0]}}]`;
parsed = JSON.parse(rawJSON);
playlistDetails = JSON.parse(html.split('{"playlistSidebarRenderer":')[1].split("}};</script>")[0]).items;
} catch (e) {
return null;
}
const API_KEY = (_d = (_c = (_a = html.split('INNERTUBE_API_KEY":"')[1]) == null ? void 0 : _a.split('"')[0]) != null ? _c : (_b = html.split('innertubeApiKey":"')[1]) == null ? void 0 : _b.split('"')[0]) != null ? _d : DEFAULT_INNERTUBE_KEY;
const videos = _Util.getPlaylistVideos(parsed, limit);
const data = playlistDetails[0].playlistSidebarPrimaryInfoRenderer;
if (!data.title.runs || !data.title.runs.length) return null;
const author = (_e = playlistDetails[1]) == null ? void 0 : _e.playlistSidebarSecondaryInfoRenderer.videoOwner;
const views = data.stats.length === 3 ? data.stats[1].simpleText.replace(/[^0-9]/g, "") : 0;
const lastUpdate = (_h = (_g = (_f = data.stats.find((x) => "runs" in x && x["runs"].find((y) => y.text.toLowerCase().includes("last update")))) == null ? void 0 : _f.runs.pop()) == null ? void 0 : _g.text) != null ? _h : null;
const videosCount = data.stats[0].runs[0].text.replace(/[^0-9]/g, "") || 0;
const res = new Playlist({
continuation: {
api: API_KEY,
token: _Util.getContinuationToken(parsed),
clientVersion: (_l = (_k = (_i = html.split('"INNERTUBE_CONTEXT_CLIENT_VERSION":"')[1]) == null ? void 0 : _i.split('"')[0]) != null ? _k : (_j = html.split('"innertube_context_client_version":"')[1]) == null ? void 0 : _j.split('"')[0]) != null ? _l : "<some version>"
},
id: data.title.runs[0].navigationEndpoint.watchEndpoint.playlistId,
title: data.title.runs[0].text,
videoCount: parseInt(videosCount) || 0,
lastUpdate,
views: parseInt(views) || 0,
videos,
url: `https://www.youtube.com/playlist?list=${data.title.runs[0].navigationEndpoint.watchEndpoint.playlistId}`,
link: `https://www.youtube.com${data.title.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`,
author: author ? {
name: author.videoOwnerRenderer.title.runs[0].text,
id: author.videoOwnerRenderer.title.runs[0].navigationEndpoint.browseEndpoint.browseId,
url: `https://www.youtube.com${author.videoOwnerRenderer.navigationEndpoint.commandMetadata.webCommandMetadata.url || author.videoOwnerRenderer.navigationEndpoint.browseEndpoint.canonicalBaseUrl}`,
icon: author.videoOwnerRenderer.thumbnail.thumbnails.length ? (_m = author.videoOwnerRenderer.thumbnail.thumbnails.pop()) == null ? void 0 : _m.url : null
} : {},
thumbnail: ((_n = data.thumbnailRenderer.playlistVideoThumbnailRenderer) == null ? void 0 : _n.thumbnail.thumbnails.length) ? data.thumbnailRenderer.playlistVideoThumbnailRenderer.thumbnail.thumbnails.pop() : null
});
return res;
}
static getContinuationToken(ctx) {
var _a, _b, _c;
const continuationToken = (_c = (_b = (_a = ctx.find((x) => Object.keys(x)[0] === "continuationItemRenderer")) == null ? void 0 : _a.continuationItemRenderer.continuationEndpoint) == null ? void 0 : _b.continuationCommand) == null ? void 0 : _c.token;
return continuationToken;
}
static getVideo(html) {
var _a, _b, _c, _d, _e, _f, _g, _h;
let data, nextData = {};
try {
const parsed = JSON.parse(html.split("var ytInitialData = ")[1].split(";</script>")[0]);
data = parsed.contents.twoColumnWatchNextResults.results.results.contents;
try {
nextData = parsed.contents.twoColumnWatchNextResults.secondaryResults.secondaryResults.results;
} catch (e) {
}
} catch (e) {
throw new Error("Could not parse video metadata!");
}
const raw = {
primary: ((_a = data == null ? void 0 : data.find((section) => "videoPrimaryInfoRenderer" in section)) == null ? void 0 : _a.videoPrimaryInfoRenderer) || {},
secondary: ((_b = data == null ? void 0 : data.find((section) => "videoSecondaryInfoRenderer" in section)) == null ? void 0 : _b.videoSecondaryInfoRenderer) || {}
};
let info;
try {
info = JSON.parse(html.split("var ytInitialPlayerResponse = ")[1].split(";</script>")[0]);
} catch (e) {
info = JSON.parse(html.split("var ytInitialPlayerResponse = ")[1].split(";var")[0]);
}
if (!(info == null ? void 0 : info.videoDetails)) return null;
info = __spreadProps(__spreadValues(__spreadValues({}, raw.primary), raw.secondary), {
info
});
let musicInfo = [];
try {
const jsonData = html.split('{"horizontalCardListRenderer":')[1].split(',{"reelShelfRenderer"')[0];
musicInfo = JSON.parse('{"horizontalCardListRenderer":' + jsonData).horizontalCardListRenderer.cards.map((val) => {
return {
title: val.videoAttributeViewModel.title,
cover: val.videoAttributeViewModel.image.sources[0].url,
artist: val.videoAttributeViewModel.subtitle,
album: val.videoAttributeViewModel.secondarySubtitle.content
};
});
} catch (e) {
musicInfo = [];
}
const payload = new Video({
id: info.info.videoDetails.videoId,
title: info.info.videoDetails.title,
views: parseInt(info.info.videoDetails.viewCount) || 0,
tags: info.info.videoDetails.keywords,
private: info.info.videoDetails.isPrivate,
unlisted: !!((_d = (_c = info.info.microformat) == null ? void 0 : _c.playerMicroformatRenderer) == null ? void 0 : _d.isUnlisted),
nsfw: ((_f = (_e = info.info.microformat) == null ? void 0 : _e.playerMicroformatRenderer) == null ? void 0 : _f.isFamilySafe) === false,
live: info.info.videoDetails.isLiveContent,
duration: parseInt(info.info.videoDetails.lengthSeconds) * 1e3,
shorts: [`{"webCommandMetadata":{"url":"/shorts/${info.info.videoDetails.videoId}"`, `{window['ytPageType'] = "shorts";`, `"/hashtag/shorts"`].some((r) => html.includes(r)),
duration_raw: _Util.durationString(_Util.parseMS(parseInt(info.info.videoDetails.lengthSeconds) * 1e3 || 0)),
channel: {
name: info.info.videoDetails.author,
id: info.info.videoDetails.channelId,
url: `https://www.youtube.com${info.owner.videoOwnerRenderer.title.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl}`,
icon: info.owner.videoOwnerRenderer.thumbnail.thumbnails[0],
subscribers: (_h = (_g = info.owner.videoOwnerRenderer.subscriberCountText) == null ? void 0 : _g.simpleText) == null ? void 0 : _h.replace(" subscribers", "")
},
description: info.info.videoDetails.shortDescription,
thumbnail: __spreadProps(__spreadValues({}, info.info.videoDetails.thumbnail.thumbnails[info.info.videoDetails.thumbnail.thumbnails.length - 1]), {
id: info.info.videoDetails.videoId
}),
uploadedAt: info.dateText.simpleText,
ratings: {
likes: this.getInfoLikesCount(info) || 0,
dislikes: 0
},
videos: _Util.getNext(nextData != null ? nextData : {}) || [],
streamingData: info.info.streamingData || null,
music: musicInfo
});
return payload;
}
static getInfoLikesCount(info) {
var _a;
const buttons = info.videoActions.menuRenderer.topLevelButtons;
const button = buttons.find((button2) => {
var _a2;
return ((_a2 = button2.toggleButtonRenderer) == null ? void 0 : _a2.defaultIcon.iconType) === "LIKE";
});
if (!button) return 0;
return parseInt((_a = button.toggleButtonRenderer.defaultText.accessibility) == null ? void 0 : _a.accessibilityData.label.split(" ")[0].replace(/,/g, ""));
}
static getNext(body, home = false) {
var _a, _b, _c, _d, _e;
const results = [];
if (typeof body[Symbol.iterator] !== "function") return results;
for (const result of body) {
const details = home ? result : result.compactVideoRenderer;
if (details) {
try {
let viewCount = details.viewCountText.simpleText;
viewCount = (/^\d/.test(viewCount) ? viewCount : "0").split(" ")[0];
results.push(
new Video({
id: details.videoId,
title: (_b = details.title.simpleText) != null ? _b : (_a = details.title.runs[0]) == null ? void 0 : _a.text,
views: parseInt(viewCount.replace(/,/g, "")) || 0,
duration_raw: (_c = details.lengthText.simpleText) != null ? _c : details.lengthText.accessibility.accessibilityData.label,
duration: _Util.parseDuration(details.lengthText.simpleText) / 1e3,
channel: {
name: details.shortBylineText.runs[0].text,
id: details.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId,
url: `https://www.youtube.com${details.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl}`,
icon: home ? details.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail.thumbnails[0] : details.channelThumbnail.thumbnails[0],
subscribers: "0",
verified: Boolean(details.ownerBadges[0].metadataBadgeRenderer.tooltip === "Verified")
},
thumbnail: __spreadProps(__spreadValues({}, details.thumbnail.thumbnails[details.thumbnail.thumbnails.length - 1]), {
id: details.videoId
}),
uploadedAt: details.publishedTimeText.simpleText,
ratings: {
likes: 0,
dislikes: 0
},
description: (_e = (_d = details.descriptionSnippet) == null ? void 0 : _d.runs[0]) == null ? void 0 : _e.text
})
);
} catch (e) {
continue;
}
}
}
return results;
}
static getMix(html) {
var _a, _b;
let data = null;
try {
const parsed = JSON.parse(html.split("var ytInitialData = ")[1].split(";</script>")[0]);
data = parsed.contents.twoColumnWatchNextResults.playlist.playlist;
} catch (e) {
}
if (!data) return null;
const videos = data.contents.map((m) => {
const t = m.playlistPanelVideoRenderer;
return new Video({
id: t.videoId,
title: t.title.simpleText,
thumbnail: {
id: t.videoId,
url: t.thumbnail.thumbnails[t.thumbnail.thumbnails.length - 1].url,
height: t.thumbnail.thumbnails[t.thumbnail.thumbnails.length - 1].height,
width: t.thumbnail.thumbnails[t.thumbnail.thumbnails.length - 1].width
},
duration: _Util.parseDuration(t.lengthText.simpleText),
duration_raw: t.lengthText.simpleText,
channel: {
name: t.shortBylineText.runs[0].text,
id: t.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId,
url: `https://www.youtube.com${t.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl}`,
icon: null
}
});
});
const list = new Playlist(
{
id: data.playlistId,
title: data.title,
videoCount: data.contents.length,
videos,
link: data.playlistShareUrl,
url: data.playlistShareUrl,
thumbnail: ((_b = (_a = videos[0]) == null ? void 0 : _a.thumbnail) == null ? void 0 : _b.toJSON()) || null,
channel: {
name: data.ownerName.simpleText
},
mix: true
},
true
);
return list;
}
static parseHomepage(html) {
let contents;
try {
contents = html.split("var ytInitialData = ")[1].split(";</script>")[0];
contents = JSON.parse(contents).contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.richGridRenderer.contents;
} catch (e) {
return [];
}
if (!contents || !contents.length || !contents.find((x) => Object.keys(x)[0] === "richItemRenderer")) return [];
contents = contents.filter((a) => Object.keys(a)[0] === "richItemRenderer").map((m) => m.richItemRenderer.content.videoRenderer);
return _Util.getNext(contents, true);
}
static getPlaylistURL(url) {
if (typeof url !== "string") return null;
const group = PLAYLIST_ID.exec(url) || ALBUM_REGEX.exec(url);
if (!group) return null;
if (group[0].startsWith("RD") && !ALBUM_REGEX.exec(group[0])) return { url, mix: true };
const finalURL = `https://www.youtube.com/playlist?list=${group[0]}`;
return { url: finalURL, mix: false };
}
static validatePlaylist(url) {
if (typeof url === "string" && (url.match(PLAYLIST_ID) !== null || url.match(ALBUM_REGEX) !== null)) return;
throw new Error("Invalid playlist url");
}
static filter(ftype) {
switch (ftype) {
case "playlist":
return "EgIQAw%253D%253D";
case "video":
return "EgIQAQ%253D%253D";
case "channel":
return "EgIQAg%253D%253D";
case "film":
return "EgIQBA%253D%253D";
default:
throw new TypeError(`Invalid filter type "${ftype}"!`);
}
}
static parseMS(milliseconds) {
return {
days: Math.trunc(milliseconds / 864e5),
hours: Math.trunc(milliseconds / 36e5) % 24,
minutes: Math.trunc(milliseconds / 6e4) % 60,
seconds: Math.trunc(milliseconds / 1e3) % 60
};
}
static durationString(data) {
const items = Object.keys(data);
const required = ["days", "hours", "minutes", "seconds"];
const parsed = items.filter((x) => required.includes(x)).map((m) => data[m] > 0 ? data[m] : "");
const final = parsed.slice(parsed.findIndex((x) => !!x)).map((x, i) => i == 0 ? x.toString() : x.toString().padStart(2, "0")).join(":");
return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final;
}
static json(data) {
try {
return JSON.parse(data);
} catch (e) {
return null;
}
}
static makeRequest() {
return __async(this, arguments, function* (url = "", data = { data: {}, requestOptions: {} }) {
const key = yield _Util.innertubeKey();
const res = yield _Util.getHTML(`https://youtube.com/youtubei/v1${url}?key=${key}`, __spreadValues({
method: "POST",
headers: {
"Content-Type": "application/json",
Host: "www.youtube.com",
Referer: "https://www.youtube.com"
},
body: JSON.stringify(__spreadValues({
context: __spreadValues({
client: __spreadValues({
utcOffsetMinutes: 0,
gl: "US",
hl: "en",
clientName: "WEB",
clientVersion: "1.20220406.00.00",
originalUrl: "https://www.youtube.com/"
}, data.clientCtx || {})
}, data.ctx || {})
}, data.data || {}))
}, data.requestOptions || {}));
return _Util.json(res);
});
}
};
__name(_Util, "Util");
var Util = _Util;
var Util_default = Util;
// src/mod.ts
var SAFE_SEARCH_COOKIE = "PREF=f2=8000000";
var TrendingFilter = {
MUSIC: "4gINGgt5dG1hX2NoYXJ0cw%3D%3D",
GAMING: "4gIcGhpnYW1pbmdfY29ycHVzX21vc3RfcG9wdWxhcg%3D%3D",
MOVIES: "4gIKGgh0cmFpbGVycw%3D%3D"
};
var _YouTube = class _YouTube {
constructor() {
return _YouTube;
}
static search(query, options) {
return __async(this, null, function* () {
if (!options) options = { limit: 100, type: "video", requestOptions: {} };
if (!query || typeof query !== "string") throw new Error(`Invalid search query "${query}"!`);
options.type = options.type || "video";
const requestOptions = options.safeSearch ? __spreadProps(__spreadValues({}, options.requestOptions), { headers: { cookie: SAFE_SEARCH_COOKIE } }) : {};
try {
const filter = options.type === "all" ? null : Util_default.filter(options.type);
const res = yield Util_default.makeRequest("/search", {
data: {
params: filter,
query
},
clientCtx: {
originalUrl: `https://youtube.com/results?search_query=${encodeURIComponent(query.trim()).replace(/%20/g, "+")}${filter}`
},
requestOptions
});
return Formatter.formatSearchResult(res.contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents[0].itemSectionRenderer.contents, options);
} catch (e) {
const filter = options.type === "all" ? "" : `&sp=${Util_default.filter(options.type)}`;
const url = `https://youtube.com/results?search_query=${encodeURIComponent(query.trim()).replace(/%20/g, "+")}&hl=en${filter}`;
const html = yield Util_default.getHTML(url, requestOptions);
return Util_default.parseSearchResult(html, options);
}
});
}
static searchOne(query, type, safeSearch, requestOptions) {
if (!type) type = "video";
return new Promise((resolve) => {
_YouTube.search(query, { limit: 1, type, requestOptions, safeSearch: Boolean(safeSearch) }).then((res) => {
if (!res || !res.length) return resolve(null);
resolve(res[0]);
}).catch(() => {
resolve(null);
});
});
}
/**
* Returns playlist details
* @param {string} url Playlist URL
* @param {object} [options] Options
* @param {number} [options.limit=100] Playlist video limit
* @param {RequestInit} [options.requestOptions] Request Options
*/
static getPlaylist(url, options) {
return __async(this, null, function* () {
var _a;
if (!options) options = { limit: 100, requestOptions: {}, fetchAll: false };
if (!url || typeof url !== "string") throw new Error(`Expected playlist url, received ${typeof url}!`);
Util_default.validatePlaylist(url);
const resolved = Util_default.getPlaylistURL(url);
if (!resolved) return null;
url = resolved.url;
const html = yield Util_default.getHTML(`${url}&hl=en`, options && options.requestOptions);
const res = resolved.mix ? Util_default.getMix(html) : Util_default.getPlaylist(html, options && options.limit);
try {
if (!res && resolved.mix) {
const videoId = new URL(url).searchParams.get("v");
if (videoId) {
const vid = yield _YouTube.getVideo(`https://www.youtube.com/watch?v=${videoId}`).catch(() => null);
if (vid) {
return new Playlist(
{
id: vid.id,
title: `Mix - ${vid.title}`,
videoCount: 1,
lastUpdate: null,
views: vid.views,
url: `https://www.youtube.com/watch?v=${vid.id}`,
link: `https://www.youtube.com/watch?v=${vid.id}`,
channel: {
name: "youtube-sr"
},
thumbnail: ((_a = vid.thumbnail) == null ? void 0 : _a.toJSON()) || null,
videos: [vid],
mix: true,
fake: true
},
true
);
}
}
}
} catch (e) {
}
if (res && res instanceof Playlist && options.fetchAll) {
return yield res.fetch(options && options.limit).catch(() => res);
}
return res;
});
}
/**
* Returns basic video info
* @param url Video url to parse
* @param requestOptions Request options
*/
static getVideo(url, requestOptions) {
return __async(this, null, function* () {
if (!url) throw new Error("Missing video url");
if (url instanceof Video) url = url.url;
const isValid = _YouTube.validate(url, "VIDEO");
if (!isValid) throw new Error("Invalid video url");
const html = yield Util_default.getHTML(`${url}&hl=en`, requestOptions);
return Util_default.getVideo(html);
});
}
/**
* Fetches homepage videos
*/
static homepage() {
return __async(this, null, function* () {
const html = yield Util_default.getHTML("https://www.youtube.com?hl=en");
return Util_default.parseHomepage(html);
});
}
/**
* Attempts to parse `INNERTUBE_API_KEY`
*/
static fetchInnerTubeKey() {
return __async(this, null, function* () {
return Util_default.fetchInnerTubeKey();
});
}
static trending(options) {
return __async(this, null, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
const type = TrendingFilter[options == null ? void 0 : options.type];
const html = yield Util_default.getHTML(`https://www.youtube.com/feed/trending?${type ? "bp=" + type + "&hl=en" : "hl=en"}`);
let json;
try {
json = JSON.parse(html.split("var ytInitialData =")[1].split(";</script>")[0]);
} catch (e) {
return [];
}
const content = (_k = (_j = (_i = (_h = (_g = (_f = (_e = (_d = (_c = (_b = (_a = json.contents) == null ? void 0 : _a.twoColumnBrowseResultsRenderer) == null ? void 0 : _b.tabs[0].tabRenderer) == null ? void 0 : _c.content) == null ? void 0 : _d.sectionListRenderer) == null ? void 0 : _e.contents[1]) == null ? void 0 : _f.itemSectionRenderer) == null ? void 0 : _g.contents[0]) == null ? void 0 : _h.shelfRenderer) == null ? void 0 : _i.content) == null ? void 0 : _j.expandedShelfContentsRenderer) == null ? void 0 : _k.items;
if (!content || !Array.isArray(content)) return [];
const res = [];
for (const item of content.map((m) => m.videoRenderer)) {
res.push(
new Video({
title: item.title.runs[0].text,
id: item.videoId,
url: `https://www.youtube.com/watch?v=${item.videoId}`,
description: (_m = (_l = item.descriptionSnippet) == null ? void 0 : _l.runs[0])