This commit is contained in:
eric
2026-05-16 19:24:30 -05:00
parent 19beec12a5
commit 75a0ca4e31
260 changed files with 51345 additions and 1 deletions

View File

@@ -0,0 +1,146 @@
/** 对齐 d2yexeapp electron `youtubeConfigUtils.ts` 的浏览器侧解析(无 OAuth) */
export function parseYoutubeStreamKeySuffix(iniOrKeyLine: string): string {
for (const line of iniOrKeyLine.split(/\r?\n/)) {
const t = line.trim();
if (t.startsWith("#") || !t) continue;
const m = t.match(/^(?:key|stream_key)\s*=\s*(.+)$/i);
if (m) {
const v = m[1].trim().split(/[#;]/)[0].trim();
if (v && !v.toLowerCase().startsWith("rtmp")) return v.length > 8 ? v.slice(-8) : v;
if (v.includes("live2/")) {
const parts = v.split("/");
const last = parts[parts.length - 1] || "";
return last.length > 8 ? last.slice(-8) : last;
}
}
}
const one = iniOrKeyLine.trim();
if (one && !one.includes("\n") && !one.toLowerCase().startsWith("rtmp"))
return one.length > 8 ? one.slice(-8) : one;
return "";
}
export function extractDouyinLiveUrls(urlIni: string): string[] {
const out: string[] = [];
for (const line of urlIni.split(/\r?\n/)) {
const t = line.trim();
if (!t || t.startsWith("#")) continue;
if (!/douyin\.com|v\.douyin\.com/i.test(t)) continue;
const full = t.match(/(https?:\/\/live\.douyin\.com\/[a-zA-Z0-9_-]+)/i);
if (full) {
out.push(full[1].replace(/^http:\/\//i, "https://"));
continue;
}
const rel = t.match(/live\.douyin\.com\/([a-zA-Z0-9_-]+)/i);
if (rel) {
out.push(`https://live.douyin.com/${rel[1]}`);
continue;
}
const n = t.match(/(\d{8,})/);
if (n) out.push(`https://live.douyin.com/${n[1]}`);
}
return Array.from(new Set(out)).slice(0, 16);
}
/** 从进程日志中解析「当前」抖音房间号(取文本中最后一次出现的 live.douyin.com/xxx) */
export function extractDouyinCurrentRoomFromLog(logText: string): string | null {
if (!logText) return null;
let last: string | null = null;
for (const line of logText.split(/\r?\n/)) {
const m = line.match(/live\.douyin\.com\/([a-zA-Z0-9_-]+)/i);
if (m) last = m[1];
const m2 = line.match(/https?:\/\/[^/\s]*douyin\.com\/(\d{8,})/i);
if (m2) last = m2[1];
}
return last;
}
/** URL_config 文本中提取抖音房间线索(与 d2y electron 一致) */
export function extractDouyinRoomHints(urlIni: string): string[] {
const out: string[] = [];
for (const line of urlIni.split(/\r?\n/)) {
const t = line.trim();
if (!t || t.startsWith("#")) continue;
if (!/douyin\.com|v\.douyin\.com/i.test(t)) continue;
const m = t.match(/live\.douyin\.com\/([a-zA-Z0-9_-]+)/i);
if (m) {
out.push(m[1]);
continue;
}
const n = t.match(/(\d{8,})/);
out.push(n ? n[1] : t.slice(0, 96));
}
return Array.from(new Set(out)).slice(0, 12);
}
export type YoutubeIniFields = {
key: string;
rtmps: boolean;
bitrate: string;
fastAudio: boolean;
};
export function parseYoutubeIniFields(content: string): YoutubeIniFields {
let key = "";
let rtmps = true;
let bitrate = "";
let fastAudio = false;
const lines = content.split(/\r?\n/);
let inYoutube = false;
for (const line of lines) {
const t = line.trim();
if (/^\[youtube\]/i.test(t)) {
inYoutube = true;
continue;
}
if (/^\[/.test(t)) {
inYoutube = false;
continue;
}
if (!inYoutube) continue;
if (!t || t.startsWith("#") || t.startsWith(";")) continue;
const eq = t.indexOf("=");
if (eq < 0) continue;
const k = t.slice(0, eq).trim().toLowerCase();
const v = t
.slice(eq + 1)
.trim()
.split(/[#;]/)[0]
.trim();
if (k === "key" || k === "stream_key") key = v;
else if (k === "rtmps") rtmps = !/^(0|false|否)/i.test(v);
else if (k === "bitrate") bitrate = v;
else if (k === "fast_audio") fastAudio = /^(1|true|yes|是|开|on)/i.test(v);
}
return { key, rtmps, bitrate, fastAudio };
}
/** 与 web.py `managed_url_config_relpath` 一致(每进程独立,避免 YouTube / TikTok 共写同一文件) */
export function managedUrlConfigRelPathForPm2(pm2: string): string {
return `URL_config.${pm2.replace(/[^a-zA-Z0-9_.-]/g, "_")}.ini`;
}
/** 与 web.py `managed_youtube_ini_relpath` 一致 */
export function managedYoutubeIniRelPathForPm2(pm2: string): string {
return `youtube.${pm2.replace(/[^a-zA-Z0-9_.-]/g, "_")}.ini`;
}
export function buildYoutubeIniFromFields(f: YoutubeIniFields): string {
const bitrateLine = f.bitrate.trim()
? `bitrate = ${f.bitrate.trim()}`
: "# bitrate = 4000";
return `[youtube]
# YouTube 推流 key(工作室 → 直播 → 串流密钥)
key = ${f.key.trim()}
# 使用 RTMPS;填 否 则 RTMP
rtmps = ${f.rtmps ? "是" : "否"}
# 视频比特率 kbps(可选)
${bitrateLine}
# 轻量音频链,减轻 CPU:1 / 是
fast_audio = ${f.fastAudio ? "1" : "0"}
`;
}