fix(matrix-bots): update to matrix-bot-sdk v0.7 API

- Import LogLevel separately instead of LogService.LogLevel
- Change sendTyping to setTyping
- Use any type for event handler to avoid generic type issues
- Fix Buffer to Uint8Array conversion for OpenAI File API

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-01-28 12:40:28 +01:00
parent 9dfad0128a
commit b50376dfdb
4 changed files with 94 additions and 49 deletions

View file

@ -6,8 +6,7 @@ import {
AutojoinRoomsMixin,
RichConsoleLogger,
LogService,
MessageEvent,
RoomEvent,
LogLevel,
} from 'matrix-bot-sdk';
import { OllamaService } from '../ollama/ollama.service';
import { SYSTEM_PROMPTS } from '../config/configuration';
@ -45,7 +44,7 @@ export class MatrixService implements OnModuleInit, OnModuleDestroy {
// Setup logging
LogService.setLogger(new RichConsoleLogger());
LogService.setLevel(LogService.LogLevel.INFO);
LogService.setLevel(LogLevel.INFO);
// Storage for sync token persistence
const storage = new SimpleFsStorageProvider(storagePath || './data/bot-storage.json');
@ -91,7 +90,7 @@ export class MatrixService implements OnModuleInit, OnModuleDestroy {
return this.sessions.get(senderId)!;
}
private async handleRoomMessage(roomId: string, event: RoomEvent<MessageEvent>) {
private async handleRoomMessage(roomId: string, event: any) {
// Ignore messages from self
if (event.sender === this.botUserId) return;
@ -102,7 +101,7 @@ export class MatrixService implements OnModuleInit, OnModuleDestroy {
}
// Only handle text messages
const content = event.content;
const content = event.content as { msgtype?: string; body?: string };
if (content.msgtype !== 'm.text') return;
const body = content.body;
@ -151,7 +150,10 @@ export class MatrixService implements OnModuleInit, OnModuleDestroy {
break;
default:
await this.sendMessage(roomId, `Unbekannter Befehl: !${command}\n\nVerwende !help für eine Liste der Befehle.`);
await this.sendMessage(
roomId,
`Unbekannter Befehl: !${command}\n\nVerwende !help für eine Liste der Befehle.`
);
}
}
@ -197,13 +199,19 @@ Schreibe einfach eine Nachricht und ich antworte!
})
.join('\n');
await this.sendMessage(roomId, `**Verfügbare Modelle:**\n\n${modelList}\n\nWechseln mit: \`!model [name]\``);
await this.sendMessage(
roomId,
`**Verfügbare Modelle:**\n\n${modelList}\n\nWechseln mit: \`!model [name]\``
);
}
private async setModel(roomId: string, sender: string, modelName: string) {
if (!modelName) {
const session = this.getSession(sender);
await this.sendMessage(roomId, `Aktuelles Modell: \`${session.model}\`\n\nVerwendung: \`!model gemma3:4b\``);
await this.sendMessage(
roomId,
`Aktuelles Modell: \`${session.model}\`\n\nVerwendung: \`!model gemma3:4b\``
);
return;
}
@ -212,7 +220,10 @@ Schreibe einfach eine Nachricht und ich antworte!
if (!exists) {
const available = models.map((m) => m.name).join(', ');
await this.sendMessage(roomId, `Modell "${modelName}" nicht gefunden.\n\nVerfügbar: ${available}`);
await this.sendMessage(
roomId,
`Modell "${modelName}" nicht gefunden.\n\nVerfügbar: ${available}`
);
return;
}
@ -230,14 +241,21 @@ Schreibe einfach eine Nachricht und ich antworte!
if (!mode) {
const session = this.getSession(sender);
const currentMode =
Object.entries(SYSTEM_PROMPTS).find(([_, v]) => v === session.systemPrompt)?.[0] || 'custom';
await this.sendMessage(roomId, `Aktueller Modus: \`${currentMode}\`\n\nVerfügbar: ${availableModes.join(', ')}`);
Object.entries(SYSTEM_PROMPTS).find(([_, v]) => v === session.systemPrompt)?.[0] ||
'custom';
await this.sendMessage(
roomId,
`Aktueller Modus: \`${currentMode}\`\n\nVerfügbar: ${availableModes.join(', ')}`
);
return;
}
const normalizedMode = mode.toLowerCase();
if (!SYSTEM_PROMPTS[normalizedMode]) {
await this.sendMessage(roomId, `Unbekannter Modus: ${mode}\n\nVerfügbar: ${availableModes.join(', ')}`);
await this.sendMessage(
roomId,
`Unbekannter Modus: ${mode}\n\nVerfügbar: ${availableModes.join(', ')}`
);
return;
}
@ -277,7 +295,7 @@ Schreibe einfach eine Nachricht und ich antworte!
const session = this.getSession(sender);
// Send typing indicator
await this.client.sendTyping(roomId, true, 30000);
await this.client.setTyping(roomId, true, 30000);
try {
// Add user message to history
@ -300,12 +318,12 @@ Schreibe einfach eine Nachricht und ich antworte!
session.history.push({ role: 'assistant', content: response });
// Stop typing indicator
await this.client.sendTyping(roomId, false);
await this.client.setTyping(roomId, false);
// Send response (Matrix has higher message limits than Telegram)
await this.sendMessage(roomId, response);
} catch (error) {
await this.client.sendTyping(roomId, false);
await this.client.setTyping(roomId, false);
this.logger.error(`Error processing message:`, error);
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
await this.sendMessage(roomId, `❌ Fehler: ${errorMessage}`);
@ -325,16 +343,18 @@ Schreibe einfach eine Nachricht und ich antworte!
}
private markdownToHtml(markdown: string): string {
return markdown
// Code blocks
.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>')
// Inline code
.replace(/`([^`]+)`/g, '<code>$1</code>')
// Bold
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
// Italic
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
// Line breaks
.replace(/\n/g, '<br/>');
return (
markdown
// Code blocks
.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>')
// Inline code
.replace(/`([^`]+)`/g, '<code>$1</code>')
// Bold
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
// Italic
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
// Line breaks
.replace(/\n/g, '<br/>')
);
}
}

View file

@ -6,8 +6,7 @@ import {
AutojoinRoomsMixin,
RichConsoleLogger,
LogService,
MessageEvent,
RoomEvent,
LogLevel,
} from 'matrix-bot-sdk';
import { ProjectService } from '../project/project.service';
import { MediaService } from '../media/media.service';
@ -44,7 +43,7 @@ export class MatrixService implements OnModuleInit, OnModuleDestroy {
}
LogService.setLogger(new RichConsoleLogger());
LogService.setLevel(LogService.LogLevel.INFO);
LogService.setLevel(LogLevel.INFO);
const storage = new SimpleFsStorageProvider(storagePath || './data/bot-storage.json');
this.client = new MatrixClient(homeserverUrl!, accessToken, storage);
@ -71,15 +70,15 @@ export class MatrixService implements OnModuleInit, OnModuleDestroy {
return this.allowedUsers.includes(userId);
}
private async handleRoomMessage(roomId: string, event: RoomEvent<MessageEvent>) {
private async handleRoomMessage(roomId: string, event: any) {
if (event.sender === this.botUserId) return;
if (!this.isAllowed(event.sender)) return;
const content = event.content;
const content = event.content as { msgtype?: string; body?: string; url?: string; info?: any };
const msgtype = content.msgtype;
if (msgtype === 'm.text') {
const body = content.body;
const body = content.body || '';
if (body.startsWith('!')) {
await this.handleCommand(roomId, event.sender, body);
} else {
@ -167,7 +166,10 @@ ${styles}
private async createProject(roomId: string, sender: string, name: string) {
if (!name) {
await this.sendMessage(roomId, 'Verwendung: `!new Projektname`\n\nBeispiel: `!new Gartenhaus-Renovierung`');
await this.sendMessage(
roomId,
'Verwendung: `!new Projektname`\n\nBeispiel: `!new Gartenhaus-Renovierung`'
);
return;
}
@ -185,7 +187,10 @@ ${styles}
);
} catch (error) {
this.logger.error('Failed to create project:', error);
await this.sendMessage(roomId, `❌ Fehler: ${error instanceof Error ? error.message : 'Unbekannt'}`);
await this.sendMessage(
roomId,
`❌ Fehler: ${error instanceof Error ? error.message : 'Unbekannt'}`
);
}
}
@ -208,12 +213,18 @@ ${styles}
})
);
await this.sendMessage(roomId, `**📂 Deine Projekte:**\n\n${projectList.join('\n\n')}\n\nWechseln mit: \`!switch [ID]\``);
await this.sendMessage(
roomId,
`**📂 Deine Projekte:**\n\n${projectList.join('\n\n')}\n\nWechseln mit: \`!switch [ID]\``
);
}
private async switchProject(roomId: string, sender: string, idPrefix: string) {
if (!idPrefix) {
await this.sendMessage(roomId, 'Verwendung: `!switch [ID]`\n\nZeige Projekte mit `!projects`');
await this.sendMessage(
roomId,
'Verwendung: `!switch [ID]`\n\nZeige Projekte mit `!projects`'
);
return;
}
@ -278,7 +289,10 @@ ${styles}
.map(([key, value]) => `**${key}** - ${value.name}\n_${value.prompt.slice(0, 80)}..._`)
.join('\n\n');
await this.sendMessage(roomId, `**📝 Verfügbare Blog-Stile:**\n\n${styles}\n\nVerwendung: \`!generate [stil]\``);
await this.sendMessage(
roomId,
`**📝 Verfügbare Blog-Stile:**\n\n${styles}\n\nVerwendung: \`!generate [stil]\``
);
}
private async generateBlogpost(roomId: string, sender: string, style: string) {
@ -300,18 +314,21 @@ ${styles}
}
await this.sendMessage(roomId, '🚀 Generiere Blogbeitrag...\n\nDas kann einen Moment dauern.');
await this.client.sendTyping(roomId, true, 60000);
await this.client.setTyping(roomId, true, 60000);
try {
const content = await this.generationService.generateBlogpost(projectId, selectedStyle);
await this.client.sendTyping(roomId, false);
await this.client.setTyping(roomId, false);
await this.sendMessage(roomId, content);
await this.sendMessage(roomId, '✅ Blogbeitrag erstellt!\n\nExportieren mit `!export`');
} catch (error) {
await this.client.sendTyping(roomId, false);
await this.client.setTyping(roomId, false);
this.logger.error('Generation failed:', error);
await this.sendMessage(roomId, `❌ Fehler: ${error instanceof Error ? error.message : 'Unbekannt'}`);
await this.sendMessage(
roomId,
`❌ Fehler: ${error instanceof Error ? error.message : 'Unbekannt'}`
);
}
}
@ -324,7 +341,10 @@ ${styles}
const latest = await this.generationService.getLatestGeneration(projectId);
if (!latest) {
await this.sendMessage(roomId, 'Noch kein Blogbeitrag generiert.\n\nErstelle einen mit `!generate`');
await this.sendMessage(
roomId,
'Noch kein Blogbeitrag generiert.\n\nErstelle einen mit `!generate`'
);
return;
}
@ -404,7 +424,13 @@ ${styles}
const contentType = content.info?.mimetype || 'audio/ogg';
const duration = Math.round((content.info?.duration || 0) / 1000);
const item = await this.mediaService.processVoice(projectId, buffer, contentType, mxcUrl, duration);
const item = await this.mediaService.processVoice(
projectId,
buffer,
contentType,
mxcUrl,
duration
);
const stats = await this.projectService.getStats(projectId);
let reply = `✅ Sprachnotiz gespeichert! (${stats.voices} gesamt)`;

View file

@ -27,7 +27,7 @@ export class TranscriptionService {
}
// Create a File-like object for the API
const file = new File([audioBuffer], 'audio.ogg', { type: 'audio/ogg' });
const file = new File([new Uint8Array(audioBuffer)], 'audio.ogg', { type: 'audio/ogg' });
const response = await this.openai.audio.transcriptions.create({
file,

View file

@ -6,8 +6,7 @@ import {
AutojoinRoomsMixin,
RichConsoleLogger,
LogService,
MessageEvent,
RoomEvent,
LogLevel,
} from 'matrix-bot-sdk';
import { AnalyticsService } from '../analytics/analytics.service';
import { UsersService } from '../users/users.service';
@ -38,7 +37,7 @@ export class MatrixService implements OnModuleInit, OnModuleDestroy {
}
LogService.setLogger(new RichConsoleLogger());
LogService.setLevel(LogService.LogLevel.INFO);
LogService.setLevel(LogLevel.INFO);
const storage = new SimpleFsStorageProvider(storagePath || './data/bot-storage.json');
this.client = new MatrixClient(homeserverUrl!, accessToken, storage);
@ -61,10 +60,10 @@ export class MatrixService implements OnModuleInit, OnModuleDestroy {
}
}
private async handleRoomMessage(roomId: string, event: RoomEvent<MessageEvent>) {
private async handleRoomMessage(roomId: string, event: any) {
if (event.sender === this.botUserId) return;
const content = event.content;
const content = event.content as { msgtype?: string; body?: string };
if (content.msgtype !== 'm.text') return;
const body = content.body;