perform only create activities
This commit is contained in:
parent
ca0c673b44
commit
08e2b6ee32
3 changed files with 96 additions and 44 deletions
|
@ -86,11 +86,19 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async performActivity(actor: RemoteUser, activity: IObject, limit = Infinity) {
|
public async performActivity(actor: RemoteUser, activity: IObject, {
|
||||||
|
limit = Infinity,
|
||||||
|
allow = null as (string[] | null) } = {},
|
||||||
|
): Promise<void> {
|
||||||
if (isCollectionOrOrderedCollection(activity) || isOrderedCollectionPage(activity)) {
|
if (isCollectionOrOrderedCollection(activity) || isOrderedCollectionPage(activity)) {
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems).slice(0, limit)) {
|
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems).slice(0, limit)) {
|
||||||
const act = await resolver.resolve(item);
|
const act = await resolver.resolve(item);
|
||||||
|
const type = getApType(act);
|
||||||
|
if (allow && !allow.includes(type)) {
|
||||||
|
this.logger.info(`skipping activity type: ${type}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.performOneActivity(actor, act);
|
await this.performOneActivity(actor, act);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -380,10 +380,10 @@ export class ApPersonService implements OnModuleInit {
|
||||||
await this.usersRepository.update(user.id, { emojis: emojiNames });
|
await this.usersRepository.update(user.id, { emojis: emojiNames });
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.allSettled([
|
||||||
this.updateFeatured(user.id, resolver),
|
this.updateFeatured(user.id, resolver).catch(err => this.logger.error(err)),
|
||||||
this.updateOutboxFirstPage(user, person.outbox, resolver),
|
this.updateOutboxFirstPage(user, person.outbox, resolver).catch(err => this.logger.error(err)),
|
||||||
]).catch(err => this.logger.error(err));
|
]);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
@ -587,33 +587,6 @@ export class ApPersonService implements OnModuleInit {
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve outbox from an actor object.
|
|
||||||
*
|
|
||||||
* This only retrieves the first page for now.
|
|
||||||
*/
|
|
||||||
public async updateOutboxFirstPage(user: RemoteUser, outbox: IActor['outbox'], resolver: Resolver): Promise<void> {
|
|
||||||
// https://www.w3.org/TR/activitypub/#actor-objects
|
|
||||||
// Outbox is a required property for all actors
|
|
||||||
if (!outbox) {
|
|
||||||
throw new Error('No outbox property');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info(`Fetching the outbox for ${user.uri}: ${outbox}`);
|
|
||||||
|
|
||||||
const collection = await resolver.resolveCollection(outbox);
|
|
||||||
if (!isOrderedCollection(collection)) {
|
|
||||||
throw new Error('Outbox must be an ordered collection');
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstPage = collection.first ?
|
|
||||||
await resolver.resolveOrderedCollectionPage(collection.first) :
|
|
||||||
collection;
|
|
||||||
|
|
||||||
// Perform activity but only the first 20 ones
|
|
||||||
await this.apInboxService.performActivity(user, firstPage, 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async updateFeatured(userId: User['id'], resolver?: Resolver): Promise<void> {
|
public async updateFeatured(userId: User['id'], resolver?: Resolver): Promise<void> {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||||
|
@ -659,6 +632,33 @@ export class ApPersonService implements OnModuleInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve outbox from an actor object.
|
||||||
|
*
|
||||||
|
* This only retrieves the first page for now.
|
||||||
|
*/
|
||||||
|
public async updateOutboxFirstPage(user: RemoteUser, outbox: IActor['outbox'], resolver: Resolver): Promise<void> {
|
||||||
|
// https://www.w3.org/TR/activitypub/#actor-objects
|
||||||
|
// Outbox is a required property for all actors
|
||||||
|
if (!outbox) {
|
||||||
|
throw new Error('No outbox property');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info(`Fetching the outbox for ${user.uri}: ${outbox}`);
|
||||||
|
|
||||||
|
const collection = await resolver.resolveCollection(outbox);
|
||||||
|
if (!isOrderedCollection(collection)) {
|
||||||
|
throw new Error('Outbox must be an ordered collection');
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstPage = collection.first ?
|
||||||
|
await resolver.resolveOrderedCollectionPage(collection.first) :
|
||||||
|
collection;
|
||||||
|
|
||||||
|
// Perform activity but only the first 20 ones with `type: Create`
|
||||||
|
await this.apInboxService.performActivity(user, firstPage, { limit: 20, allow: ['Create'] });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* リモート由来のアカウント移行処理を行います
|
* リモート由来のアカウント移行処理を行います
|
||||||
* @param src 移行元アカウント(リモートかつupdatePerson後である必要がある、というかこれ自体がupdatePersonで呼ばれる前提)
|
* @param src 移行元アカウント(リモートかつupdatePerson後である必要がある、というかこれ自体がupdatePersonで呼ばれる前提)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { CoreModule } from '@/core/CoreModule.js';
|
import { CoreModule } from '@/core/CoreModule.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type { IActor, ICollection, ICreate, IObject, IOrderedCollection, IOrderedCollectionPage, IPost } from '@/core/activitypub/type.js';
|
import type { IActivity, IActor, ICollection, IObject, IOrderedCollection, IOrderedCollectionPage, IPost } from '@/core/activitypub/type.js';
|
||||||
import { Note } from '@/models/index.js';
|
import { Note } from '@/models/index.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { MockResolver } from '../misc/mock-resolver.js';
|
import { MockResolver } from '../misc/mock-resolver.js';
|
||||||
|
@ -24,6 +24,13 @@ type NonTransientICollection = ICollection & { id: string };
|
||||||
type NonTransientIOrderedCollection = IOrderedCollection & { id: string };
|
type NonTransientIOrderedCollection = IOrderedCollection & { id: string };
|
||||||
type NonTransientIOrderedCollectionPage = IOrderedCollectionPage & { id: string };
|
type NonTransientIOrderedCollectionPage = IOrderedCollectionPage & { id: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use when the order of the array is not definitive
|
||||||
|
*/
|
||||||
|
function deepSortedEqual<T extends unknown[]>(array1: unknown[], array2: T): asserts array1 is T {
|
||||||
|
return assert.deepStrictEqual(array1.sort(), array2.sort());
|
||||||
|
}
|
||||||
|
|
||||||
function createRandomActor({ actorHost = host } = {}): NonTransientIActor {
|
function createRandomActor({ actorHost = host } = {}): NonTransientIActor {
|
||||||
const preferredUsername = secureRndstr(8);
|
const preferredUsername = secureRndstr(8);
|
||||||
const actorId = `${actorHost}/users/${preferredUsername.toLowerCase()}`;
|
const actorId = `${actorHost}/users/${preferredUsername.toLowerCase()}`;
|
||||||
|
@ -66,12 +73,12 @@ function createRandomFeaturedCollection(actor: NonTransientIActor, length: numbe
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRandomCreateActivity(actor: NonTransientIActor, length: number): ICreate[] {
|
function createRandomActivities(actor: NonTransientIActor, type: string, length: number): IActivity[] {
|
||||||
return new Array(length).fill(null).map((): ICreate => {
|
return new Array(length).fill(null).map((): IActivity => {
|
||||||
const note = createRandomNote(actor);
|
const note = createRandomNote(actor);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'Create',
|
type,
|
||||||
id: `${note.id}/activity`,
|
id: `${note.id}/activity`,
|
||||||
actor,
|
actor,
|
||||||
object: note,
|
object: note,
|
||||||
|
@ -80,7 +87,7 @@ function createRandomCreateActivity(actor: NonTransientIActor, length: number):
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRandomNonPagedOutbox(actor: NonTransientIActor, length: number): NonTransientIOrderedCollection {
|
function createRandomNonPagedOutbox(actor: NonTransientIActor, length: number): NonTransientIOrderedCollection {
|
||||||
const orderedItems = createRandomCreateActivity(actor, length);
|
const orderedItems = createRandomActivities(actor, 'Create', length);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
@ -92,7 +99,7 @@ function createRandomNonPagedOutbox(actor: NonTransientIActor, length: number):
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRandomOutboxPage(actor: NonTransientIActor, id: string, length: number): NonTransientIOrderedCollectionPage {
|
function createRandomOutboxPage(actor: NonTransientIActor, id: string, length: number): NonTransientIOrderedCollectionPage {
|
||||||
const orderedItems = createRandomCreateActivity(actor, length);
|
const orderedItems = createRandomActivities(actor, 'Create', length);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
@ -225,7 +232,7 @@ describe('ActivityPub', () => {
|
||||||
await personService.createPerson(actor.id, resolver);
|
await personService.createPerson(actor.id, resolver);
|
||||||
|
|
||||||
// All notes in `featured` are same-origin, no need to fetch notes again
|
// All notes in `featured` are same-origin, no need to fetch notes again
|
||||||
assert.deepStrictEqual(resolver.remoteGetTrials(), [actor.id, actor.featured]);
|
deepSortedEqual(resolver.remoteGetTrials(), [actor.id, actor.featured, actor.outbox]);
|
||||||
|
|
||||||
// Created notes without resolving anything
|
// Created notes without resolving anything
|
||||||
for (const item of featured.items as IPost[]) {
|
for (const item of featured.items as IPost[]) {
|
||||||
|
@ -256,9 +263,9 @@ describe('ActivityPub', () => {
|
||||||
await personService.createPerson(actor1.id, resolver);
|
await personService.createPerson(actor1.id, resolver);
|
||||||
|
|
||||||
// actor2Note is from a different server and needs to be fetched again
|
// actor2Note is from a different server and needs to be fetched again
|
||||||
assert.deepStrictEqual(
|
deepSortedEqual(
|
||||||
resolver.remoteGetTrials(),
|
resolver.remoteGetTrials(),
|
||||||
[actor1.id, actor1.featured, actor2Note.id, actor2.id],
|
[actor1.id, actor1.featured, actor1.outbox, actor2Note.id, actor2.id, actor2.outbox],
|
||||||
);
|
);
|
||||||
|
|
||||||
const note = await noteService.fetchNote(actor2Note.id);
|
const note = await noteService.fetchNote(actor2Note.id);
|
||||||
|
@ -280,7 +287,12 @@ describe('ActivityPub', () => {
|
||||||
|
|
||||||
await personService.createPerson(actor.id, resolver);
|
await personService.createPerson(actor.id, resolver);
|
||||||
|
|
||||||
for (const item of outbox.orderedItems as ICreate[]) {
|
deepSortedEqual(
|
||||||
|
resolver.remoteGetTrials(),
|
||||||
|
[actor.id, actor.outbox],
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const item of outbox.orderedItems as IActivity[]) {
|
||||||
const note = await noteService.fetchNote(item.object);
|
const note = await noteService.fetchNote(item.object);
|
||||||
assert.ok(note);
|
assert.ok(note);
|
||||||
assert.strictEqual(note.text, 'test test foo');
|
assert.strictEqual(note.text, 'test test foo');
|
||||||
|
@ -299,7 +311,12 @@ describe('ActivityPub', () => {
|
||||||
|
|
||||||
await personService.createPerson(actor.id, resolver);
|
await personService.createPerson(actor.id, resolver);
|
||||||
|
|
||||||
for (const item of page.orderedItems as ICreate[]) {
|
deepSortedEqual(
|
||||||
|
resolver.remoteGetTrials(),
|
||||||
|
[actor.id, actor.outbox, outbox.first],
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const item of page.orderedItems as IActivity[]) {
|
||||||
const note = await noteService.fetchNote(item.object);
|
const note = await noteService.fetchNote(item.object);
|
||||||
assert.ok(note);
|
assert.ok(note);
|
||||||
assert.strictEqual(note.text, 'test test foo');
|
assert.strictEqual(note.text, 'test test foo');
|
||||||
|
@ -316,9 +333,36 @@ describe('ActivityPub', () => {
|
||||||
|
|
||||||
await personService.createPerson(actor.id, resolver);
|
await personService.createPerson(actor.id, resolver);
|
||||||
|
|
||||||
const items = outbox.orderedItems as ICreate[];
|
const items = outbox.orderedItems as IActivity[];
|
||||||
|
|
||||||
|
deepSortedEqual(
|
||||||
|
resolver.remoteGetTrials(),
|
||||||
|
[actor.id, actor.outbox],
|
||||||
|
);
|
||||||
|
|
||||||
assert.ok(await noteService.fetchNote(items[19].object));
|
assert.ok(await noteService.fetchNote(items[19].object));
|
||||||
assert.ok(!await noteService.fetchNote(items[20].object));
|
assert.ok(!await noteService.fetchNote(items[20].object));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Perform only Create activities', async () => {
|
||||||
|
const actor = createRandomActor();
|
||||||
|
const outbox = createRandomNonPagedOutbox(actor, 0);
|
||||||
|
outbox.orderedItems = createRandomActivities(actor, 'Announce', 10);
|
||||||
|
|
||||||
|
resolver.register(actor.id, actor);
|
||||||
|
resolver.register(actor.outbox as string, outbox);
|
||||||
|
|
||||||
|
await personService.createPerson(actor.id, resolver);
|
||||||
|
|
||||||
|
deepSortedEqual(
|
||||||
|
resolver.remoteGetTrials(),
|
||||||
|
[actor.id, actor.outbox],
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const item of outbox.orderedItems as IActivity[]) {
|
||||||
|
const note = await noteService.fetchNote(item.object);
|
||||||
|
assert.ok(!note);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue