🩹 fix(matrix-bots): use authenticated media download for all bots

The Matrix Media API now requires authentication (spec v1.11+). Updated
all 5 affected bots to use downloadMedia() from BaseMatrixService which
handles authenticated downloads via /_matrix/client/v1/media/download/.

Affected bots:
- matrix-nutriphi-bot (images + audio)
- matrix-zitare-bot (audio)
- matrix-todo-bot (audio)
- matrix-ollama-bot (images)
- matrix-project-doc-bot (images + audio)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-14 11:04:13 +01:00
parent 70e45ed82e
commit fa7fb3c294
4 changed files with 44 additions and 61 deletions

View file

@ -654,18 +654,11 @@ Schreibe einfach eine Nachricht und ich antworte!
}
private async downloadMatrixImage(mxcUrl: string): Promise<string> {
// Convert mxc:// URL to HTTP URL and download
const httpUrl = this.client.mxcToHttp(mxcUrl);
this.logger.log(`Downloading image from ${httpUrl}`);
this.logger.log(`Downloading image from ${mxcUrl}`);
const response = await fetch(httpUrl);
if (!response.ok) {
throw new Error(`Failed to download image: ${response.status}`);
}
const buffer = await response.arrayBuffer();
const base64 = Buffer.from(buffer).toString('base64');
return base64;
// Use the authenticated download method from BaseMatrixService
const buffer = await this.downloadMedia(mxcUrl);
return buffer.toString('base64');
}
private async pinHelpMessage(roomId: string) {

View file

@ -498,9 +498,7 @@ ${styles}
try {
const mxcUrl = content.url;
const httpUrl = this.client.mxcToHttp(mxcUrl);
const response = await fetch(httpUrl);
const buffer = Buffer.from(await response.arrayBuffer());
const buffer = await this.downloadMedia(mxcUrl);
const contentType = content.info?.mimetype || 'image/jpeg';
await this.mediaService.processPhoto(projectId, buffer, contentType, mxcUrl, content.body);
@ -528,9 +526,7 @@ ${styles}
try {
const mxcUrl = content.url;
const httpUrl = this.client.mxcToHttp(mxcUrl);
const response = await fetch(httpUrl);
const buffer = Buffer.from(await response.arrayBuffer());
const buffer = await this.downloadMedia(mxcUrl);
const contentType = content.info?.mimetype || 'audio/ogg';
const duration = Math.round((content.info?.duration || 0) / 1000);

View file

@ -31,7 +31,10 @@ const TASK_CREATE_CREDITS = 0.02;
type Task = ApiTask;
@Injectable()
export class MatrixService extends BaseMatrixService implements CreditCommandsHost, GiftCommandsHost {
export class MatrixService
extends BaseMatrixService
implements CreditCommandsHost, GiftCommandsHost
{
// Expose services for credit and gift commands mixins
public creditService: CreditService;
public giftService: GiftService;
@ -324,17 +327,11 @@ export class MatrixService extends BaseMatrixService implements CreditCommandsHo
await this.sendReply(roomId, event, 'Verarbeite Sprachnotiz...');
// Download audio from Matrix
// Download audio from Matrix using authenticated API
const mxcUrl = content.url;
const httpUrl = this.client.mxcToHttp(mxcUrl);
this.logger.log(`Downloading audio from ${httpUrl}`);
this.logger.log(`Downloading audio from ${mxcUrl}`);
const response = await fetch(httpUrl);
if (!response.ok) {
throw new Error(`Failed to download audio: ${response.status}`);
}
const buffer = Buffer.from(await response.arrayBuffer());
const buffer = await this.downloadMedia(mxcUrl);
// Transcribe audio
const transcription = await this.transcriptionService.transcribe(buffer);

View file

@ -9,7 +9,12 @@ import {
} from '@manacore/matrix-bot-common';
import { QuotesService } from '../quotes/quotes.service';
import { ZitareService } from '../quotes/zitare.service';
import { SessionService, TranscriptionService, CreditService } from '@manacore/bot-services';
import {
SessionService,
TranscriptionService,
CreditService,
LOGIN_MESSAGES,
} from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';
import type { Category } from '@zitare/content';
@ -103,14 +108,9 @@ Sag "hilfe" fuer alle Befehle!`;
this.logger.log(`Processing voice message from ${sender}`);
try {
// Download audio from Matrix
const httpUrl = this.client.mxcToHttp(content.url);
const response = await fetch(httpUrl);
if (!response.ok) {
throw new Error(`Failed to download audio: ${response.status}`);
}
const audioBuffer = Buffer.from(await response.arrayBuffer());
// Download audio from Matrix using authenticated API
this.logger.log(`Downloading audio from ${content.url}`);
const audioBuffer = await this.downloadMedia(content.url);
// Transcribe
await this.sendMessage(roomId, 'Transkribiere Sprachnotiz...');
@ -388,11 +388,8 @@ Sag "hilfe" fuer alle Befehle!`;
}
private async handleAddFavorite(roomId: string, sender: string) {
const token = await this.sessionService.getToken(sender);
if (!token) {
await this.sendMessage(roomId, `Du bist nicht angemeldet. Nutze \`!login\` zuerst.`);
return;
}
const token = await this.requireLogin(roomId, sender);
if (!token) return;
const lastQuoteId = this.lastQuotes.get(sender);
if (!lastQuoteId) {
@ -418,11 +415,8 @@ Sag "hilfe" fuer alle Befehle!`;
}
private async handleFavorites(roomId: string, sender: string) {
const token = await this.sessionService.getToken(sender);
if (!token) {
await this.sendMessage(roomId, `Du bist nicht angemeldet. Nutze \`!login\` zuerst.`);
return;
}
const token = await this.requireLogin(roomId, sender);
if (!token) return;
try {
const favorites = await this.zitareService.getFavorites(token);
@ -458,11 +452,8 @@ Sag "hilfe" fuer alle Befehle!`;
}
private async handleLists(roomId: string, sender: string) {
const token = await this.sessionService.getToken(sender);
if (!token) {
await this.sendMessage(roomId, `Du bist nicht angemeldet. Nutze \`!login\` zuerst.`);
return;
}
const token = await this.requireLogin(roomId, sender);
if (!token) return;
try {
const lists = await this.zitareService.getLists(token);
@ -493,11 +484,8 @@ Sag "hilfe" fuer alle Befehle!`;
}
private async handleCreateList(roomId: string, sender: string, name: string) {
const token = await this.sessionService.getToken(sender);
if (!token) {
await this.sendMessage(roomId, `Du bist nicht angemeldet. Nutze \`!login\` zuerst.`);
return;
}
const token = await this.requireLogin(roomId, sender);
if (!token) return;
if (!name.trim()) {
await this.sendMessage(
@ -520,11 +508,8 @@ Sag "hilfe" fuer alle Befehle!`;
}
private async handleAddToList(roomId: string, sender: string, args: string[]) {
const token = await this.sessionService.getToken(sender);
if (!token) {
await this.sendMessage(roomId, `Du bist nicht angemeldet. Nutze \`!login\` zuerst.`);
return;
}
const token = await this.requireLogin(roomId, sender);
if (!token) return;
if (args.length < 1) {
await this.sendMessage(
@ -571,6 +556,18 @@ Sag "hilfe" fuer alle Befehle!`;
}
}
/**
* Require login - returns token or sends login prompt and returns null
*/
private async requireLogin(roomId: string, userId: string): Promise<string | null> {
const token = await this.sessionService.getToken(userId);
if (!token) {
await this.sendMessage(roomId, LOGIN_MESSAGES.zitare);
return null;
}
return token;
}
private async handleStatus(roomId: string, sender: string) {
const backendHealthy = await this.zitareService.checkHealth();
const isLoggedIn = await this.sessionService.isLoggedIn(sender);