From 793b6d8e17e0c140790c1dfdf438dd35d4f13032 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Sun, 1 Feb 2026 04:07:38 +0100 Subject: [PATCH] fix(matrix-bot-common): use authenticated media API for downloads Newer Synapse versions (1.98+) require authenticated downloads via /_matrix/client/v1/media/download/ endpoint. Falls back to legacy API for older servers. Co-Authored-By: Claude Opus 4.5 --- .../src/base/base-matrix.service.ts | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/matrix-bot-common/src/base/base-matrix.service.ts b/packages/matrix-bot-common/src/base/base-matrix.service.ts index 335d7044b..6de1be3cc 100644 --- a/packages/matrix-bot-common/src/base/base-matrix.service.ts +++ b/packages/matrix-bot-common/src/base/base-matrix.service.ts @@ -221,11 +221,41 @@ export abstract class BaseMatrixService implements OnModuleInit, OnModuleDestroy } /** - * Download media from Matrix + * Download media from Matrix using authenticated media API (v1) + * Newer Synapse versions require authenticated downloads via /_matrix/client/v1/media/download/ */ protected async downloadMedia(mxcUrl: string): Promise { - const result = await this.client.downloadContent(mxcUrl); - return result.data; + // Parse mxc:// URL -> mxc://server/mediaId + const match = mxcUrl.match(/^mxc:\/\/([^/]+)\/(.+)$/); + if (!match) { + throw new Error(`Invalid mxc URL: ${mxcUrl}`); + } + + const [, serverName, mediaId] = match; + const config = this.getConfig(); + + // Use the new authenticated media API (Matrix spec v1.11+) + const downloadUrl = `${config.homeserverUrl}/_matrix/client/v1/media/download/${serverName}/${mediaId}`; + + const response = await fetch(downloadUrl, { + headers: { + Authorization: `Bearer ${config.accessToken}`, + }, + }); + + if (!response.ok) { + // Fallback to old API for older servers + this.logger.debug(`v1 media API failed (${response.status}), trying legacy API...`); + try { + const result = await this.client.downloadContent(mxcUrl); + return result.data; + } catch { + throw new Error(`Failed to download media: ${response.status} ${response.statusText}`); + } + } + + const arrayBuffer = await response.arrayBuffer(); + return Buffer.from(arrayBuffer); } /**