fix(matrix-tts-bot): add API key authentication for mana-tts service

- Add TTS_API_KEY config option to configuration.ts
- Send X-API-Key header in TtsService requests
- Update docker-compose.macmini.yml with TTS_INTERNAL_API_KEY env var
- Update .env.example and CLAUDE.md documentation

The mana-tts service requires authentication, but the TTS bot was not
sending the required X-API-Key header, causing 401 errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-14 10:59:08 +01:00
parent 7c0bc735b9
commit 1c9c2301a5
5 changed files with 18 additions and 2 deletions

View file

@ -973,6 +973,7 @@ services:
MATRIX_ACCESS_TOKEN: ${MATRIX_TTS_BOT_TOKEN}
MATRIX_ALLOWED_ROOMS: ${MATRIX_TTS_BOT_ROOMS:-}
TTS_URL: http://host.docker.internal:3022
TTS_API_KEY: ${TTS_INTERNAL_API_KEY:-}
DEFAULT_VOICE: af_heart
DEFAULT_SPEED: 1.0
MAX_TEXT_LENGTH: 500

View file

@ -9,6 +9,7 @@ MATRIX_STORAGE_PATH=./data/bot-storage.json
# TTS Service (mana-tts)
TTS_URL=http://localhost:3022
TTS_API_KEY=sk-internal-xxx # Internal API key for mana-tts service
# TTS Defaults
DEFAULT_VOICE=af_heart

View file

@ -77,6 +77,7 @@ MATRIX_STORAGE_PATH=./data/bot-storage.json
# TTS Service
TTS_URL=http://localhost:3022
TTS_API_KEY=sk-internal-xxx
# Defaults
DEFAULT_VOICE=af_heart

View file

@ -8,6 +8,7 @@ export default () => ({
},
tts: {
url: process.env.TTS_URL || 'http://localhost:3022',
apiKey: process.env.TTS_API_KEY || '',
defaultVoice: process.env.DEFAULT_VOICE || 'af_heart',
defaultSpeed: parseFloat(process.env.DEFAULT_SPEED || '1.0'),
maxTextLength: parseInt(process.env.MAX_TEXT_LENGTH || '500', 10),

View file

@ -17,9 +17,11 @@ export interface VoicesResponse {
export class TtsService {
private readonly logger = new Logger(TtsService.name);
private readonly ttsUrl: string;
private readonly apiKey: string;
constructor(private configService: ConfigService) {
this.ttsUrl = this.configService.get<string>('tts.url', 'http://localhost:3022');
this.apiKey = this.configService.get<string>('tts.apiKey', '');
}
/**
@ -32,9 +34,14 @@ export class TtsService {
`Synthesizing: "${text.substring(0, 50)}..." with voice=${voice}, speed=${speed}`
);
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (this.apiKey) {
headers['X-API-Key'] = this.apiKey;
}
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers,
body: JSON.stringify({
text,
voice,
@ -61,7 +68,12 @@ export class TtsService {
async getVoices(): Promise<VoicesResponse> {
const url = `${this.ttsUrl}/voices`;
const response = await fetch(url);
const headers: Record<string, string> = {};
if (this.apiKey) {
headers['X-API-Key'] = this.apiKey;
}
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`Failed to get voices: ${response.status}`);