diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 809e808..049d522 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -9,7 +9,18 @@ import { prismjsPlugin } from '@vuepress/plugin-prismjs'; const __dirname = getDirname(import.meta.url) export default defineUserConfig({ - lang: 'en-US', + locales: { + '/': { + lang: 'en-US', + title: 'YouTube.js', + description: 'A JavaScript client for YouTube\'s private API, known as InnerTube.', + }, + '/zh/': { + lang: 'zh-CN', + title: 'YouTube.js', + description: 'YouTube 私有 API(InnerTube)的 JavaScript 客户端', + }, + }, head: [ ['link', { rel: 'icon', href: '/images/favicon/favicon-32x32.png' }], ['link', { rel: 'icon', href: '/images/favicon/favicon-16x16.png' }], @@ -25,8 +36,6 @@ export default defineUserConfig({ ['meta', { property: 'og:locale', content: 'en_US' }], ['meta', { property: 'og:image', content: '/images/logo.png' }], ], - title: 'YouTube.js', - description: 'A JavaScript client for YouTube\'s private API, known as InnerTube.', clientConfigFile: path.resolve(__dirname, 'client.js'), theme: defaultTheme({ docsRepo: 'https://github.com/LuanRT/ytjs-docs', @@ -34,71 +43,145 @@ export default defineUserConfig({ docsDir: 'docs', contributors: false, repo: 'https://github.com/LuanRT/YouTube.js', - navbar: [ - { - text: 'Guide', - link: '/guide/' - }, - { - text: 'Discord', - link: 'https://discord.gg/syDu7Yks54' - }, - { - text: '❤️ Sponsor', - link: 'https://github.com/sponsors/LuanRT', - }, - ], - colorModeSwitch: true, - sidebar: { - '/guide/': [ - { - title: 'Guide', - collapsable: false, - children: [ - { - link: '/guide/', - text: 'Introduction' - }, - { - link: '/guide/getting-started', - text: 'Getting Started' - }, - { - link: '/guide/browser-usage', - text: 'Browser Usage' - }, - { - link: '/guide/caching', - text: 'Caching' - }, + locales: { + '/': { + selectLanguageName: 'English', + editLinkText: 'Edit this page', + navbar: [ + { + text: 'Guide', + link: '/guide/' + }, + { + text: 'Discord', + link: 'https://discord.gg/syDu7Yks54' + }, + { + text: '❤️ Sponsor', + link: 'https://github.com/sponsors/LuanRT', + }, + ], + sidebar: { + '/guide/': [ { - link: '/guide/proxies', - text: 'Proxies' - }, - { - link: '/guide/authentication', - text: 'Authentication' - }, - { - link: '/guide/advanced-usage', - text: 'Advanced Usage' - }, - { - link: '/guide/troubleshooting', - text: 'Troubleshooting' - }, + title: 'Guide', + collapsable: false, + children: [ + { + link: '/guide/', + text: 'Introduction' + }, + { + link: '/guide/getting-started', + text: 'Getting Started' + }, + { + link: '/guide/browser-usage', + text: 'Browser Usage' + }, + { + link: '/guide/caching', + text: 'Caching' + }, + { + link: '/guide/proxies', + text: 'Proxies' + }, + { + link: '/guide/authentication', + text: 'Authentication' + }, + { + link: '/guide/advanced-usage', + text: 'Advanced Usage' + }, + { + link: '/guide/troubleshooting', + text: 'Troubleshooting' + }, + { + link: '/guide/faq', + text: 'FAQ' + }, + ] + } + ], + '/api/': [ + ], + '/googlevideo/': [ + ] + } + }, + '/zh/': { + selectLanguageName: '简体中文', + editLinkText: '编辑此页', + navbar: [ + { + text: '指南', + link: '/zh/guide/' + }, + { + text: 'Discord', + link: 'https://discord.gg/syDu7Yks54' + }, + { + text: '❤️ 赞助', + link: 'https://github.com/sponsors/LuanRT', + }, + ], + sidebar: { + '/zh/guide/': [ { - link: '/guide/faq', - text: 'FAQ' - }, + title: '指南', + collapsable: false, + children: [ + { + link: '/zh/guide/', + text: '介绍' + }, + { + link: '/zh/guide/getting-started', + text: '快速开始' + }, + { + link: '/zh/guide/browser-usage', + text: '浏览器使用' + }, + { + link: '/zh/guide/caching', + text: '缓存' + }, + { + link: '/zh/guide/proxies', + text: '代理' + }, + { + link: '/zh/guide/authentication', + text: '身份验证' + }, + { + link: '/zh/guide/advanced-usage', + text: '高级用法' + }, + { + link: '/zh/guide/troubleshooting', + text: '故障排除' + }, + { + link: '/zh/guide/faq', + text: '常见问题' + }, + ] + } + ], + '/zh/api/': [ + ], + '/zh/googlevideo/': [ ] } - ], - '/api/': [ - ], - '/googlevideo/': [ - ] - } + } + }, + colorModeSwitch: true, }), plugins: [ prismjsPlugin({ @@ -113,6 +196,9 @@ export default defineUserConfig({ '/': { placeholder: 'Search', }, + '/zh/': { + placeholder: '搜索', + }, }, }), ], diff --git a/docs/zh/README.md b/docs/zh/README.md new file mode 100644 index 0000000..ff07fde --- /dev/null +++ b/docs/zh/README.md @@ -0,0 +1,84 @@ +--- +home: true +title: 首页 +heroText: YouTube.js +heroImage: '/images/logo.png' +tagline: YouTube 私有 API 的 JavaScript 客户端 +actions: + - text: 快速开始 → + link: /zh/guide/ + type: primary + - text: API 文档 + link: /zh/api/ + type: secondary +features: + - title: 🚀 强大且灵活 + details: 无限制访问 YouTube 数据。从简单搜索到复杂操作,应有尽有。 + - title: 🛡️ 无需 API 密钥 + details: 无需 API 密钥或配额限制。像 YouTube 网站一样使用私有 API。 + - title: 🔌 平台无关 + details: 兼容 Node.js、Deno 和现代浏览器。在任何 JavaScript 运行环境中使用。 +footer: MIT 许可证 | 版权所有 © LuanRT +--- + +## 生态系统 + +
+
+

GoogleVideo

+

用于处理 YouTube 专有视频流协议(UMP/SABR)的模块集合。

+ 查看文档 → +
+ +
+

扩展

+

为 YouTube.js 添加额外功能,满足特定使用场景。

+ 了解更多 → +
+
+ + diff --git a/docs/zh/api/README.md b/docs/zh/api/README.md new file mode 100644 index 0000000..1af4526 --- /dev/null +++ b/docs/zh/api/README.md @@ -0,0 +1,30 @@ +# API 文档 + +API 技术文档主要包含代码、类型定义和接口说明,这些内容使用英文是开发者的标准实践。 + +为了保持文档的准确性和及时性,API 参考文档暂未提供中文翻译。 + +**请切换到英文版查看完整的 API 文档:** + +👉 [查看英文 API 文档](/api/) + +--- + +## 快速链接 + +- [Innertube 类](/api/classes/Innertube.md) - 主要的客户端类 +- [Actions 类](/api/classes/Actions.md) - 执行 InnerTube 操作 +- [Session 类](/api/classes/Session.md) - 会话管理 +- [Parser 命名空间](/api/youtubei.js/namespaces/Parser/README.md) - 响应解析器 +- [YTNodes 命名空间](/api/youtubei.js/namespaces/YTNodes/README.md) - YouTube 节点类型 + +## 主要命名空间 + +- [YT](/api/youtubei.js/namespaces/YT/README.md) - YouTube 相关类型 +- [YTMusic](/api/youtubei.js/namespaces/YTMusic/README.md) - YouTube Music 相关类型 +- [YTKids](/api/youtubei.js/namespaces/YTKids/README.md) - YouTube Kids 相关类型 +- [YTShorts](/api/youtubei.js/namespaces/YTShorts/README.md) - YouTube Shorts 相关类型 + +--- + +如有疑问,请参考[指南部分](/zh/guide/)或加入我们的 [Discord 社区](https://discord.gg/syDu7Yks54)。 diff --git a/docs/zh/googlevideo/README.md b/docs/zh/googlevideo/README.md new file mode 100644 index 0000000..383de59 --- /dev/null +++ b/docs/zh/googlevideo/README.md @@ -0,0 +1,118 @@ +# GoogleVideo + +用于处理 YouTube 专有视频流协议(UMP/SABR)的模块集合。可用于构建客户端或与现有媒体播放器集成(例如 [Shaka Player](https://shaka-player-demo.appspot.com/docs/api/index.html))。 + +[API 参考 →](/googlevideo/api/) + +[代码仓库](https://github.com/LuanRT/googlevideo) + +## 安装 + +```bash +# NPM +npm install googlevideo + +# JSR / Deno +npx jsr add @luanrt/googlevideo +deno add jsr:@luanrt/googlevideo + +# GitHub +npm install LuanRT/googlevideo +``` + +## 基本用法 + +以下是使用 UMP 模块创建缓冲区、写入部分并处理它们的基本示例: + +```typescript +import { CompositeBuffer, UmpReader, UmpWriter } from 'googlevideo/ump'; +import { MediaHeader, UMPPartId } from 'googlevideo/protos'; +import { concatenateChunks } from 'googlevideo/utils'; +import { Part } from 'googlevideo/shared-types'; + +function handleMediaHeader(part: Part) { + const mediaHeader = MediaHeader.decode(concatenateChunks(part.data.chunks)); + console.log('Media Header:', mediaHeader); +} + +function handleMedia(part: Part) { + const headerId = part.data.getUint8(0); + console.log(`Media Part (Associated Header ID: ${headerId}):`, part.data.split(1).remainingBuffer.getLength(), 'bytes'); +} + +function handleMediaEnd(part: Part) { + const headerId = part.data.getUint8(0); + console.log(`Media End Part (Associated Header ID: ${headerId}):`, part.data.split(1).remainingBuffer.getLength(), 'bytes'); +} + +const umpPartHandlers = new Map void>([ + [ UMPPartId.MEDIA_HEADER, handleMediaHeader ], + [ UMPPartId.MEDIA, handleMedia ], + [ UMPPartId.MEDIA_END, handleMediaEnd ] +]); + +const buffer = mockUmpData(); +const reader = new UmpReader(buffer); + +reader.read((part) => { + const handler = umpPartHandlers.get(part.type); + if (handler) { + handler(part); + } else { + console.warn(`No handler for part type: ${part.type}`); + } +}); + +/** + * 生成包含 MEDIA_HEADER 以及相应的 MEDIA 和 MEDIA_END 部分的模拟 UMP 数据缓冲区。 + * 此组表示单个音频段,这是你在真实 UMP 流中通常会看到的内容。 + */ +function mockUmpData(): CompositeBuffer { + const buffer = new CompositeBuffer(); + const writer = new UmpWriter(buffer); + + const audioHeaderId = 0; + + const partsToWrite: [UMPPartId, Uint8Array][] = [ + [ + UMPPartId.MEDIA_HEADER, + MediaHeader.encode({ + headerId: audioHeaderId, + videoId: "sOa4VVlI9tE", + itag: 141, + lmt: 1645502668395260, + xtags: "", + startRange: 5463800, + isInitSeg: false, + sequenceNumber: 0, + durationMs: 0, + formatId: { + itag: 141, + lastModified: 1645502668395260, + xtags: "" + }, + contentLength: 963966, + }).finish() + ], + [ UMPPartId.MEDIA, new Uint8Array([ audioHeaderId, ...new Uint8Array(827609).fill(0) ]) ], + [ UMPPartId.MEDIA, new Uint8Array([ audioHeaderId, ...new Uint8Array(136357).fill(0) ]) ], + [ UMPPartId.MEDIA_END, new Uint8Array([ audioHeaderId ]) ] + ]; + + for (const [type, data] of partsToWrite) { + writer.write(type, data); + } + + return buffer; +} +``` + +预期输出: +``` +Media Header: { ... } +Media Part (Associated Header ID: 0): 827609 bytes +Media Part (Associated Header ID: 0): 136357 bytes +Media End Part (Associated Header ID: 0): 0 bytes +``` + +> 更多高级用法示例可以在 [examples](https://github.com/LuanRT/googlevideo/tree/main/examples) 目录中找到。 diff --git a/docs/zh/guide/README.md b/docs/zh/guide/README.md new file mode 100644 index 0000000..6769a65 --- /dev/null +++ b/docs/zh/guide/README.md @@ -0,0 +1,7 @@ +# 介绍 + +YouTube.js 是 YouTube 私有 API(称为 "InnerTube")的 JavaScript 客户端。它允许你以编程方式与 YouTube 交互,提供对视频、评论、直播聊天、流媒体数据等的访问。支持 Node.js、Deno、浏览器以及任何其他现代 JavaScript 环境。 + +[Discord](https://discord.gg/syDu7Yks54) | [GitHub](https://github.com/LuanRT/YouTube.js) | [NPM](https://www.npmjs.com/package/youtubei.js) + +**免责声明**:本库与 YouTube 或 Google 无关联。 diff --git a/docs/zh/guide/advanced-usage.md b/docs/zh/guide/advanced-usage.md new file mode 100644 index 0000000..c270a4d --- /dev/null +++ b/docs/zh/guide/advanced-usage.md @@ -0,0 +1,79 @@ +## 扩展库 + +YouTube.js 是模块化的,易于扩展。内部使用的大多数方法、类和实用程序都是公开的,可以用来实现你自己的扩展,而无需修改库的源代码。 + +例如,假设我们想实现一个检索视频信息的方法。我们可以使用 `Actions` 类的实例来做到这一点: +```ts +import { Innertube, UniversalCache } from 'youtubei.js'; + +const yt = await Innertube.create({ cache: new UniversalCache(true) }); + +async function getVideoInfo(videoId: string) { + const videoInfo = await yt.actions.execute('/player', { + // 你可以在这里添加任何额外的负载,它们将与发送到 InnerTube 的默认负载合并。 + videoId, + client: 'YTMUSIC', // 要使用的 InnerTube 客户端。 + parse: true // 告诉 YouTube.js 解析响应(不发送到 InnerTube)。 + }); + + return videoInfo; +} + +const videoInfo = await getVideoInfo('jLTOuvBTLxA'); +console.info(videoInfo); +``` + +或者,假设我们在解析的响应中找到一个 `NavigationEndpoint`(例如,一个按钮)。我们可以像这样轻松调用它: +```ts +import { Innertube, UniversalCache, YTNodes } from 'youtubei.js'; + +const yt = await Innertube.create({ cache: new UniversalCache(true) }); + +const artist = await yt.music.getArtist('UC52ZqHVQz5OoGhvbWiRal6g'); +const albums = artist.sections[1].as(YTNodes.MusicCarouselShelf); + +// 假设我们想点击"更多"按钮: +const button = albums.as(YTNodes.MusicCarouselShelf).header?.more_content; + +if (button) { + // 确保它存在后,我们可以使用以下代码调用其导航端点: + const page = await button.endpoint.call(yt.actions, { parse: true }); + console.info(page); +} +``` + +## 使用解析器 + +YouTube.js 的解析器使你能够解析 InnerTube 响应并将其节点转换为易于操作的强类型对象。此外,它还提供了许多实用方法。 + +这是一个例子: +```ts +// 参见 ./examples/parser + +import { Parser, YTNodes } from 'youtubei.js'; +import { readFileSync } from 'fs'; + +// YouTube Music 的艺术家页面响应 +const data = readFileSync('./artist.json').toString(); + +const page = Parser.parseResponse(JSON.parse(data)); + +const header = page.header?.item().as(YTNodes.MusicImmersiveHeader, YTNodes.MusicVisualHeader); + +console.info('Header:', header); + +// 解析器使用代理对象为处理 InnerTube 的数据数组添加类型安全和实用方法: +const tab = page.contents?.item().as(YTNodes.SingleColumnBrowseResults).tabs.firstOfType(YTNodes.Tab); + +if (!tab) + throw new Error('未找到目标选项卡'); + +if (!tab.content) + throw new Error('目标选项卡似乎为空'); + +const sections = tab.content?.as(YTNodes.SectionList).contents.as(YTNodes.MusicCarouselShelf, YTNodes.MusicDescriptionShelf, YTNodes.MusicShelf); + +console.info('Sections:', sections); +``` + +解析器的文档可以在[这里](https://github.com/LuanRT/YouTube.js/blob/main/src/parser)找到。 diff --git a/docs/zh/guide/authentication.md b/docs/zh/guide/authentication.md new file mode 100644 index 0000000..4a00068 --- /dev/null +++ b/docs/zh/guide/authentication.md @@ -0,0 +1,67 @@ +# 身份验证 + +## Cookies + +这是大多数基于 Web 的客户端类型向 YouTube 进行身份验证的推荐方式: + +```js +const innertube = await Innertube.create({ + cookie: '...' +}); +``` + +获取你的 cookies: +1. 在浏览器中打开一个新的无痕/隐私窗口(这可以防止你的 cookies 被轮换) +2. 在无痕窗口中登录 YouTube +3. 打开开发者工具(F12) +4. 转到网络(Network)选项卡 +5. 从任何对 `youtube.com` 的请求中复制 `Cookie` 标头的值 +6. 复制 cookies 后关闭无痕窗口 + +## YouTube TV OAuth2(受限) + +**重要提示:** 由于 Google 所做的更改,OAuth2 身份验证现在仅适用于 TV InnerTube 客户端。对于其他客户端类型,请使用基于 cookie 的身份验证(请参阅上面的 Cookies 部分)。 + +智能电视的 YouTube 应用使用 OAuth2 进行身份验证,由于它也使用 InnerTube,我们可以使用其客户端 ID 和客户端密钥检索有效令牌。 + +```ts +innertube.session.on('auth-pending', (data) => { + // data.verification_url 包含授权 URL。 + // data.user_code 包含要在网站上输入的代码。 +}); + +innertube.session.on('auth', ({ credentials }) => { + // 对凭据执行某些操作,例如将它们保存到文件。 + console.log('登录成功'); +}); + +// 当访问令牌过期时触发。 +innertube.session.on('update-credentials', ({ credentials }) => { /** 对更新的凭据执行某些操作。 */ }); + +await innertube.session.signIn(/* 凭据 */); +``` + +可以在[这里](https://github.com/LuanRT/YouTube.js/blob/main/examples/auth/yttv-oauth2.js)找到示例。 + +### 缓存 + +如果你不想每次初始化会话时都启动登录流程,可以缓存凭据。请注意,这不是推荐的做法,因为可能会导致安全问题。 + +```js +// 如果使用此方法,下次调用 signIn 不会触发 'auth-pending',而只会触发 'auth' +await innertube.session.oauth.cacheCredentials(); +``` + +**注意:** 使用缓存的凭据时,仍然需要调用 `Session#signIn()`。 + +### 撤销凭据 + +注销方法可用于撤销和删除当前会话的凭据。 + +```js +await innertube.session.signOut(); + +// 如果你不想注销当前会话 +// 只想删除缓存的凭据,请使用: +await innertube.session.oauth.removeCache(); +``` diff --git a/docs/zh/guide/browser-usage.md b/docs/zh/guide/browser-usage.md new file mode 100644 index 0000000..88ee47d --- /dev/null +++ b/docs/zh/guide/browser-usage.md @@ -0,0 +1,52 @@ +::: warning +此示例已过时。请查看 [kira](https://github.com/LuanRT/kira) 或 [sabr-shaka-example](https://github.com/LuanRT/googlevideo/tree/main/examples/sabr-shaka-example) 以获取最新的浏览器使用方法。 +::: + +# 浏览器使用 +要在浏览器中使用 YouTube.js,你必须通过自己的服务器代理请求。Deno 中的简单代理实现可在 [`examples/browser/proxy/deno.ts`](https://github.com/LuanRT/YouTube.js/tree/main/examples/browser/proxy/deno.ts) 找到。 + +你可以提供自己的 fetch 实现供 YouTube.js 使用,我们将使用它来修改请求并通过代理发送。有关使用 [Vite](https://vitejs.dev/) 的简单示例,请参阅 [`examples/browser/web`](https://github.com/LuanRT/YouTube.js/tree/main/examples/browser/web)。 + + +```ts +import { Innertube } from 'youtubei.js/web'; +await Innertube.create({ + fetch: async (input: RequestInfo | URL, init?: RequestInit) => { + // 修改请求 + // 并将其发送到代理 + + return fetch(request, init); + } +}); +``` + +### 流媒体 +YouTube.js 支持在浏览器中通过将 YouTube 的流媒体数据转换为 MPEG-DASH 清单来播放视频。 + +下面的示例使用 [`dash.js`](https://github.com/Dash-Industry-Forum/dash.js) 来播放视频。 + +```ts +import { Innertube } from 'youtubei.js/web'; +import dashjs from 'dashjs'; + +const innertube = await Innertube.create({ /* 设置 - 见上文 */ }); + +// 获取视频信息 +const videoInfo = await innertube.getInfo('videoId', { client: 'TV' }); + +// 现在转换为 dash 清单 +// 再次强调 - 要能够在浏览器中播放视频 - 你必须通过自己的服务器代理请求 +// 为此,我们提供了一个方法来在将 URL 写入清单之前转换它们 +const manifest = await videoInfo.toDash(url => { + // 修改 url + // 并返回它 + return url; +}); + +const uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(manifest); + +const videoElement = document.getElementById('video_player'); + +const player = dashjs.MediaPlayer().create(); +player.initialize(videoElement, uri, true); +``` diff --git a/docs/zh/guide/caching.md b/docs/zh/guide/caching.md new file mode 100644 index 0000000..b945217 --- /dev/null +++ b/docs/zh/guide/caching.md @@ -0,0 +1,24 @@ +# 缓存 +缓存转换后的播放器和会话实例可以大大提高性能。我们的 `UniversalCache` 实现根据环境使用不同的缓存方法。 + +在 Node.js 中,我们使用 `node:fs` 模块,在 Deno 中使用 `Deno.writeFile()`,在浏览器中使用 `indexedDB`。 + +默认情况下,缓存将数据存储在操作系统的临时目录中(或浏览器中的 `indexedDB`)。 + +下面的示例创建一个非持久缓存。 +```ts +import { Innertube, UniversalCache } from 'youtubei.js'; +const innertube = await Innertube.create({ cache: new UniversalCache(false) }); +``` + +你可以通过指定缓存目录的路径使其持久化,如果目录不存在将会创建。 +```ts +const innertube = await Innertube.create({ + cache: new UniversalCache( + // 启用持久缓存 + true, + // 缓存目录的路径。如果目录不存在将会创建 + './.cache' + ) +}); +``` diff --git a/docs/zh/guide/faq.md b/docs/zh/guide/faq.md new file mode 100644 index 0000000..2d675c8 --- /dev/null +++ b/docs/zh/guide/faq.md @@ -0,0 +1,23 @@ +# 常见问题 + +## 什么是 "InnerTube"? + +InnerTube 是 YouTube 的私有 API。官方 YouTube 网站和应用程序使用它来获取数据。 + +[How Project InnerTube Helped Pull YouTube Out of the Gutter](https://gizmodo.com/how-project-innertube-helped-pull-youtube-out-of-the-gu-1704946491); + +## 是否支持身份验证? + +是的,请参阅[身份验证](./authentication.md)指南。 + +## 如何禁用日志记录? + +你可以通过更改默认日志级别来禁用日志记录。更多信息可以在[故障排除](./troubleshooting.md)页面找到。 + +## 支持哪些环境? + +支持 Node.js、Deno、现代浏览器和 React Native。 + +## 为什么视频信息请求在我的服务器上失败? + +失败的原因有很多。最常见的原因是服务器的 IP 地址被 YouTube 封锁。不幸的是,目前没有已知的解决方案。 diff --git a/docs/zh/guide/getting-started.md b/docs/zh/guide/getting-started.md new file mode 100644 index 0000000..69c8f66 --- /dev/null +++ b/docs/zh/guide/getting-started.md @@ -0,0 +1,144 @@ +# 快速开始 + +## 前置要求 +YouTube.js 可在 Node.js、Deno 和现代浏览器上运行。 + +它需要具有以下特性的运行时: +- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) + - 在 Node 上,我们使用 [undici](https://github.com/nodejs/undici) 的 fetch 实现,需要 Node.js 16.8+。如果需要使用更旧的版本,可以提供自己的 fetch 实现。详见[提供自定义 fetch 实现](#custom-fetch)。 + - fetch 返回的 `Response` 对象必须符合规范,如果要使用 `VideoInfo#download` 方法,必须返回 `ReadableStream` 对象。(像 `node-fetch` 这样的实现返回非标准的 `Readable` 对象。) +- 需要 [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) 和 [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent)。 + + +## 安装 +```bash +# NPM +npm install youtubei.js@latest + +# Yarn +yarn add youtubei.js@latest + +# Git(最新版本) +npm install github:LuanRT/YouTube.js + +# Deno +deno add npm:youtubei.js@latest +``` + +Deno(已弃用): +```ts +import { Innertube } from 'https://deno.land/x/youtubei/deno.ts'; +``` + +## 基本用法 +```ts +import { Innertube } from 'youtubei.js'; +const innertube = await Innertube.create(/* 选项 */); +``` + +### 配置选项 + +#### `lang` (string) +- **描述**:会话语言。 +- **默认值**:`en` + +#### `location` (string) +- **描述**:地理位置设置。 +- **默认值**:`US` + +#### `user_agent` (string) +- **描述**:InnerTube 请求的用户代理。 +- **默认值**:`undefined` + +#### `account_index` (number) +- **描述**:要使用的账户索引。如果有多个账户登录,这很有用。仅适用于 cookies。 +- **默认值**:`0` + +#### `on_behalf_of_user` (string) +- **描述**:要使用的 YouTube 个人资料/频道的页面 ID,如果登录账户有多个个人资料。 +- **默认值**:`undefined` + +#### `visitor_data` (string) +- **描述**:持久访客数据字符串,允许 YouTube 即使在未登录时也能提供定制内容。 +- **默认值**:`undefined` + +#### `po_token` (string) +- **描述**:会话绑定的原始证明令牌(认证令牌),用于确认请求来自真实客户端。 +- **默认值**:`undefined` + +#### `player_id` (string) +- **描述**:播放器 ID 覆盖。可用于在 YouTube 引入破坏性更改时通过强制使用旧播放器来解决临时问题。 +- **默认值**:`undefined` + +#### `retrieve_player` (boolean) +- **描述**:指定是否检索 JS 播放器。禁用此选项将使会话创建更快,但无法解密格式。 +- **默认值**:`true` + +#### `enable_safety_mode` (boolean) +- **描述**:启用 YouTube 的安全模式,防止加载潜在不安全的内容。 +- **默认值**:`false` + +#### `retrieve_innertube_config` (boolean) +- **描述**:指定是否检索 InnerTube 配置。对 "onesie" 请求有用。 +- **默认值**:`true` + +#### `generate_session_locally` (boolean) +- **描述**:在本地生成会话数据而不是从 YouTube 检索,以获得更好的性能。如果会话已缓存,则忽略此选项。 +- **默认值**:`false` + +#### `enable_session_cache` (boolean) +- **描述**:缓存会话数据以供将来使用。 +- **默认值**:`true` + +#### `device_category` (string) +- **描述**:会话的平台类型(`DESKTOP`、`MOBILE` 等)。 +- **默认值**:`DESKTOP` + +#### `client_type` (string) +- **描述**:InnerTube 客户端类型(`WEB`、`ANDROID` 等)。 +- **默认值**:`WEB` + +#### `timezone` (string) +- **描述**:会话的时区。 +- **默认值**:`*` + +#### `cache` (ICache) +- **描述**:缓存实现。 +- **默认值**:`undefined` + +#### `cookie` (string) +- **描述**:用于身份验证会话的 Cookies。 +- **默认值**:`undefined` + +#### `fetch` (FetchFunction) +- **描述**:自定义 fetch 实现。 +- **默认值**:`fetch` + +## 提供自定义 JavaScript 解释器 + +某些功能(如解密流媒体 URL)需要执行 YouTube 的混淆 JavaScript 代码。YouTube.js **不**包含用于此目的的内置解释器,因此你必须提供自己的解释器。 + +以下是使用 JavaScript 的 `Function` 构造函数的示例: + +```ts +import { Innertube, Platform, Types } from 'youtubei.js/web'; + +Platform.shim.eval = async (data: Types.BuildScriptResult, env: Record) => { + const properties = []; + + if(env.n) { + properties.push(`n: exportedVars.nFunction("${env.n}")`) + } + + if (env.sig) { + properties.push(`sig: exportedVars.sigFunction("${env.sig}")`) + } + + const code = `${data.output}\nreturn { ${properties.join(', ')} }`; + + return new Function(code)(); +} + +const innertube = await Innertube.create(/* 选项 */); +// ... +``` diff --git a/docs/zh/guide/proxies.md b/docs/zh/guide/proxies.md new file mode 100644 index 0000000..1f7e499 --- /dev/null +++ b/docs/zh/guide/proxies.md @@ -0,0 +1,14 @@ +# 代理 +你可以提供自己的 fetch 实现供 YouTube.js 使用。这在某些情况下很有用,可以在发送请求之前修改它们,并在返回响应之前转换它们(例如用于代理)。 +```ts +// 提供 fetch 实现 +const yt = await Innertube.create({ + fetch: async (input: RequestInfo | URL, init?: RequestInit) => { + // 使用你自己的 fetch 实现发起请求 + // 并返回响应 + return new Response( + /* ... */ + ); + } +}); +``` diff --git a/docs/zh/guide/troubleshooting.md b/docs/zh/guide/troubleshooting.md new file mode 100644 index 0000000..cb82563 --- /dev/null +++ b/docs/zh/guide/troubleshooting.md @@ -0,0 +1,9 @@ +# 故障排除 + +## 更改日志级别 + +```typescript +import { Log } from 'youtubei.js'; +// 可用级别:NONE, ERROR, WARNING, INFO, DEBUG +Log.setLevel(Log.Level.NONE); +```