View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/690 Closes #641 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
commit
16847ba491
7 changed files with 65 additions and 28 deletions
|
@ -59,7 +59,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public parseUri(value: string | IObject): UriParseResult {
|
public parseUri(value: string | IObject | [string | IObject]): UriParseResult {
|
||||||
const separator = '/';
|
const separator = '/';
|
||||||
|
|
||||||
const uri = new URL(getApId(value));
|
const uri = new URL(getApId(value));
|
||||||
|
@ -78,7 +78,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
* AP Note => Misskey Note in DB
|
* AP Note => Misskey Note in DB
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getNoteFromApId(value: string | IObject): Promise<MiNote | null> {
|
public async getNoteFromApId(value: string | IObject | [string | IObject]): Promise<MiNote | null> {
|
||||||
const parsed = this.parseUri(value);
|
const parsed = this.parseUri(value);
|
||||||
|
|
||||||
if (parsed.local) {
|
if (parsed.local) {
|
||||||
|
@ -98,7 +98,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
* AP Person => Misskey User in DB
|
* AP Person => Misskey User in DB
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getUserFromApId(value: string | IObject): Promise<MiLocalUser | MiRemoteUser | null> {
|
public async getUserFromApId(value: string | IObject | [string | IObject]): Promise<MiLocalUser | MiRemoteUser | null> {
|
||||||
const parsed = this.parseUri(value);
|
const parsed = this.parseUri(value);
|
||||||
|
|
||||||
if (parsed.local) {
|
if (parsed.local) {
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { ApPersonService } from './models/ApPersonService.js';
|
||||||
import { ApQuestionService } from './models/ApQuestionService.js';
|
import { ApQuestionService } from './models/ApQuestionService.js';
|
||||||
import type { Resolver } from './ApResolverService.js';
|
import type { Resolver } from './ApResolverService.js';
|
||||||
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
|
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
|
||||||
|
import { fromTuple } from '@/misc/from-tuple.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApInboxService {
|
export class ApInboxService {
|
||||||
|
@ -253,7 +254,8 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.target === actor.featured) {
|
if (activity.target === actor.featured) {
|
||||||
const note = await this.apNoteService.resolveNote(activity.object);
|
const object = fromTuple(activity.object);
|
||||||
|
const note = await this.apNoteService.resolveNote(object);
|
||||||
if (note == null) return 'note not found';
|
if (note == null) return 'note not found';
|
||||||
await this.notePiningService.addPinned(actor, note.id);
|
await this.notePiningService.addPinned(actor, note.id);
|
||||||
return;
|
return;
|
||||||
|
@ -270,11 +272,12 @@ export class ApInboxService {
|
||||||
|
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
|
|
||||||
if (!activity.object) return 'skip: activity has no object property';
|
const activityObject = fromTuple(activity.object);
|
||||||
const targetUri = getApId(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.';
|
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}`);
|
this.logger.error(`Resolution failed: ${e}`);
|
||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
|
@ -370,29 +373,30 @@ export class ApInboxService {
|
||||||
|
|
||||||
this.logger.info(`Create: ${uri}`);
|
this.logger.info(`Create: ${uri}`);
|
||||||
|
|
||||||
if (!activity.object) return 'skip: activity has no object property';
|
const activityObject = fromTuple(activity.object);
|
||||||
const targetUri = getApId(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.';
|
if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
|
||||||
|
|
||||||
// copy audiences between activity <=> object.
|
// copy audiences between activity <=> object.
|
||||||
if (typeof activity.object === 'object') {
|
if (typeof activityObject === 'object') {
|
||||||
const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
|
const to = unique(concat([toArray(activity.to), toArray(activityObject.to)]));
|
||||||
const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)]));
|
const cc = unique(concat([toArray(activity.cc), toArray(activityObject.cc)]));
|
||||||
|
|
||||||
activity.to = to;
|
activity.to = to;
|
||||||
activity.cc = cc;
|
activity.cc = cc;
|
||||||
activity.object.to = to;
|
activityObject.to = to;
|
||||||
activity.object.cc = cc;
|
activityObject.cc = cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no attributedTo, use Activity actor.
|
// If there is no attributedTo, use Activity actor.
|
||||||
if (typeof activity.object === 'object' && !activity.object.attributedTo) {
|
if (typeof activityObject === 'object' && !activityObject.attributedTo) {
|
||||||
activity.object.attributedTo = activity.actor;
|
activityObject.attributedTo = activity.actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolver = this.apResolverService.createResolver();
|
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}`);
|
this.logger.error(`Resolution failed: ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
@ -448,15 +452,15 @@ export class ApInboxService {
|
||||||
// 削除対象objectのtype
|
// 削除対象objectのtype
|
||||||
let formerType: string | undefined;
|
let formerType: string | undefined;
|
||||||
|
|
||||||
if (typeof activity.object === 'string') {
|
const activityObject = fromTuple(activity.object);
|
||||||
|
if (typeof activityObject === 'string') {
|
||||||
// typeが不明だけど、どうせ消えてるのでremote resolveしない
|
// typeが不明だけど、どうせ消えてるのでremote resolveしない
|
||||||
formerType = undefined;
|
formerType = undefined;
|
||||||
} else {
|
} else {
|
||||||
const object = activity.object;
|
if (isTombstone(activityObject)) {
|
||||||
if (isTombstone(object)) {
|
formerType = toSingle(activityObject.formerType);
|
||||||
formerType = toSingle(object.formerType);
|
|
||||||
} else {
|
} else {
|
||||||
formerType = toSingle(object.type);
|
formerType = toSingle(activityObject.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,7 +620,8 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.target === actor.featured) {
|
if (activity.target === actor.featured) {
|
||||||
const note = await this.apNoteService.resolveNote(activity.object);
|
const activityObject = fromTuple(activity.object);
|
||||||
|
const note = await this.apNoteService.resolveNote(activityObject);
|
||||||
if (note == null) return 'note not found';
|
if (note == null) return 'note not found';
|
||||||
await this.notePiningService.removePinned(actor, note.id);
|
await this.notePiningService.removePinned(actor, note.id);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -199,7 +199,8 @@ export class ApRendererService {
|
||||||
type: 'Flag',
|
type: 'Flag',
|
||||||
actor: this.userEntityService.genLocalUserUri(user.id),
|
actor: this.userEntityService.genLocalUserUri(user.id),
|
||||||
content,
|
content,
|
||||||
object,
|
// This MUST be an array for Pleroma compatibility: https://activitypub.software/TransFem-org/Sharkey/-/issues/641#note_7301
|
||||||
|
object: [object],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { ApDbResolverService } from './ApDbResolverService.js';
|
||||||
import { ApRendererService } from './ApRendererService.js';
|
import { ApRendererService } from './ApRendererService.js';
|
||||||
import { ApRequestService } from './ApRequestService.js';
|
import { ApRequestService } from './ApRequestService.js';
|
||||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||||
|
import { fromTuple } from '@/misc/from-tuple.js';
|
||||||
|
|
||||||
export class Resolver {
|
export class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
|
@ -67,7 +68,10 @@ export class Resolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async resolve(value: string | IObject): Promise<IObject> {
|
public async resolve(value: string | IObject | [string | IObject]): Promise<IObject> {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
value = fromTuple(value);
|
||||||
|
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { fromTuple } from '@/misc/from-tuple.js';
|
||||||
|
|
||||||
export type Obj = { [x: string]: any };
|
export type Obj = { [x: string]: any };
|
||||||
export type ApObject = IObject | string | (IObject | string)[];
|
export type ApObject = IObject | string | (IObject | string)[];
|
||||||
|
|
||||||
|
@ -52,10 +54,13 @@ export function getOneApId(value: ApObject): string {
|
||||||
/**
|
/**
|
||||||
* Get ActivityStreams Object id
|
* 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
|
||||||
|
value = fromTuple(value);
|
||||||
|
|
||||||
if (typeof value === 'string') return value;
|
if (typeof value === 'string') return value;
|
||||||
if (typeof value.id === 'string') return value.id;
|
if (typeof value.id === 'string') return value.id;
|
||||||
throw new Error('cannot detemine id');
|
throw new Error('cannot determine id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,7 +89,9 @@ export function getApHrefNullable(value: string | IObject | undefined): string |
|
||||||
export interface IActivity extends IObject {
|
export interface IActivity extends IObject {
|
||||||
//type: 'Activity';
|
//type: 'Activity';
|
||||||
actor: IObject | string;
|
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;
|
target?: IObject | string;
|
||||||
/** LD-Signature */
|
/** LD-Signature */
|
||||||
signature?: {
|
signature?: {
|
||||||
|
|
7
packages/backend/src/misc/from-tuple.ts
Normal file
7
packages/backend/src/misc/from-tuple.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export function fromTuple<T>(value: T | [T]): T {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
13
packages/backend/test/unit/misc/from-tuple.ts
Normal file
13
packages/backend/test/unit/misc/from-tuple.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { fromTuple } from '@/misc/from-tuple.js';
|
||||||
|
|
||||||
|
describe(fromTuple, () => {
|
||||||
|
it('should return value when value is not an array', () => {
|
||||||
|
const value = fromTuple('abc');
|
||||||
|
expect(value).toBe('abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return first element when value is an array', () => {
|
||||||
|
const value = fromTuple(['abc']);
|
||||||
|
expect(value).toBe('abc');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue