1 Commits

Author SHA1 Message Date
92d41d33cd improve(tts-voice): isolate per-request LuxTTS cookie jar 2026-03-16 12:03:00 +08:00

View File

@@ -8,13 +8,15 @@
* - curl CLI
*/
import { randomUUID } from 'crypto';
import { spawnSync } from 'child_process';
import { readFileSync, existsSync, unlinkSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
const LUXTTS_BASE = 'http://localhost:7860';
const REF_AUDIO = '/home/selig/LuxTTS/ref_speech.wav';
const ENV_PATH = '/home/selig/LuxTTS/.env';
const COOKIE_JAR = '/tmp/luxtts-skill-cookie';
// Trigger keywords to strip from user message
const TRIGGER_WORDS = [
@@ -38,6 +40,18 @@ interface TtsParams {
tShift: number;
}
function createCookieJarPath(): string {
return join(tmpdir(), `luxtts-skill-cookie-${randomUUID()}.txt`);
}
function safeUnlink(path: string): void {
try {
if (existsSync(path)) unlinkSync(path);
} catch {
// ignore cleanup errors
}
}
/** Read credentials from .env */
function loadCredentials(): { user: string; pass: string } {
try {
@@ -51,10 +65,13 @@ function loadCredentials(): { user: string; pass: string } {
}
/** Ensure we have a valid session cookie */
function ensureCookie(): boolean {
function ensureCookie(cookieJar: string): boolean {
const { user, pass } = loadCredentials();
if (!pass) return false;
// always start fresh to avoid stale session side effects
safeUnlink(cookieJar);
try {
const result = spawnSync(
'curl',
@@ -62,7 +79,7 @@ function ensureCookie(): boolean {
'-s',
'-o', '/dev/null',
'-w', '%{http_code}',
'-c', COOKIE_JAR,
'-c', cookieJar,
'-X', 'POST',
'-d', `username=${user}&password=${pass}`,
`${LUXTTS_BASE}/luxtts/login`,
@@ -71,7 +88,7 @@ function ensureCookie(): boolean {
);
const httpCode = (result.stdout || '').trim();
return result.status === 0 && httpCode === '200' && existsSync(COOKIE_JAR);
return result.status === 0 && httpCode === '200' && existsSync(cookieJar);
} catch {
return false;
}
@@ -120,7 +137,7 @@ function parseMessage(message: string): { text: string; params: TtsParams } {
}
/** Call LuxTTS API to generate speech */
function generateSpeech(text: string, params: TtsParams): string | null {
function generateSpeech(text: string, params: TtsParams, cookieJar: string): string | null {
const timestamp = Date.now();
const outPath = `/tmp/tts_output_${timestamp}.wav`;
@@ -129,7 +146,7 @@ function generateSpeech(text: string, params: TtsParams): string | null {
'-s',
'-o', outPath,
'-w', '%{http_code}',
'-b', COOKIE_JAR,
'-b', cookieJar,
'-X', 'POST',
`${LUXTTS_BASE}/luxtts/api/tts`,
'-F', `ref_audio=@${REF_AUDIO}`,
@@ -151,10 +168,10 @@ function generateSpeech(text: string, params: TtsParams): string | null {
}
// Clean up failed output
if (existsSync(outPath)) unlinkSync(outPath);
safeUnlink(outPath);
return null;
} catch {
if (existsSync(outPath)) unlinkSync(outPath);
safeUnlink(outPath);
return null;
}
}
@@ -180,8 +197,11 @@ export async function handler(ctx: any) {
return { reply: '❌ LuxTTS 服務未啟動,請先執行:`systemctl --user start luxtts`' };
}
const cookieJar = createCookieJarPath();
try {
// Ensure authentication
if (!ensureCookie()) {
if (!ensureCookie(cookieJar)) {
return { reply: '❌ LuxTTS 認證失敗,請檢查 ~/LuxTTS/.env 的帳密設定' };
}
@@ -192,11 +212,11 @@ export async function handler(ctx: any) {
const paramStr = paramDesc.length ? `${paramDesc.join('、')}` : '';
// Generate
const wavPath = generateSpeech(text, params);
const wavPath = generateSpeech(text, params, cookieJar);
if (!wavPath) {
return {
reply: `❌ 語音合成失敗,請稍後再試。`,
reply: '❌ 語音合成失敗,請稍後再試。',
metadata: { text, params, error: true },
};
}
@@ -210,4 +230,7 @@ export async function handler(ctx: any) {
},
files: [wavPath],
};
} finally {
safeUnlink(cookieJar);
}
}