From b246afdc7ff6d8d15a8ced2d065f66678deb7cee Mon Sep 17 00:00:00 2001 From: neru Date: Thu, 29 Jan 2026 01:04:19 -0300 Subject: [PATCH] feat: add ElevenLabs Firebase token emulation --- docker-compose.yml | 2 +- src/modules/tts-modes/elevenlabs.ts | 78 +++++++++++++++++++++++------ src/utils/config.ts | 4 +- 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 040566c..9beac41 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: DISCORD_TOKEN: TTS_TIKTOK_SESSIONID: TTS_ELEVENLABS_KEY: - TTS_ELEVENLABS_TOKEN: + TTS_ELEVENLABS_REFRESHTOKEN: restart: unless-stopped volumes: - ./db.sqlite:/app/db.sqlite \ No newline at end of file diff --git a/src/modules/tts-modes/elevenlabs.ts b/src/modules/tts-modes/elevenlabs.ts index bc90c9b..2eaff95 100644 --- a/src/modules/tts-modes/elevenlabs.ts +++ b/src/modules/tts-modes/elevenlabs.ts @@ -5,6 +5,10 @@ import * as https from 'https'; const ELEVENLABS_API_ENDPOINT = 'api.elevenlabs.io'; +const FIREBASE_API_KEY = "AIzaSyBSsRE_1Os04-bxpd5JTLIniy3UK4OqKys"; +const FIREBASE_URL = `https://securetoken.googleapis.com/v1/token?key=${FIREBASE_API_KEY}`; + + /* TO-DO: Implement previous text */ @@ -39,6 +43,12 @@ interface ElevenLabsStreamRequest { voice_settings: ElevenLabsVoiceSettings; } +interface FirebaseSession { + idToken: string; + refreshToken: string; + expiresAt: number; +} + export class ElevenLabsTTS implements TTSModule { private voices: Array | undefined = undefined; private models: Array | undefined = undefined; @@ -48,29 +58,38 @@ export class ElevenLabsTTS implements TTSModule { public settings: ElevenLabsVoiceSettings; public modelId: string; + private session: FirebaseSession | undefined = undefined; + + private initializationPromise: Promise | undefined = undefined; + public static readonly DEFAULT_SETTINGS: ElevenLabsVoiceSettings = { - stability: 0.5, + stability: 0.0, similarity_boost: 0.5, - style: 0.0, + style: 1.0, speed: 1.0, user_speaker_boost: true }; constructor() { - if (this.canBeUsed()) { - this.fetchVoices(); - this.fetchModels(); - } - this.settings = ElevenLabsTTS.DEFAULT_SETTINGS; - this.modelId = 'eleven_flash_v2_5'; + if (this.canBeUsed()) + this.initializationPromise = this.init(); + this.setSettings = this.setSettings.bind(this); this.setModel = this.setModel.bind(this); this.getModels = this.getModels.bind(this); } + private async init(): Promise { + await this.ensureSession(); + await Promise.all([ + this.fetchVoices(), + this.fetchModels() + ]); + } + /* TTSModule methods */ @@ -79,7 +98,11 @@ export class ElevenLabsTTS implements TTSModule { } async generate(voice: string, text: string): Promise { - if (!this.voices) return {}; + await this.initializationPromise; + await this.ensureSession(); + + if (!this.voices) return { error: 'no voices' }; + if (!this.session) return { error: 'no session' }; const voiceData = this.voices.find((entry) => entry.name === voice); if (!voiceData) return { error: 'Invalid voice' }; @@ -100,10 +123,8 @@ export class ElevenLabsTTS implements TTSModule { 'Sec-Fetch-Site': 'same-site', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty', - host: 'api.us.elevenlabs.io', - ...(config.tts_elevenlabs_token - ? { Authorization: `Bearer ${config.tts_elevenlabs_token}` } - : { 'xi-api-key': config.tts_elevenlabs_key }) + host: 'api.elevenlabs.io', + Authorization: `Bearer ${this.session.idToken}` } }; @@ -132,7 +153,7 @@ export class ElevenLabsTTS implements TTSModule { } canBeUsed(): boolean { - return config.tts_elevenlabs_key != undefined; + return config.tts_elevenlabs_refreshtoken != undefined && config.tts_elevenlabs_key != undefined; } /* @@ -221,6 +242,35 @@ export class ElevenLabsTTS implements TTSModule { }); }); } + + private async ensureSession(): Promise { + if (this.session && Date.now() < this.session.expiresAt - 300000) + return; + + const refreshToken = this.session?.refreshToken || config.tts_elevenlabs_refreshtoken; + if (!refreshToken) throw new Error("No refresh token available"); + + const response = await fetch(FIREBASE_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Referer': 'https://elevenlabs.io/', + 'Origin': 'https://elevenlabs.io' + }, + body: new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: refreshToken, + }), + }); + + if (!response.ok) throw new Error(`Auth Refresh Failed: ${await response.text()}`); + const data = await response.json(); + this.session = { + idToken: data.id_token, + refreshToken: data.refresh_token, + expiresAt: Date.now() + (parseInt(data.expires_in) * 1000) + }; + } } export default new ElevenLabsTTS(); diff --git a/src/utils/config.ts b/src/utils/config.ts index 5351dad..4e00e93 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -7,7 +7,7 @@ export interface Config { tts_default_voice: string | undefined; tts_elevenlabs_key: string | undefined; - tts_elevenlabs_token: string | undefined; + tts_elevenlabs_refreshtoken: string | undefined; tts_tiktok_sessionid: string | undefined; steam_webapi_key: string | undefined; @@ -31,7 +31,7 @@ function loadConfig(): Config { tts_default_mode: process.env.DEFAULT_TTS_MODE, tts_default_voice: process.env.DEFAULT_TTS_VOICE, tts_elevenlabs_key: process.env.TTS_ELEVENLABS_KEY, - tts_elevenlabs_token: process.env.TTS_ELEVENLABS_TOKEN, + tts_elevenlabs_refreshtoken: process.env.TTS_ELEVENLABS_REFRESHTOKEN, steam_webapi_key: process.env.STEAM_WEBAPI_KEY, aws_access_id: process.env.AWS_ACCESS_ID, aws_access_key: process.env.AWS_ACCESS_KEY,