mirror of
https://github.com/imputnet/cobalt.git
synced 2025-06-28 09:28:29 +00:00
youtube全部通过oauth下载
This commit is contained in:
parent
d648b69222
commit
f464e62474
108
YOUTUBE_AUTH_SETUP.md
Normal file
108
YOUTUBE_AUTH_SETUP.md
Normal file
@ -0,0 +1,108 @@
|
||||
# YouTube认证开发环境配置说明
|
||||
|
||||
## 概述
|
||||
现在所有的YouTube下载都要求使用cookies.json中的认证信息。本指南将帮你在开发环境中配置YouTube OAuth认证。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 设置YouTube OAuth认证
|
||||
```powershell
|
||||
cd d:\code\cobalt_bam\api
|
||||
npm run setup-youtube
|
||||
```
|
||||
|
||||
这个命令会:
|
||||
- ✅ 创建cookies.json文件(如果不存在)
|
||||
- ✅ 运行YouTube OAuth token生成器
|
||||
- ✅ 提供详细的认证步骤
|
||||
|
||||
### 2. 验证认证配置
|
||||
```powershell
|
||||
npm run verify-cookies
|
||||
```
|
||||
|
||||
这个命令会检查:
|
||||
- ✅ cookies.json是否存在
|
||||
- ✅ YouTube认证是否配置正确
|
||||
- ✅ 认证信息是否有效
|
||||
|
||||
### 3. 启动开发服务器
|
||||
```powershell
|
||||
npm run dev
|
||||
```
|
||||
|
||||
这个命令会:
|
||||
- ✅ 自动设置COOKIE_PATH环境变量
|
||||
- ✅ 强制所有YouTube下载使用认证
|
||||
- ✅ 提供详细的认证日志
|
||||
|
||||
## 认证日志说明
|
||||
|
||||
所有关键的认证调用都会有特殊的日志标记,以`======>`开头:
|
||||
|
||||
```
|
||||
======> [getCookie] Requesting cookie for service: youtube_oauth
|
||||
======> [getCookie] Found 1 cookies for youtube_oauth, using index 0
|
||||
======> [cloneInnertube] Starting Innertube creation, useSession: false
|
||||
======> [cloneInnertube] Cookie available: true
|
||||
======> [youtube] Starting YouTube video processing for URL: https://youtube.com/watch?v=...
|
||||
======> [youtube] Cookie availability check - OAuth: true, Regular: false, Has any: true
|
||||
======> [getHeaders] Getting headers for service: youtube
|
||||
======> [getHeaders] YouTube service detected, checking for authentication cookies
|
||||
======> [getHeaders] Added authentication cookie for YouTube: access_token=ya29...
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题1: "No YouTube authentication cookies found!"
|
||||
**解决方案:**
|
||||
```powershell
|
||||
npm run setup-youtube
|
||||
# 按照提示完成OAuth认证
|
||||
# 将生成的token添加到cookies.json
|
||||
npm run verify-cookies
|
||||
```
|
||||
|
||||
### 问题2: "youtube.auth_required"错误
|
||||
这意味着没有找到有效的YouTube认证。检查:
|
||||
1. cookies.json文件存在
|
||||
2. youtube_oauth字段有有效的token
|
||||
3. token没有过期
|
||||
|
||||
### 问题3: 403错误持续出现
|
||||
可能的原因:
|
||||
1. OAuth token已过期
|
||||
2. Google账户被限制
|
||||
3. 需要重新认证
|
||||
|
||||
**解决方案:**
|
||||
```powershell
|
||||
# 重新生成token
|
||||
npm run setup-youtube
|
||||
```
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
d:\code\cobalt_bam\
|
||||
├── cookies.json # 认证配置文件
|
||||
├── api/
|
||||
│ ├── dev-start.js # 开发环境启动脚本
|
||||
│ ├── setup-youtube-auth.js # YouTube OAuth设置脚本
|
||||
│ ├── verify-cookies.js # Cookie验证脚本
|
||||
│ └── src/
|
||||
│ ├── cookies.json # API目录下的cookies(生产用)
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
⚠️ **重要**:
|
||||
- 不要提交包含真实token的cookies.json到Git
|
||||
- 不要在公共场所分享你的OAuth token
|
||||
- 定期更新和轮换认证token
|
||||
- 使用专门的测试Google账户,不要使用个人账户
|
||||
|
||||
## 生产环境部署
|
||||
|
||||
生产环境中,cookies.json的路径通过deployment配置管理,你不需要关心生产环境的配置。
|
20
api/dev-start.js
Normal file
20
api/dev-start.js
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// 开发环境启动脚本 - 设置cookies.json路径
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const cookiesPath = resolve(__dirname, '../cookies.json');
|
||||
|
||||
// 设置环境变量
|
||||
process.env.COOKIE_PATH = cookiesPath;
|
||||
|
||||
console.log(`======> [dev-start] Setting COOKIE_PATH to: ${cookiesPath}`);
|
||||
console.log(`======> [dev-start] YouTube authentication will be required for all downloads`);
|
||||
|
||||
// 导入并启动主应用
|
||||
import('./src/cobalt.js').catch(err => {
|
||||
console.error('Failed to start application:', err);
|
||||
process.exit(1);
|
||||
});
|
@ -7,9 +7,11 @@
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
}, "scripts": {
|
||||
"start": "node src/cobalt",
|
||||
"dev": "node dev-start.js",
|
||||
"setup-youtube": "node setup-youtube-auth.js",
|
||||
"verify-cookies": "node verify-cookies.js",
|
||||
"test": "node src/util/test",
|
||||
"token:jwt": "node src/util/generate-jwt-secret"
|
||||
},
|
||||
|
38
api/setup-youtube-auth.js
Normal file
38
api/setup-youtube-auth.js
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// 生成YouTube OAuth token并自动添加到cookies.json
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const cookiesPath = resolve(__dirname, '../cookies.json');
|
||||
|
||||
console.log(`======> [setup-youtube-auth] Starting YouTube OAuth setup`);
|
||||
console.log(`======> [setup-youtube-auth] Cookies file: ${cookiesPath}`);
|
||||
|
||||
// 检查cookies.json是否存在
|
||||
if (!existsSync(cookiesPath)) {
|
||||
console.log(`======> [setup-youtube-auth] Creating new cookies.json file`);
|
||||
const defaultCookies = {
|
||||
"instagram": ["mid=<replace>; ig_did=<with>; csrftoken=<your>; ds_user_id=<own>; sessionid=<cookies>"],
|
||||
"instagram_bearer": ["token=<token_with_no_bearer_in_front>", "token=IGT:2:<looks_like_this>"],
|
||||
"reddit": ["client_id=<replace_this>; client_secret=<replace_this>; refresh_token=<replace_this>"],
|
||||
"twitter": ["auth_token=<replace_this>; ct0=<replace_this>"],
|
||||
"youtube_oauth": ["access_token=<your_oauth_token>; refresh_token=<your_refresh_token>; expires_in=<expiry>"]
|
||||
};
|
||||
writeFileSync(cookiesPath, JSON.stringify(defaultCookies, null, 2));
|
||||
}
|
||||
|
||||
console.log(`======> [setup-youtube-auth] Running YouTube token generator...`);
|
||||
console.log(`======> [setup-youtube-auth] IMPORTANT: Please follow the authentication instructions carefully`);
|
||||
|
||||
// 导入并运行YouTube token生成器
|
||||
import('./src/util/generate-youtube-tokens.js').then(() => {
|
||||
console.log(`======> [setup-youtube-auth] OAuth token generation completed`);
|
||||
console.log(`======> [setup-youtube-auth] Please copy the generated token to ${cookiesPath}`);
|
||||
console.log(`======> [setup-youtube-auth] Replace the youtube_oauth array content with the generated token`);
|
||||
}).catch(err => {
|
||||
console.error(`======> [setup-youtube-auth] Failed to generate token:`, err);
|
||||
process.exit(1);
|
||||
});
|
@ -13,6 +13,7 @@ const VALID_SERVICES = new Set([
|
||||
'reddit',
|
||||
'twitter',
|
||||
'youtube',
|
||||
'youtube_oauth',
|
||||
]);
|
||||
|
||||
const invalidCookies = {};
|
||||
@ -102,24 +103,36 @@ export const setup = async (path) => {
|
||||
}
|
||||
|
||||
export function getCookie(service) {
|
||||
console.log(`======> [getCookie] Requesting cookie for service: ${service}`);
|
||||
|
||||
if (!VALID_SERVICES.has(service)) {
|
||||
console.error(
|
||||
`${Red('[!]')} ${service} not in allowed services list for cookies.`
|
||||
+ ' if adding a new cookie type, include it there.'
|
||||
);
|
||||
console.log(`======> [getCookie] Service ${service} not in valid services list`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cookies[service] || !cookies[service].length) return;
|
||||
if (!cookies[service] || !cookies[service].length) {
|
||||
console.log(`======> [getCookie] No cookies found for service: ${service}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = Math.floor(Math.random() * cookies[service].length);
|
||||
console.log(`======> [getCookie] Found ${cookies[service].length} cookies for ${service}, using index ${idx}`);
|
||||
|
||||
const cookie = cookies[service][idx];
|
||||
if (typeof cookie === 'string') {
|
||||
console.log(`======> [getCookie] Converting string cookie to Cookie object for ${service}`);
|
||||
cookies[service][idx] = Cookie.fromString(cookie);
|
||||
}
|
||||
|
||||
cookies[service][idx].meta = { service, idx };
|
||||
|
||||
const cookieStr = cookies[service][idx].toString();
|
||||
console.log(`======> [getCookie] Returning cookie for ${service}: ${cookieStr.substring(0, 50)}${cookieStr.length > 50 ? '...' : ''}`);
|
||||
|
||||
return cookies[service][idx];
|
||||
}
|
||||
|
||||
|
@ -47,19 +47,37 @@ const clientsWithNoCipher = ['IOS', 'ANDROID', 'YTSTUDIO_ANDROID', 'YTMUSIC_ANDR
|
||||
const videoQualities = [144, 240, 360, 480, 720, 1080, 1440, 2160, 4320];
|
||||
|
||||
const cloneInnertube = async (customFetch, useSession) => {
|
||||
console.log(`======> [cloneInnertube] Starting Innertube creation, useSession: ${useSession}`);
|
||||
|
||||
const shouldRefreshPlayer = lastRefreshedAt + PLAYER_REFRESH_PERIOD < new Date();
|
||||
console.log(`======> [cloneInnertube] Should refresh player: ${shouldRefreshPlayer}`);
|
||||
|
||||
const rawCookie = getCookie('youtube');
|
||||
// First try youtube_oauth cookies
|
||||
let rawCookie = getCookie('youtube_oauth');
|
||||
if (!rawCookie) {
|
||||
console.log(`======> [cloneInnertube] No youtube_oauth cookies found, trying regular youtube cookies`);
|
||||
rawCookie = getCookie('youtube');
|
||||
}
|
||||
|
||||
const cookie = rawCookie?.toString();
|
||||
console.log(`======> [cloneInnertube] Cookie available: ${!!cookie}`);
|
||||
if (cookie) {
|
||||
console.log(`======> [cloneInnertube] Cookie length: ${cookie.length}, preview: ${cookie.substring(0, 100)}...`);
|
||||
}
|
||||
|
||||
const sessionTokens = getYouTubeSession();
|
||||
console.log(`======> [cloneInnertube] Session tokens available: ${!!sessionTokens}`);
|
||||
|
||||
const retrieve_player = Boolean(sessionTokens || cookie);
|
||||
console.log(`======> [cloneInnertube] Will retrieve player: ${retrieve_player}`);
|
||||
|
||||
if (useSession && env.ytSessionServer && !sessionTokens?.potoken) {
|
||||
console.log(`======> [cloneInnertube] Throwing no_session_tokens error`);
|
||||
throw "no_session_tokens";
|
||||
}
|
||||
|
||||
if (!innertube || shouldRefreshPlayer) {
|
||||
console.log(`======> [cloneInnertube] Creating new Innertube instance with cookie authentication`);
|
||||
innertube = await Innertube.create({
|
||||
fetch: customFetch,
|
||||
retrieve_player,
|
||||
@ -68,6 +86,7 @@ const cloneInnertube = async (customFetch, useSession) => {
|
||||
visitor_data: useSession ? sessionTokens?.visitor_data : undefined,
|
||||
});
|
||||
lastRefreshedAt = +new Date();
|
||||
console.log(`======> [cloneInnertube] Innertube instance created successfully`);
|
||||
}
|
||||
|
||||
const session = new Session(
|
||||
@ -88,18 +107,34 @@ const cloneInnertube = async (customFetch, useSession) => {
|
||||
}
|
||||
|
||||
export default async function (o) {
|
||||
console.log(`======> [youtube] Starting YouTube video processing for URL: ${o.url || o.id}`);
|
||||
|
||||
// Check if cookies are available before proceeding
|
||||
const oauthCookie = getCookie('youtube_oauth');
|
||||
const regularCookie = getCookie('youtube');
|
||||
const hasCookies = !!(oauthCookie || regularCookie);
|
||||
|
||||
console.log(`======> [youtube] Cookie availability check - OAuth: ${!!oauthCookie}, Regular: ${!!regularCookie}, Has any: ${hasCookies}`);
|
||||
|
||||
if (!hasCookies) {
|
||||
console.log(`======> [youtube] ERROR: No YouTube authentication cookies found! All YouTube downloads require authentication.`);
|
||||
return { error: "youtube.auth_required" };
|
||||
}
|
||||
|
||||
const quality = o.quality === "max" ? 9000 : Number(o.quality);
|
||||
console.log(`======> [youtube] Processing with quality: ${quality}`);
|
||||
|
||||
let useHLS = o.youtubeHLS;
|
||||
let innertubeClient = o.innertubeClient || env.customInnertubeClient || "IOS";
|
||||
console.log(`======> [youtube] Using HLS: ${useHLS}, Client: ${innertubeClient}`);
|
||||
|
||||
// HLS playlists from the iOS client don't contain the av1 video format.
|
||||
if (useHLS && o.format === "av1") {
|
||||
useHLS = false;
|
||||
}
|
||||
|
||||
if (useHLS) {
|
||||
console.log(`======> [youtube] Disabled HLS due to av1 format`);
|
||||
} if (useHLS) {
|
||||
innertubeClient = "IOS";
|
||||
console.log(`======> [youtube] Set client to IOS for HLS`);
|
||||
}
|
||||
|
||||
// iOS client doesn't have adaptive formats of resolution >1080p,
|
||||
@ -114,14 +149,14 @@ export default async function (o) {
|
||||
|| (quality > 1080 && o.format !== "vp9")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (useSession) {
|
||||
); if (useSession) {
|
||||
innertubeClient = env.ytSessionInnertubeClient || "WEB_EMBEDDED";
|
||||
console.log(`======> [youtube] Using session client: ${innertubeClient}`);
|
||||
}
|
||||
|
||||
let yt;
|
||||
try {
|
||||
console.log(`======> [youtube] Creating Innertube instance with authentication`);
|
||||
yt = await cloneInnertube(
|
||||
(input, init) => fetch(input, {
|
||||
...init,
|
||||
@ -129,7 +164,9 @@ export default async function (o) {
|
||||
}),
|
||||
useSession
|
||||
);
|
||||
console.log(`======> [youtube] Innertube instance created successfully with authentication`);
|
||||
} catch (e) {
|
||||
console.log(`======> [youtube] Innertube creation failed: ${e}`);
|
||||
if (e === "no_session_tokens") {
|
||||
return { error: "youtube.no_session_tokens" };
|
||||
} else if (e.message?.endsWith("decipher algorithm")) {
|
||||
@ -141,8 +178,11 @@ export default async function (o) {
|
||||
|
||||
let info;
|
||||
try {
|
||||
console.log(`======> [youtube] Getting basic video info for ID: ${o.id} with client: ${innertubeClient}`);
|
||||
info = await yt.getBasicInfo(o.id, innertubeClient);
|
||||
console.log(`======> [youtube] Successfully retrieved video info with authentication`);
|
||||
} catch (e) {
|
||||
console.log(`======> [youtube] Failed to get video info: ${e.message}`);
|
||||
if (e?.info) {
|
||||
let errorInfo;
|
||||
try { errorInfo = JSON.parse(e?.info); } catch {}
|
||||
|
@ -7,8 +7,8 @@ const CHUNK_SIZE = BigInt(8e6); // 8 MB
|
||||
const min = (a, b) => a < b ? a : b;
|
||||
|
||||
async function* readChunks(streamInfo, size) {
|
||||
let read = 0n, chunksSinceTransplant = 0;
|
||||
console.log(`[readChunks] Starting chunk download - Total size: ${size}, URL: ${streamInfo.url}`);
|
||||
let read = 0n, chunksSinceTransplant = 0; console.log(`[readChunks] Starting chunk download - Total size: ${size}, URL: ${streamInfo.url}`);
|
||||
console.log(`======> [readChunks] YouTube chunk download with authentication started`);
|
||||
|
||||
while (read < size) {
|
||||
if (streamInfo.controller.signal.aborted) {
|
||||
@ -20,19 +20,26 @@ async function* readChunks(streamInfo, size) {
|
||||
const rangeEnd = read + CHUNK_SIZE;
|
||||
console.log(`[readChunks] Requesting chunk: bytes=${rangeStart}-${rangeEnd}, read=${read}/${size}`);
|
||||
|
||||
const headers = {
|
||||
...getHeaders('youtube'),
|
||||
Range: `bytes=${read}-${read + CHUNK_SIZE}`
|
||||
};
|
||||
console.log(`======> [readChunks] Chunk request using authenticated headers: ${!!headers.Cookie}`);
|
||||
|
||||
const chunk = await request(streamInfo.url, {
|
||||
headers: {
|
||||
...getHeaders('youtube'),
|
||||
Range: `bytes=${read}-${read + CHUNK_SIZE}`
|
||||
},
|
||||
headers,
|
||||
dispatcher: streamInfo.dispatcher,
|
||||
signal: streamInfo.controller.signal,
|
||||
maxRedirections: 4
|
||||
});
|
||||
|
||||
console.log(`[readChunks] Chunk response: status=${chunk.statusCode}, content-length=${chunk.headers['content-length']}`); if (chunk.statusCode === 403 && chunksSinceTransplant >= 3 && streamInfo.originalRequest) {
|
||||
console.log(`[readChunks] Chunk response: status=${chunk.statusCode}, content-length=${chunk.headers['content-length']}`);
|
||||
console.log(`======> [readChunks] Authenticated chunk request result: status=${chunk.statusCode}`);
|
||||
|
||||
if (chunk.statusCode === 403 && chunksSinceTransplant >= 3 && streamInfo.originalRequest) {
|
||||
chunksSinceTransplant = 0;
|
||||
console.log(`[readChunks] 403 error after 3+ chunks, attempting fresh YouTube API call`);
|
||||
console.log(`======> [readChunks] 403 error detected, attempting fresh authenticated API call`);
|
||||
try {
|
||||
// Import YouTube service dynamically
|
||||
const handler = await import(`../processing/services/youtube.js`);
|
||||
@ -49,6 +56,7 @@ async function* readChunks(streamInfo, size) {
|
||||
error: response.error,
|
||||
type: response.type
|
||||
});
|
||||
console.log(`======> [readChunks] Fresh authenticated API call result:`, { hasUrls: !!response.urls, error: response.error });
|
||||
|
||||
if (response.urls) {
|
||||
response.urls = [response.urls].flat();
|
||||
@ -124,22 +132,28 @@ async function handleYoutubeStream(streamInfo, res) {
|
||||
};
|
||||
|
||||
console.log(`[handleYoutubeStream] Starting YouTube stream for URL: ${streamInfo.url}`);
|
||||
console.log(`======> [handleYoutubeStream] YouTube stream processing initiated with authentication`);
|
||||
|
||||
try {
|
||||
let req, attempts = 3;
|
||||
console.log(`[handleYoutubeStream] Starting HEAD request with ${attempts} attempts`);
|
||||
console.log(`======> [handleYoutubeStream] Using authenticated headers for HEAD request`);
|
||||
|
||||
while (attempts--) {
|
||||
const headers = getHeaders('youtube');
|
||||
console.log(`======> [handleYoutubeStream] HEAD request headers prepared with auth: ${!!headers.Cookie}`);
|
||||
|
||||
req = await fetch(streamInfo.url, {
|
||||
headers: getHeaders('youtube'),
|
||||
headers,
|
||||
method: 'HEAD',
|
||||
dispatcher: streamInfo.dispatcher,
|
||||
signal
|
||||
});
|
||||
|
||||
console.log(`[handleYoutubeStream] HEAD response: status=${req.status}, url=${req.url}`);
|
||||
console.log(`======> [handleYoutubeStream] Authenticated HEAD request completed: status=${req.status}`);
|
||||
|
||||
streamInfo.url = req.url; if (req.status === 403 && streamInfo.originalRequest && attempts > 0) {
|
||||
streamInfo.url = req.url;if (req.status === 403 && streamInfo.originalRequest && attempts > 0) {
|
||||
console.log(`[handleYoutubeStream] Got 403, attempting fresh YouTube API call (attempts left: ${attempts})`);
|
||||
try {
|
||||
// Import YouTube service dynamically
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { genericUserAgent } from "../config.js";
|
||||
import { vkClientAgent } from "../processing/services/vk.js";
|
||||
import { getCookie } from "../processing/cookie/manager.js";
|
||||
import { getInternalTunnelFromURL } from "./manage.js";
|
||||
import { probeInternalTunnel } from "./internal.js";
|
||||
|
||||
@ -44,9 +45,34 @@ export function closeResponse(res) {
|
||||
}
|
||||
|
||||
export function getHeaders(service) {
|
||||
console.log(`======> [getHeaders] Getting headers for service: ${service}`);
|
||||
|
||||
// Converting all header values to strings
|
||||
return Object.entries({ ...defaultHeaders, ...serviceHeaders[service] })
|
||||
.reduce((p, [key, val]) => ({ ...p, [key]: String(val) }), {})
|
||||
const baseHeaders = Object.entries({ ...defaultHeaders, ...serviceHeaders[service] })
|
||||
.reduce((p, [key, val]) => ({ ...p, [key]: String(val) }), {});
|
||||
|
||||
// For YouTube, always try to add authentication cookies
|
||||
if (service === 'youtube') {
|
||||
console.log(`======> [getHeaders] YouTube service detected, checking for authentication cookies`);
|
||||
|
||||
// First try OAuth cookies, then regular cookies
|
||||
let cookie = getCookie('youtube_oauth');
|
||||
if (!cookie) {
|
||||
console.log(`======> [getHeaders] No OAuth cookies found, trying regular youtube cookies`);
|
||||
cookie = getCookie('youtube');
|
||||
}
|
||||
|
||||
if (cookie) {
|
||||
const cookieStr = cookie.toString();
|
||||
baseHeaders.Cookie = cookieStr;
|
||||
console.log(`======> [getHeaders] Added authentication cookie for YouTube: ${cookieStr.substring(0, 50)}${cookieStr.length > 50 ? '...' : ''}`);
|
||||
} else {
|
||||
console.log(`======> [getHeaders] WARNING: No YouTube authentication cookies found! Requests may fail.`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`======> [getHeaders] Final headers for ${service}:`, Object.keys(baseHeaders));
|
||||
return baseHeaders;
|
||||
}
|
||||
|
||||
export function pipe(from, to, done) {
|
||||
|
51
api/verify-cookies.js
Normal file
51
api/verify-cookies.js
Normal file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Cookie验证工具 - 检查cookies.json的有效性
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const cookiesPath = process.argv[2] || resolve(__dirname, '../cookies.json');
|
||||
|
||||
console.log(`======> [verify-cookies] Verifying cookies at: ${cookiesPath}`);
|
||||
|
||||
if (!existsSync(cookiesPath)) {
|
||||
console.log(`======> [verify-cookies] ❌ cookies.json not found at: ${cookiesPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const cookies = JSON.parse(readFileSync(cookiesPath, 'utf8'));
|
||||
console.log(`======> [verify-cookies] ✅ cookies.json loaded successfully`);
|
||||
|
||||
// 检查YouTube相关cookies
|
||||
const hasYouTubeOAuth = cookies.youtube_oauth && cookies.youtube_oauth.length > 0;
|
||||
const hasYouTubeRegular = cookies.youtube && cookies.youtube.length > 0;
|
||||
|
||||
console.log(`======> [verify-cookies] YouTube OAuth cookies: ${hasYouTubeOAuth ? '✅ Found' : '❌ Missing'}`);
|
||||
console.log(`======> [verify-cookies] YouTube regular cookies: ${hasYouTubeRegular ? '✅ Found' : '❌ Missing'}`);
|
||||
|
||||
if (hasYouTubeOAuth) {
|
||||
const oauthCookie = cookies.youtube_oauth[0];
|
||||
if (oauthCookie.includes('<your_oauth_token>')) {
|
||||
console.log(`======> [verify-cookies] ⚠️ YouTube OAuth contains placeholder values`);
|
||||
console.log(`======> [verify-cookies] Please run: npm run setup-youtube`);
|
||||
} else {
|
||||
console.log(`======> [verify-cookies] ✅ YouTube OAuth appears to be configured`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasYouTubeOAuth && !hasYouTubeRegular) {
|
||||
console.log(`======> [verify-cookies] ❌ No YouTube authentication found!`);
|
||||
console.log(`======> [verify-cookies] YouTube downloads will fail without authentication`);
|
||||
console.log(`======> [verify-cookies] Please run: npm run setup-youtube`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`======> [verify-cookies] ✅ Cookie verification completed`);
|
||||
|
||||
} catch (error) {
|
||||
console.log(`======> [verify-cookies] ❌ Error reading cookies.json: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
Loading…
Reference in New Issue
Block a user