feat: add voice transcription support to Matrix bots

Add TranscriptionModule and handleAudioMessage to enable voice message
transcription across all Matrix bots. Users can now send voice messages
which are automatically transcribed and processed as text commands.

Affected bots:
- matrix-calendar-bot
- matrix-chat-bot
- matrix-contacts-bot
- matrix-manadeck-bot
- matrix-ollama-bot
- matrix-picture-bot
- matrix-planta-bot
- matrix-presi-bot
- matrix-questions-bot
- matrix-skilltree-bot
- matrix-stats-bot
- matrix-storage-bot
- matrix-tts-bot

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-01 03:37:30 +01:00
parent 12f1288aec
commit c29939e7bc
30 changed files with 934 additions and 459 deletions

910
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,15 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { CalendarModule } from '../calendar/calendar.module';
import { TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [CalendarModule],
imports: [
CalendarModule,
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -7,6 +7,7 @@ import {
KeywordCommandDetector,
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { TranscriptionService } from '@manacore/bot-services';
import { CalendarService, CalendarEvent } from '../calendar/calendar.service';
import { HELP_TEXT, WELCOME_TEXT, BOT_INTRODUCTION } from '../config/configuration';
@ -27,11 +28,36 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private readonly transcriptionService: TranscriptionService,
private calendarService: CalendarService
) {
super(configService);
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendReply(roomId, event, '❌ Sprachnachricht konnte nicht erkannt werden.');
return;
}
await this.sendMessage(roomId, `🎤 *"${text}"*`);
await this.handleTextMessage(roomId, event, text, sender);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendReply(roomId, event, '❌ Fehler bei der Spracherkennung.');
}
}
protected getConfig(): MatrixBotConfig {
return {
homeserverUrl: this.configService.get<string>('matrix.homeserverUrl') || 'http://localhost:8008',

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { ChatModule } from '../chat/chat.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [ChatModule, SessionModule.forRoot()],
imports: [
ChatModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -8,7 +8,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { ChatService, Conversation } from '../chat/chat.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE, BRANCH_ICONS } from '../config/configuration';
@Injectable()
@ -25,12 +25,37 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private readonly transcriptionService: TranscriptionService,
private chatService: ChatService,
private sessionService: SessionService
) {
super(configService);
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendReply(roomId, event, '❌ Sprachnachricht konnte nicht erkannt werden.');
return;
}
await this.sendMessage(roomId, `🎤 *"${text}"*`);
await this.handleTextMessage(roomId, event, text, sender);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendReply(roomId, event, '❌ Fehler bei der Spracherkennung.');
}
}
protected getConfig(): MatrixBotConfig {
return {
homeserverUrl: this.configService.get<string>('matrix.homeserverUrl') || 'http://localhost:8008',

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { ContactsModule } from '../contacts/contacts.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [ContactsModule, SessionModule.forRoot()],
imports: [
ContactsModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -9,7 +9,7 @@ import {
UserListMapper,
} from '@manacore/matrix-bot-common';
import { ContactsService, Contact } from '../contacts/contacts.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
// Natural language keyword detector
@ -27,12 +27,37 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private readonly transcriptionService: TranscriptionService,
private contactsService: ContactsService,
private sessionService: SessionService
) {
super(configService);
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendReply(roomId, event, '❌ Sprachnachricht konnte nicht erkannt werden.');
return;
}
await this.sendMessage(roomId, `🎤 *"${text}"*`);
await this.handleTextMessage(roomId, event, text, sender);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendReply(roomId, event, '❌ Fehler bei der Spracherkennung.');
}
}
protected getConfig(): MatrixBotConfig {
return {
homeserverUrl:

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { ManadeckModule } from '../manadeck/manadeck.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [ManadeckModule, SessionModule.forRoot()],
imports: [
ManadeckModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -9,7 +9,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { ManadeckService, Deck, Card } from '../manadeck/manadeck.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
@Injectable()
@ -34,12 +34,37 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private readonly transcriptionService: TranscriptionService,
private manadeckService: ManadeckService,
private sessionService: SessionService
) {
super(configService);
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendHtml(roomId, '<p>❌ Sprachnachricht konnte nicht erkannt werden.</p>');
return;
}
await this.sendHtml(roomId, `<p>🎤 <em>"${text}"</em></p>`);
await this.handleTextMessage(roomId, event, text, sender);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendHtml(roomId, '<p>❌ Fehler bei der Spracherkennung.</p>');
}
}
protected getConfig(): MatrixBotConfig {
return {
homeserverUrl: this.configService.get<string>('matrix.homeserverUrl') || 'http://localhost:8008',

View file

@ -27,6 +27,7 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@manacore/bot-services": "workspace:*",
"@manacore/matrix-bot-common": "workspace:*",
"@nestjs/common": "^10.4.15",
"@nestjs/config": "^3.3.0",

View file

@ -1,9 +1,15 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { OllamaModule } from '../ollama/ollama.module';
import { TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [OllamaModule],
imports: [
OllamaModule,
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -7,6 +7,7 @@ import {
KeywordCommandDetector,
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { TranscriptionService } from '@manacore/bot-services';
import { OllamaService } from '../ollama/ollama.service';
import { SYSTEM_PROMPTS } from '../config/configuration';
@ -37,11 +38,36 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private readonly transcriptionService: TranscriptionService,
private ollamaService: OllamaService
) {
super(configService);
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendMessage(roomId, '❌ Sprachnachricht konnte nicht erkannt werden.');
return;
}
await this.sendMessage(roomId, `🎤 *"${text}"*`);
await this.handleTextMessage(roomId, event, text, sender);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendMessage(roomId, '❌ Fehler bei der Spracherkennung.');
}
}
protected getConfig(): MatrixBotConfig {
return {
homeserverUrl:

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { PictureModule } from '../picture/picture.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [PictureModule, SessionModule.forRoot()],
imports: [
PictureModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -8,7 +8,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { PictureService } from '../picture/picture.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
interface ParsedPrompt {
@ -36,12 +36,37 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private readonly transcriptionService: TranscriptionService,
private pictureService: PictureService,
private sessionService: SessionService
) {
super(configService);
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendMessage(roomId, '❌ Sprachnachricht konnte nicht erkannt werden.');
return;
}
await this.sendMessage(roomId, `🎤 *"${text}"*`);
await this.handleTextMessage(roomId, event, text, sender);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendMessage(roomId, '❌ Fehler bei der Spracherkennung.');
}
}
protected getConfig(): MatrixBotConfig {
return {
homeserverUrl: this.configService.get<string>('matrix.homeserverUrl') || 'http://localhost:8008',

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { PlantaModule } from '../planta/planta.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [PlantaModule, SessionModule.forRoot()],
imports: [
PlantaModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -9,7 +9,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { PlantaService, Plant } from '../planta/planta.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
@Injectable()
@ -49,12 +49,37 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private readonly transcriptionService: TranscriptionService,
private plantaService: PlantaService,
private sessionService: SessionService
) {
super(configService);
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendMessage(roomId, '<p>❌ Sprachnachricht konnte nicht erkannt werden.</p>');
return;
}
await this.sendMessage(roomId, `<p>🎤 <em>"${text}"</em></p>`);
await this.handleTextMessage(roomId, event, text);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendMessage(roomId, '<p>❌ Fehler bei der Spracherkennung.</p>');
}
}
protected getConfig(): MatrixBotConfig {
return {
homeserverUrl: this.configService.get<string>('matrix.homeserverUrl') || 'http://localhost:8008',

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { PresiModule } from '../presi/presi.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [PresiModule, SessionModule.forRoot()],
imports: [
PresiModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -9,7 +9,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { PresiService, Deck, Theme, SlideContent } from '../presi/presi.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
@Injectable()
@ -30,12 +30,37 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private readonly transcriptionService: TranscriptionService,
private presiService: PresiService,
private sessionService: SessionService
) {
super(configService);
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendMessage(roomId, '<p>❌ Sprachnachricht konnte nicht erkannt werden.</p>');
return;
}
await this.sendMessage(roomId, `<p>🎤 <em>"${text}"</em></p>`);
await this.handleTextMessage(roomId, event, text);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendMessage(roomId, '<p>❌ Fehler bei der Spracherkennung.</p>');
}
}
protected getConfig(): MatrixBotConfig {
return {
homeserverUrl:

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { QuestionsModule } from '../questions/questions.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [QuestionsModule, SessionModule.forRoot()],
imports: [
QuestionsModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -9,7 +9,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { QuestionsService, Question, Collection, Answer } from '../questions/questions.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
@Injectable()
@ -33,7 +33,8 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private questionsService: QuestionsService,
private sessionService: SessionService
private sessionService: SessionService,
private readonly transcriptionService: TranscriptionService
) {
super(configService);
}
@ -176,6 +177,30 @@ export class MatrixService extends BaseMatrixService {
}
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
_sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendReply(roomId, event, '<p>Sprachnachricht konnte nicht erkannt werden.</p>');
return;
}
await this.sendMessage(roomId, `<p><em>"${text}"</em></p>`);
await this.handleTextMessage(roomId, event, text);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendReply(roomId, event, '<p>Fehler bei der Spracherkennung.</p>');
}
}
private requireAuth(sender: string): string {
const token = this.sessionService.getToken(sender);
if (!token) {

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { SkilltreeModule } from '../skilltree/skilltree.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [SkilltreeModule, SessionModule.forRoot()],
imports: [
SkilltreeModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -9,7 +9,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { SkilltreeService, Skill, SkillBranch } from '../skilltree/skilltree.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
@Injectable()
@ -54,7 +54,8 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private skilltreeService: SkilltreeService,
private sessionService: SessionService
private sessionService: SessionService,
private readonly transcriptionService: TranscriptionService
) {
super(configService);
}
@ -163,6 +164,30 @@ export class MatrixService extends BaseMatrixService {
}
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
_sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendReply(roomId, event, '<p>Sprachnachricht konnte nicht erkannt werden.</p>');
return;
}
await this.sendMessage(roomId, `<p><em>"${text}"</em></p>`);
await this.handleTextMessage(roomId, event, text);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendReply(roomId, event, '<p>Fehler bei der Spracherkennung.</p>');
}
}
private requireAuth(sender: string): string {
const token = this.sessionService.getToken(sender);
if (!token) {

View file

@ -24,6 +24,7 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@manacore/bot-services": "workspace:*",
"@manacore/matrix-bot-common": "workspace:*",
"@nestjs/common": "^10.4.15",
"@nestjs/config": "^3.3.0",

View file

@ -2,9 +2,16 @@ import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { AnalyticsModule } from '../analytics/analytics.module';
import { UsersModule } from '../users/users.module';
import { TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [AnalyticsModule, UsersModule],
imports: [
AnalyticsModule,
UsersModule,
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -9,6 +9,7 @@ import {
} from '@manacore/matrix-bot-common';
import { AnalyticsService } from '../analytics/analytics.service';
import { UsersService } from '../users/users.service';
import { TranscriptionService } from '@manacore/bot-services';
@Injectable()
export class MatrixService extends BaseMatrixService {
@ -26,7 +27,8 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private analyticsService: AnalyticsService,
private usersService: UsersService
private usersService: UsersService,
private readonly transcriptionService: TranscriptionService
) {
super(configService);
this.reportRoomId = this.configService.get<string>('matrix.reportRoomId') || '';
@ -60,6 +62,30 @@ export class MatrixService extends BaseMatrixService {
await this.handleCommand(roomId, command.toLowerCase());
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendReply(roomId, event, 'Sprachnachricht konnte nicht erkannt werden.');
return;
}
await this.sendMessage(roomId, `*"${text}"*`);
await this.handleTextMessage(roomId, event, text, sender);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendReply(roomId, event, 'Fehler bei der Spracherkennung.');
}
}
private async handleCommand(roomId: string, command: string) {
switch (command) {
case 'help':

View file

@ -1,10 +1,16 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { StorageModule } from '../storage/storage.module';
import { SessionModule } from '@manacore/bot-services';
import { SessionModule, TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [StorageModule, SessionModule.forRoot()],
imports: [
StorageModule,
SessionModule.forRoot(),
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
exports: [MatrixService],
})

View file

@ -9,7 +9,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { StorageService, StorageFile, Folder, ShareLink, TrashItem } from '../storage/storage.service';
import { SessionService } from '@manacore/bot-services';
import { SessionService, TranscriptionService } from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
@Injectable()
@ -35,7 +35,8 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private storageService: StorageService,
private sessionService: SessionService
private sessionService: SessionService,
private readonly transcriptionService: TranscriptionService
) {
super(configService);
}
@ -203,6 +204,30 @@ export class MatrixService extends BaseMatrixService {
}
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
_sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendReply(roomId, event, '<p>Sprachnachricht konnte nicht erkannt werden.</p>');
return;
}
await this.sendMessage(roomId, `<p><em>"${text}"</em></p>`);
await this.handleTextMessage(roomId, event, text);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendReply(roomId, event, '<p>Fehler bei der Spracherkennung.</p>');
}
}
private requireAuth(sender: string): string {
const token = this.sessionService.getToken(sender);
if (!token) {

View file

@ -24,6 +24,7 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@manacore/bot-services": "workspace:*",
"@manacore/matrix-bot-common": "workspace:*",
"@nestjs/common": "^10.4.17",
"@nestjs/config": "^3.3.0",

View file

@ -1,9 +1,15 @@
import { Module } from '@nestjs/common';
import { MatrixService } from './matrix.service';
import { TtsModule } from '../tts/tts.module';
import { TranscriptionModule } from '@manacore/bot-services';
@Module({
imports: [TtsModule],
imports: [
TtsModule,
TranscriptionModule.register({
sttUrl: process.env.STT_URL || 'http://localhost:3020',
}),
],
providers: [MatrixService],
})
export class BotModule {}

View file

@ -8,6 +8,7 @@ import {
COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { TtsService } from '../tts/tts.service';
import { TranscriptionService } from '@manacore/bot-services';
import { HELP_TEXT, WELCOME_TEXT } from '../config/configuration';
interface UserSettings {
@ -36,7 +37,8 @@ export class MatrixService extends BaseMatrixService {
constructor(
configService: ConfigService,
private ttsService: TtsService
private ttsService: TtsService,
private readonly transcriptionService: TranscriptionService
) {
super(configService);
this.defaultVoice = this.configService.get<string>('tts.defaultVoice') || 'af_heart';
@ -123,6 +125,30 @@ export class MatrixService extends BaseMatrixService {
}
}
protected override async handleAudioMessage(
roomId: string,
event: MatrixRoomEvent,
_sender: string
): Promise<void> {
try {
const mxcUrl = event.content.url;
if (!mxcUrl) return;
const audioBuffer = await this.downloadMedia(mxcUrl);
const text = await this.transcriptionService.transcribe(audioBuffer);
if (!text) {
await this.sendReply(roomId, event, 'Sprachnachricht konnte nicht erkannt werden.');
return;
}
await this.sendMessage(roomId, `*"${text}"*`);
await this.handleTextMessage(roomId, event, text);
} catch (error) {
this.logger.error(`Audio transcription error: ${error}`);
await this.sendReply(roomId, event, 'Fehler bei der Spracherkennung.');
}
}
private async executeCommand(
roomId: string,
event: MatrixRoomEvent,