Compare commits

...

7 Commits

Author SHA1 Message Date
neru 20e162dc32 chore: remove unused param 2026-05-06 01:00:36 -03:00
neru 09e10e4113 fix: make v3 default 2026-02-13 22:48:32 -03:00
neru cbb5a9a76a fix: uncomment ShortName 2026-02-11 02:39:09 -03:00
neru 9025831f3d feat: use refreshtoken instead of api token 2026-02-11 02:37:02 -03:00
neru 06926e5601 style: run format:apply 2026-02-11 02:36:44 -03:00
neru 85c35021b5 style: run format:apply 2026-02-11 02:36:38 -03:00
neru c44f92f777 fix: dont use any 2026-02-11 02:36:33 -03:00
5 changed files with 146 additions and 121 deletions
-1
View File
@@ -8,7 +8,6 @@ services:
DISCORD_TOKEN: ${DISCORD_TOKEN} DISCORD_TOKEN: ${DISCORD_TOKEN}
DISCORD_OWNER_ID: ${DISCORD_OWNER_ID} DISCORD_OWNER_ID: ${DISCORD_OWNER_ID}
TTS_TIKTOK_SESSIONID: ${TTS_TIKTOK_SESSIONID} TTS_TIKTOK_SESSIONID: ${TTS_TIKTOK_SESSIONID}
TTS_ELEVENLABS_KEY: ${TTS_ELEVENLABS_KEY}
TTS_ELEVENLABS_REFRESHTOKEN: ${TTS_ELEVENLABS_REFRESHTOKEN} TTS_ELEVENLABS_REFRESHTOKEN: ${TTS_ELEVENLABS_REFRESHTOKEN}
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+22 -11
View File
@@ -1,29 +1,34 @@
import { ChatInputCommandInteraction, MessageCreateOptions, MessageFlags, SlashCommandBuilder, TextChannel } from "discord.js"; import {
import { Command } from "../../commands"; ChatInputCommandInteraction,
MessageCreateOptions,
MessageFlags,
SlashCommandBuilder,
TextChannel
} from 'discord.js';
import { Command } from '../../commands';
const builder = new SlashCommandBuilder() const builder = new SlashCommandBuilder()
.setName('bot-mimic') .setName('bot-mimic')
.setDescription('Makes the bot send a message') .setDescription('Makes the bot send a message')
.addStringOption(opt => .addStringOption((opt) =>
opt opt
.setName('content') .setName('content')
.setDescription('The text content of the message') .setDescription('The text content of the message')
.setRequired(false) .setRequired(false)
) )
.addAttachmentOption(opt => .addAttachmentOption((opt) =>
opt opt
.setName('attachment') .setName('attachment')
.setDescription('An attachment for the message') .setDescription('An attachment for the message')
.setRequired(false) .setRequired(false)
) )
.addStringOption(opt => .addStringOption((opt) =>
opt opt
.setName('reply') .setName('reply')
.setDescription('The message ID that the bot should reply to') .setDescription('The message ID that the bot should reply to')
.setRequired(false) .setRequired(false)
); );
const command: Command = { const command: Command = {
name: 'bot-mimic', name: 'bot-mimic',
builder: builder, builder: builder,
@@ -32,7 +37,9 @@ const command: Command = {
await interaction.deferReply({ flags: MessageFlags.Ephemeral }); await interaction.deferReply({ flags: MessageFlags.Ephemeral });
if (!interaction.channel?.isTextBased()) { if (!interaction.channel?.isTextBased()) {
await interaction.editReply('This command can only be used in a text channel.'); await interaction.editReply(
'This command can only be used in a text channel.'
);
return; return;
} }
@@ -46,7 +53,9 @@ const command: Command = {
const replyId = interaction.options.getString('reply'); const replyId = interaction.options.getString('reply');
if (!content && !attachment) { if (!content && !attachment) {
await interaction.editReply('Unable to send empty message. Specify content or attachment, or both.'); await interaction.editReply(
'Unable to send empty message. Specify content or attachment, or both.'
);
return; return;
} }
@@ -70,10 +79,12 @@ const command: Command = {
} }
if (attachment) { if (attachment) {
message.files = [{ message.files = [
{
attachment: attachment.proxyURL, attachment: attachment.proxyURL,
name: attachment.name name: attachment.name
}]; }
];
} }
try { try {
@@ -84,6 +95,6 @@ const command: Command = {
await interaction.editReply('Failed to send message.'); await interaction.editReply('Failed to send message.');
} }
} }
} };
export default command; export default command;
+53 -37
View File
@@ -3,13 +3,13 @@ import { TTSModule, TTSResponse } from '../tts';
import * as https from 'https'; import * as https from 'https';
import { WebSocket } from 'ws' import { WebSocket } from 'ws';
import { Logger } from '../../utils/log'; import { Logger } from '../../utils/log';
const CLIENT_TOKEN = "6A5AA1D4EAFF4E9FB37E23D68491D6F4"; const CLIENT_TOKEN = '6A5AA1D4EAFF4E9FB37E23D68491D6F4';
const AZURE_ENDPOINT = "speech.platform.bing.com"; const AZURE_ENDPOINT = 'speech.platform.bing.com';
const READALOUD_PATH = `/consumer/speech/synthesize/readaloud` const READALOUD_PATH = `/consumer/speech/synthesize/readaloud`;
const WEBSOCKET_URL = `wss://${AZURE_ENDPOINT}${READALOUD_PATH}/edge/v1?TrustedClientToken=${CLIENT_TOKEN}`; const WEBSOCKET_URL = `wss://${AZURE_ENDPOINT}${READALOUD_PATH}/edge/v1?TrustedClientToken=${CLIENT_TOKEN}`;
const VOICES_PATH = `${READALOUD_PATH}/voices/list?TrustedClientToken=${CLIENT_TOKEN}`; const VOICES_PATH = `${READALOUD_PATH}/voices/list?TrustedClientToken=${CLIENT_TOKEN}`;
@@ -28,6 +28,13 @@ interface PendingRequest {
audioBuff: Buffer[]; audioBuff: Buffer[];
} }
interface VoiceInfo {
// Name: string;
ShortName: string,
// Gender: string,
// Locale: string,
}
class AzureTTS implements TTSModule { class AzureTTS implements TTSModule {
private voices: Array<string> | undefined = undefined; private voices: Array<string> | undefined = undefined;
@@ -52,26 +59,25 @@ class AzureTTS implements TTSModule {
} }
async getVoices(): Promise<Array<string> | undefined> { async getVoices(): Promise<Array<string> | undefined> {
if (this.voices) if (this.voices) return this.voices;
return this.voices;
const options: https.RequestOptions = { const options: https.RequestOptions = {
hostname: AZURE_ENDPOINT, hostname: AZURE_ENDPOINT,
path: `${VOICES_PATH}&Sec-MS-GEC=${this.genSecToken()}&Sec-MS-GEC-Version=${SEC_VERSION}`, path: `${VOICES_PATH}&Sec-MS-GEC=${this.genSecToken()}&Sec-MS-GEC-Version=${SEC_VERSION}`,
method: 'GET', method: 'GET',
headers: { headers: {
'Pragma': 'no-cache', Pragma: 'no-cache',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
'User-Agent': USER_AGENT, 'User-Agent': USER_AGENT,
"Accept-Encoding": "gzip, deflate, br", 'Accept-Encoding': 'gzip, deflate, br',
"Accept-Language": "en-US,en;q=0.9", 'Accept-Language': 'en-US,en;q=0.9',
"Authority": "speech.platform.bing.com", Authority: 'speech.platform.bing.com',
"Sec-CH-UA": `" Not;A Brand";v="99", "Microsoft Edge";v="${CHROME_VERSION.split('.')[0]}", "Chromium";v="${CHROME_VERSION.split('.')[0]}"`, 'Sec-CH-UA': `" Not;A Brand";v="99", "Microsoft Edge";v="${CHROME_VERSION.split('.')[0]}", "Chromium";v="${CHROME_VERSION.split('.')[0]}"`,
"Sec-CH-UA-Mobile": "?0", 'Sec-CH-UA-Mobile': '?0',
"Accept": "*/*", Accept: '*/*',
"Sec-Fetch-Site": "none", 'Sec-Fetch-Site': 'none',
"Sec-Fetch-Mode": "cors", 'Sec-Fetch-Mode': 'cors',
"Sec-Fetch-Dest": "empty", 'Sec-Fetch-Dest': 'empty'
} }
}; };
@@ -81,14 +87,14 @@ class AzureTTS implements TTSModule {
res.on('data', (chunk) => chunks.push(chunk)); res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => { res.on('end', () => {
const body = Buffer.concat(chunks).toString(); const body = Buffer.concat(chunks).toString();
this.voices = JSON.parse(body).map((v: any) => v.ShortName) this.voices = JSON.parse(body).map((v: VoiceInfo) => v.ShortName);
resolve(this.voices); resolve(this.voices);
}); });
req.on('error', (err) => { req.on('error', (err) => {
throw err; throw err;
}); });
res.on('aborted', () => { res.on('aborted', () => {
throw new Error('Response aborted') throw new Error('Response aborted');
}); });
}); });
req.end(); req.end();
@@ -136,8 +142,8 @@ class AzureTTS implements TTSModule {
host: 'speech.platform.bing.com', host: 'speech.platform.bing.com',
origin: 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold', origin: 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold',
headers: { headers: {
'Pragma': 'no-cache', Pragma: 'no-cache',
'User-Agent': USER_AGENT, 'User-Agent': USER_AGENT
} }
}); });
@@ -166,10 +172,10 @@ class AzureTTS implements TTSModule {
this.handleIncomingMessage(data, isBinary); this.handleIncomingMessage(data, isBinary);
}); });
this.ws.on('close', (code/*, reason*/) => { this.ws.on('close', (/*code, reason*/) => {
this.ready = false; this.ready = false;
// this.log.verbose(`WS Closed: ${code}`); // this.log.verbose(`WS Closed: ${code}`);
this.rejectAllPending(new Error("Connection closed")); this.rejectAllPending(new Error('Connection closed'));
this.scheduleReconnect(); this.scheduleReconnect();
}); });
@@ -203,8 +209,11 @@ class AzureTTS implements TTSModule {
if (message.includes('Path:turn.end')) { if (message.includes('Path:turn.end')) {
request.resolve({ data: Buffer.concat(request.audioBuff) }); request.resolve({ data: Buffer.concat(request.audioBuff) });
this.pendingRequests.delete(reqId); this.pendingRequests.delete(reqId);
} else if (message.includes('Path:turn.error') || message.includes('Path:error')) { } else if (
request.reject(new Error("Azure synthesis error")); message.includes('Path:turn.error') ||
message.includes('Path:error')
) {
request.reject(new Error('Azure synthesis error'));
this.pendingRequests.delete(reqId); this.pendingRequests.delete(reqId);
} }
} }
@@ -218,28 +227,35 @@ class AzureTTS implements TTSModule {
} }
private genSecToken(): string { private genSecToken(): string {
const ticks = BigInt(Math.floor((Date.now() / 1000) + Number(WIN_EPOCH))) * 10000000n const ticks =
const roundedTicks = ticks - (ticks % 3000000000n) BigInt(Math.floor(Date.now() / 1000 + Number(WIN_EPOCH))) * 10000000n;
const roundedTicks = ticks - (ticks % 3000000000n);
const strToHash = `${roundedTicks}${CLIENT_TOKEN}` const strToHash = `${roundedTicks}${CLIENT_TOKEN}`;
const hash = createHash('sha256') const hash = createHash('sha256');
hash.update(strToHash, 'ascii') hash.update(strToHash, 'ascii');
return hash.digest('hex').toUpperCase() return hash.digest('hex').toUpperCase();
} }
private escapeXml(unsafe: string): string { private escapeXml(unsafe: string): string {
return unsafe.replace(/[<>&"']/g, (c) => { return unsafe.replace(/[<>&"']/g, (c) => {
switch (c) { switch (c) {
case '<': return '&lt;' case '<':
case '>': return '&gt;' return '&lt;';
case '&': return '&amp;' case '>':
case '"': return '&quot;' return '&gt;';
case "'": return '&apos;' case '&':
default: return c return '&amp;';
case '"':
return '&quot;';
case "'":
return '&apos;';
default:
return c;
} }
}) });
} }
} }
+8 -7
View File
@@ -71,7 +71,7 @@ export class ElevenLabsTTS implements TTSModule {
constructor() { constructor() {
this.settings = ElevenLabsTTS.DEFAULT_SETTINGS; this.settings = ElevenLabsTTS.DEFAULT_SETTINGS;
this.modelId = 'eleven_flash_v2_5'; this.modelId = 'eleven_v3';
if (this.canBeUsed()) this.initializationPromise = this.init(); if (this.canBeUsed()) this.initializationPromise = this.init();
@@ -148,10 +148,7 @@ export class ElevenLabsTTS implements TTSModule {
} }
canBeUsed(): boolean { canBeUsed(): boolean {
return ( return config.tts_elevenlabs_refreshtoken != undefined;
config.tts_elevenlabs_refreshtoken != undefined &&
config.tts_elevenlabs_key != undefined
);
} }
/* /*
@@ -176,13 +173,15 @@ export class ElevenLabsTTS implements TTSModule {
} }
private async fetchVoices(): Promise<void> { private async fetchVoices(): Promise<void> {
if (!this.session) return;
const opt: https.RequestOptions = { const opt: https.RequestOptions = {
hostname: ELEVENLABS_API_ENDPOINT, hostname: ELEVENLABS_API_ENDPOINT,
path: '/v2/voices', path: '/v2/voices',
method: 'GET', method: 'GET',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'xi-api-key': config.tts_elevenlabs_key, Authorization: `Bearer ${this.session.idToken}`,
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}; };
@@ -211,13 +210,15 @@ export class ElevenLabsTTS implements TTSModule {
} }
private async fetchModels(): Promise<void> { private async fetchModels(): Promise<void> {
if (!this.session) return;
const opt: https.RequestOptions = { const opt: https.RequestOptions = {
hostname: ELEVENLABS_API_ENDPOINT, hostname: ELEVENLABS_API_ENDPOINT,
path: '/v1/models', path: '/v1/models',
method: 'GET', method: 'GET',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'xi-api-key': config.tts_elevenlabs_key, Authorization: `Bearer ${this.session.idToken}`,
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}; };
-2
View File
@@ -6,7 +6,6 @@ export interface Config {
tts_default_mode: string | undefined; tts_default_mode: string | undefined;
tts_default_voice: string | undefined; tts_default_voice: string | undefined;
tts_elevenlabs_key: string | undefined;
tts_elevenlabs_refreshtoken: string | undefined; tts_elevenlabs_refreshtoken: string | undefined;
tts_tiktok_sessionid: string | undefined; tts_tiktok_sessionid: string | undefined;
@@ -30,7 +29,6 @@ function loadConfig(): Config {
owner_id: process.env.DISCORD_OWNER_ID, owner_id: process.env.DISCORD_OWNER_ID,
tts_default_mode: process.env.DEFAULT_TTS_MODE, tts_default_mode: process.env.DEFAULT_TTS_MODE,
tts_default_voice: process.env.DEFAULT_TTS_VOICE, tts_default_voice: process.env.DEFAULT_TTS_VOICE,
tts_elevenlabs_key: process.env.TTS_ELEVENLABS_KEY,
tts_elevenlabs_refreshtoken: process.env.TTS_ELEVENLABS_REFRESHTOKEN, tts_elevenlabs_refreshtoken: process.env.TTS_ELEVENLABS_REFRESHTOKEN,
steam_webapi_key: process.env.STEAM_WEBAPI_KEY, steam_webapi_key: process.env.STEAM_WEBAPI_KEY,
aws_access_id: process.env.AWS_ACCESS_ID, aws_access_id: process.env.AWS_ACCESS_ID,