mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
d2974a9ff6
* refactor(adapters): convert adapter layer from TypeScript to JavaScript Core framework stays TypeScript; adapter layer moves to JS-first. Adapters are essentially "executable config + browser scripts" that barely use TS features — this simplifies the build/distribution pipeline by removing the dist/clis/ intermediate compilation step. Changes: - Convert all 753 adapter files in clis/ from .ts to .js - Update tsconfig to exclude clis/ from compilation - Simplify build-manifest to scan clis/*.js directly (no dist/clis/) - Update discovery, main, fetch-adapters to load JS adapters from clis/ - Update generate-verified to output .js artifacts - Update package.json files field: dist/clis/ → clis/ - Fix all test files for the .ts → .js transition * fix(main): use findPackageRoot for BUILTIN_CLIS path The previous relative path (../../clis from __dirname) only worked for dist/src/main.js but broke dev mode (tsx src/main.ts) where __dirname is <repo>/src — resolving to /clis instead of <repo>/clis. Use findPackageRoot() which works for both dev and prod paths.
93 lines
2.9 KiB
JavaScript
93 lines
2.9 KiB
JavaScript
/**
|
|
* Extract a YouTube video ID from a URL or bare video ID string.
|
|
* Supports: watch?v=, youtu.be/, /shorts/, /embed/, /live/, /v/
|
|
*/
|
|
export function parseVideoId(input) {
|
|
if (!input.startsWith('http'))
|
|
return input;
|
|
try {
|
|
const parsed = new URL(input);
|
|
if (parsed.searchParams.has('v')) {
|
|
return parsed.searchParams.get('v');
|
|
}
|
|
if (parsed.hostname === 'youtu.be') {
|
|
return parsed.pathname.slice(1).split('/')[0];
|
|
}
|
|
// Handle /shorts/xxx, /embed/xxx, /live/xxx, /v/xxx
|
|
const pathMatch = parsed.pathname.match(/^\/(shorts|embed|live|v)\/([^/?]+)/);
|
|
if (pathMatch)
|
|
return pathMatch[2];
|
|
}
|
|
catch {
|
|
// Not a valid URL — treat entire input as video ID
|
|
}
|
|
return input;
|
|
}
|
|
/**
|
|
* Extract a JSON object assigned to a known bootstrap variable inside YouTube HTML.
|
|
*/
|
|
export function extractJsonAssignmentFromHtml(html, keys) {
|
|
const candidates = Array.isArray(keys) ? keys : [keys];
|
|
for (const key of candidates) {
|
|
const markers = [
|
|
`var ${key} = `,
|
|
`window["${key}"] = `,
|
|
`window.${key} = `,
|
|
`${key} = `,
|
|
];
|
|
for (const marker of markers) {
|
|
const markerIndex = html.indexOf(marker);
|
|
if (markerIndex === -1)
|
|
continue;
|
|
const jsonStart = html.indexOf('{', markerIndex + marker.length);
|
|
if (jsonStart === -1)
|
|
continue;
|
|
let depth = 0;
|
|
let inString = false;
|
|
let escaping = false;
|
|
for (let i = jsonStart; i < html.length; i += 1) {
|
|
const ch = html[i];
|
|
if (inString) {
|
|
if (escaping) {
|
|
escaping = false;
|
|
}
|
|
else if (ch === '\\') {
|
|
escaping = true;
|
|
}
|
|
else if (ch === '"') {
|
|
inString = false;
|
|
}
|
|
continue;
|
|
}
|
|
if (ch === '"') {
|
|
inString = true;
|
|
continue;
|
|
}
|
|
if (ch === '{') {
|
|
depth += 1;
|
|
continue;
|
|
}
|
|
if (ch === '}') {
|
|
depth -= 1;
|
|
if (depth === 0) {
|
|
try {
|
|
return JSON.parse(html.slice(jsonStart, i + 1));
|
|
}
|
|
catch {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* Prepare a quiet YouTube API-capable page without opening the watch UI.
|
|
*/
|
|
export async function prepareYoutubeApiPage(page) {
|
|
await page.goto('https://www.youtube.com', { waitUntil: 'none' });
|
|
await page.wait(2);
|
|
}
|