diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 9b053a4b95..04212e35c2 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -100,6 +100,7 @@ type Source = { bypassRateLimit?: { header: string; value: string }[]; + remapDriveFileUrlForActivityPub?: { target: string; replacement: string }[]; signToActivityPubGet?: boolean; perChannelMaxNoteCacheCount?: number; @@ -160,6 +161,7 @@ export type Config = { deliverJobMaxAttempts: number | undefined; inboxJobMaxAttempts: number | undefined; proxyRemoteFiles: boolean | undefined; + remapDriveFileUrlForActivityPub: { target: string; replacement: string }[] | undefined; signToActivityPubGet: boolean | undefined; version: string; @@ -285,6 +287,7 @@ export function loadConfig(): Config { deliverJobMaxAttempts: config.deliverJobMaxAttempts, inboxJobMaxAttempts: config.inboxJobMaxAttempts, proxyRemoteFiles: config.proxyRemoteFiles, + remapDriveFileUrlForActivityPub: config.remapDriveFileUrlForActivityPub, signToActivityPubGet: config.signToActivityPubGet, mediaProxy: externalMediaProxy ?? internalMediaProxy, externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy, diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 3a0367ba64..83169a3d3b 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -164,13 +164,20 @@ export class ApRendererService { return { type: 'Document', mediaType: file.webpublicType ?? file.type, - url: this.driveFileEntityService.getPublicUrl(file), + url: this.driveFileEntityService.getPublicUrl(file, { remapActivityPub: true }), name: file.comment, }; } @bindThis public renderEmoji(emoji: MiEmoji): IApEmoji { + // || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ) + let url = emoji.publicUrl || emoji.originalUrl; + + this.config.remapDriveFileUrlForActivityPub?.forEach(({ target, replacement }) => { + url = url.replace(target, replacement); + }); + return { id: `${this.config.url}/emojis/${emoji.name}`, type: 'Emoji', @@ -179,8 +186,7 @@ export class ApRendererService { icon: { type: 'Image', mediaType: emoji.type ?? 'image/png', - // || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ) - url: emoji.publicUrl || emoji.originalUrl, + url: url, }, }; } @@ -243,7 +249,7 @@ export class ApRendererService { public renderImage(file: MiDriveFile): IApImage { return { type: 'Image', - url: this.driveFileEntityService.getPublicUrl(file), + url: this.driveFileEntityService.getPublicUrl(file, { remapActivityPub: true }), sensitive: file.isSensitive, name: file.comment, }; diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 56e4e521ed..6a3b543066 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -267,12 +267,12 @@ export class ApPersonService implements OnModuleInit { return { ...( avatar ? { avatarId: avatar.id, - avatarUrl: avatar.url ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null, + avatarUrl: avatar.url ? this.driveFileEntityService.getPublicUrl(avatar, { mode: 'avatar', remapActivityPub: true }) : null, avatarBlurhash: avatar.blurhash, } : {}), ...( banner ? { bannerId: banner.id, - bannerUrl: banner.url ? this.driveFileEntityService.getPublicUrl(banner) : null, + bannerUrl: banner.url ? this.driveFileEntityService.getPublicUrl(banner, { remapActivityPub: true }) : null, bannerBlurhash: banner.blurhash, } : {}), }; diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 6f8f5797a1..75e4dda505 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -109,10 +109,10 @@ export class DriveFileEntityService { } @bindThis - public getPublicUrl(file: MiDriveFile, mode?: 'avatar'): string { // static = thumbnail + public getPublicUrl(file: MiDriveFile, option?: { mode?: 'avatar', remapActivityPub?: boolean }): string { // static = thumbnail // リモートかつメディアプロキシ if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) { - return this.getProxiedUrl(file.uri, mode); + return this.getProxiedUrl(file.uri, option?.mode); } // リモートかつ期限切れはローカルプロキシを試みる @@ -121,17 +121,23 @@ export class DriveFileEntityService { if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外 const url = `${this.config.url}/files/${key}`; - if (mode === 'avatar') return this.getProxiedUrl(file.uri, 'avatar'); + if (option?.mode === 'avatar') return this.getProxiedUrl(file.uri, 'avatar'); return url; } } - const url = file.webpublicUrl ?? file.url; - - if (mode === 'avatar') { - return this.getProxiedUrl(url, 'avatar'); + let publicUrl = file.webpublicUrl ?? file.url; + if (option?.remapActivityPub) { + this.config.remapDriveFileUrlForActivityPub?.forEach(({ target, replacement }) => { + publicUrl = publicUrl.replace(target, replacement); + }); } - return url; + + const url = new URL(publicUrl); + if (file.isSensitive) url.searchParams.set('sensitive', 'true'); + + if (option?.mode === 'avatar') return this.getProxiedUrl(url.href, 'avatar'); + else return url.href; } @bindThis diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 5e64511f72..5ff035ec4a 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -314,7 +314,7 @@ export default class extends Endpoint { // eslint- if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); updates.avatarId = avatar.id; - updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar'); + updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, { mode: 'avatar' }); updates.avatarBlurhash = avatar.blurhash; } else if (ps.avatarId === null) { updates.avatarId = null;