diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 062af39732..6905f8bee9 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -98,7 +98,10 @@ export class ApDbResolverService implements OnApplicationShutdown { * AP Person => Misskey User in DB */ @bindThis - public async getUserFromApId(value: string | IObject): Promise { + public async getUserFromApId(value: string | IObject | [string | IObject]): Promise { + // eslint-disable-next-line no-param-reassign + if (Array.isArray(value)) value = value[0]; + const parsed = this.parseUri(value); if (parsed.local) { diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index bce67a458f..289f1606a1 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -253,7 +253,8 @@ export class ApInboxService { } if (activity.target === actor.featured) { - const note = await this.apNoteService.resolveNote(activity.object); + const object = Array.isArray(activity.object) ? activity.object[0] : activity.object; + const note = await this.apNoteService.resolveNote(object); if (note == null) return 'note not found'; await this.notePiningService.addPinned(actor, note.id); return; @@ -270,11 +271,12 @@ export class ApInboxService { const resolver = this.apResolverService.createResolver(); - if (!activity.object) return 'skip: activity has no object property'; - const targetUri = getApId(activity.object); + const activityObject = Array.isArray(activity.object) ? activity.object[0] : activity.object; + if (!activityObject) return 'skip: activity has no object property'; + const targetUri = getApId(activityObject); if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.'; - const target = await resolver.resolve(activity.object).catch(e => { + const target = await resolver.resolve(activityObject).catch(e => { this.logger.error(`Resolution failed: ${e}`); return e; }); @@ -370,29 +372,30 @@ export class ApInboxService { this.logger.info(`Create: ${uri}`); - if (!activity.object) return 'skip: activity has no object property'; - const targetUri = getApId(activity.object); + const activityObject = Array.isArray(activity.object) ? activity.object[0] : activity.object; + if (!activityObject) return 'skip: activity has no object property'; + const targetUri = getApId(activityObject); if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.'; // copy audiences between activity <=> object. - if (typeof activity.object === 'object') { - const to = unique(concat([toArray(activity.to), toArray(activity.object.to)])); - const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)])); + if (typeof activityObject === 'object') { + const to = unique(concat([toArray(activity.to), toArray(activityObject.to)])); + const cc = unique(concat([toArray(activity.cc), toArray(activityObject.cc)])); activity.to = to; activity.cc = cc; - activity.object.to = to; - activity.object.cc = cc; + activityObject.to = to; + activityObject.cc = cc; } // If there is no attributedTo, use Activity actor. - if (typeof activity.object === 'object' && !activity.object.attributedTo) { - activity.object.attributedTo = activity.actor; + if (typeof activityObject === 'object' && !activityObject.attributedTo) { + activityObject.attributedTo = activity.actor; } const resolver = this.apResolverService.createResolver(); - const object = await resolver.resolve(activity.object).catch(e => { + const object = await resolver.resolve(activityObject).catch(e => { this.logger.error(`Resolution failed: ${e}`); throw e; }); @@ -448,15 +451,15 @@ export class ApInboxService { // 削除対象objectのtype let formerType: string | undefined; - if (typeof activity.object === 'string') { + const activityObject = Array.isArray(activity.object) ? activity.object[0] : activity.object; + if (typeof activityObject === 'string') { // typeが不明だけど、どうせ消えてるのでremote resolveしない formerType = undefined; } else { - const object = activity.object; - if (isTombstone(object)) { - formerType = toSingle(object.formerType); + if (isTombstone(activityObject)) { + formerType = toSingle(activityObject.formerType); } else { - formerType = toSingle(object.type); + formerType = toSingle(activityObject.type); } } @@ -616,7 +619,8 @@ export class ApInboxService { } if (activity.target === actor.featured) { - const note = await this.apNoteService.resolveNote(activity.object); + const activityObject = Array.isArray(activity.object) ? activity.object[0] : activity.object; + const note = await this.apNoteService.resolveNote(activityObject); if (note == null) return 'note not found'; await this.notePiningService.removePinned(actor, note.id); return; diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 499a163d6c..ff6f462b4c 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -199,7 +199,8 @@ export class ApRendererService { type: 'Flag', actor: this.userEntityService.genLocalUserUri(user.id), content, - object, + // This MUST be an array for Pleroma compatibility: https://activitypub.software/TransFem-org/Sharkey/-/issues/641#note_7301 + object: [object], }; } diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index b047a6c59b..2a803d394d 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -67,7 +67,10 @@ export class Resolver { } @bindThis - public async resolve(value: string | IObject): Promise { + public async resolve(value: string | IObject | [string | IObject]): Promise { + // eslint-disable-next-line no-param-reassign + if (Array.isArray(value)) value = value[0]; + if (typeof value !== 'string') { return value; } diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 2f58825de1..2ec3b0baff 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -52,10 +52,13 @@ export function getOneApId(value: ApObject): string { /** * Get ActivityStreams Object id */ -export function getApId(value: string | IObject): string { +export function getApId(value: string | IObject | [string | IObject]): string { + // eslint-disable-next-line no-param-reassign + if (Array.isArray(value)) value = value[0]; + if (typeof value === 'string') return value; if (typeof value.id === 'string') return value.id; - throw new Error('cannot detemine id'); + throw new Error('cannot determine id'); } /** @@ -84,7 +87,9 @@ export function getApHrefNullable(value: string | IObject | undefined): string | export interface IActivity extends IObject { //type: 'Activity'; actor: IObject | string; - object: IObject | string; + // ActivityPub spec allows for arrays: https://www.w3.org/TR/activitystreams-vocabulary/#properties + // Misskey can only handle one value, so we use a tuple for that case. + object: IObject | string | [IObject | string] ; target?: IObject | string; /** LD-Signature */ signature?: {