diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 423044b985..383c77a177 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -18,7 +18,7 @@ import type { Config } from '@/config.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; -import { isDocument, type IObject } from '../type.js'; +import { isDocument, type IObject, isApObject } from '../type.js'; @Injectable() export class ApImageService { diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 9fc6945edb..2995b1e764 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -25,7 +25,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { checkHttps } from '@/misc/check-https.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; +import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType, isApObject, isDocument, IApDocument } from '../type.js'; import { ApLoggerService } from '../ApLoggerService.js'; import { ApMfmService } from '../ApMfmService.js'; import { ApDbResolverService } from '../ApDbResolverService.js'; @@ -270,6 +270,14 @@ export class ApNoteService { if (file) files.push(file); } + // Some software (Peertube) attaches a thumbnail under "icon" instead of "attachment" + const icon = getBestIcon(note); + if (icon) { + icon.sensitive ??= note.sensitive; + const file = await this.apImageService.resolveImage(actor, icon); + if (file) files.push(file); + } + // リプライ const reply: MiNote | null = note.inReplyTo ? await this.resolveNote(note.inReplyTo, { resolver }) @@ -504,6 +512,14 @@ export class ApNoteService { if (file) files.push(file); } + // Some software (Peertube) attaches a thumbnail under "icon" instead of "attachment" + const icon = getBestIcon(note); + if (icon) { + icon.sensitive ??= note.sensitive; + const file = await this.apImageService.resolveImage(actor, icon); + if (file) files.push(file); + } + // リプライ const reply: MiNote | null = note.inReplyTo ? await this.resolveNote(note.inReplyTo, { resolver }) @@ -719,3 +735,21 @@ export class ApNoteService { })); } } + +function getBestIcon(note: IObject): IObject | null { + const icons: IObject[] = toArray(note.icon); + if (icons.length < 2) { + return icons[0] ?? null; + } + + return icons.reduce((best, i) => { + if (!isApObject(i)) return best; + if (!isDocument(i)) return best; + if (!best) return i; + if (!best.width || !best.height) return i; + if (!i.width || !i.height) return best; + if (i.width > best.width) return i; + if (i.height > best.height) return i; + return best; + }, null as IApDocument | null) ?? null; +} diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 4e0f131647..731a46c0d4 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -297,6 +297,8 @@ export const validDocumentTypes = ['Audio', 'Document', 'Image', 'Page', 'Video' export interface IApDocument extends IObject { type: 'Audio' | 'Document' | 'Image' | 'Page' | 'Video'; + width?: number; + height?: number; } export const isDocument = (object: IObject): object is IApDocument => {