2022-09-17 18:27:08 +00:00
|
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
2022-03-26 08:43:08 +00:00
|
|
|
|
import { IsNull, Not } from 'typeorm';
|
2022-09-17 18:27:08 +00:00
|
|
|
|
import { DI } from '@/di-symbols.js';
|
2022-09-20 20:33:11 +00:00
|
|
|
|
import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
|
|
|
|
|
import type { Config } from '@/config.js';
|
2023-02-13 06:50:22 +00:00
|
|
|
|
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
2022-09-17 18:27:08 +00:00
|
|
|
|
import { QueueService } from '@/core/QueueService.js';
|
|
|
|
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
2022-12-04 06:03:09 +00:00
|
|
|
|
import { bindThis } from '@/decorators.js';
|
2023-07-05 03:17:52 +00:00
|
|
|
|
import type { IActivity } from '@/core/activitypub/type.js';
|
2023-07-05 03:15:48 +00:00
|
|
|
|
import { ThinUser } from '@/queue/types.js';
|
2019-11-09 09:51:54 +00:00
|
|
|
|
|
|
|
|
|
interface IRecipe {
|
|
|
|
|
type: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IFollowersRecipe extends IRecipe {
|
|
|
|
|
type: 'Followers';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IDirectRecipe extends IRecipe {
|
|
|
|
|
type: 'Direct';
|
2023-02-13 06:50:22 +00:00
|
|
|
|
to: RemoteUser;
|
2019-11-09 09:51:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-05 03:17:52 +00:00
|
|
|
|
const isFollowers = (recipe: IRecipe): recipe is IFollowersRecipe =>
|
2019-11-09 09:51:54 +00:00
|
|
|
|
recipe.type === 'Followers';
|
|
|
|
|
|
2023-07-05 03:17:52 +00:00
|
|
|
|
const isDirect = (recipe: IRecipe): recipe is IDirectRecipe =>
|
2019-11-09 09:51:54 +00:00
|
|
|
|
recipe.type === 'Direct';
|
|
|
|
|
|
2022-09-17 18:27:08 +00:00
|
|
|
|
@Injectable()
|
|
|
|
|
export class ApDeliverManagerService {
|
|
|
|
|
constructor(
|
|
|
|
|
@Inject(DI.config)
|
|
|
|
|
private config: Config,
|
|
|
|
|
|
|
|
|
|
@Inject(DI.usersRepository)
|
|
|
|
|
private usersRepository: UsersRepository,
|
|
|
|
|
|
|
|
|
|
@Inject(DI.followingsRepository)
|
|
|
|
|
private followingsRepository: FollowingsRepository,
|
|
|
|
|
|
|
|
|
|
private userEntityService: UserEntityService,
|
|
|
|
|
private queueService: QueueService,
|
|
|
|
|
) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Deliver activity to followers
|
2023-07-05 03:17:52 +00:00
|
|
|
|
* @param actor
|
2022-09-17 18:27:08 +00:00
|
|
|
|
* @param activity Activity
|
|
|
|
|
*/
|
2022-12-04 06:03:09 +00:00
|
|
|
|
@bindThis
|
2023-07-05 03:17:52 +00:00
|
|
|
|
public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity) {
|
2022-09-17 18:27:08 +00:00
|
|
|
|
const manager = new DeliverManager(
|
|
|
|
|
this.userEntityService,
|
|
|
|
|
this.followingsRepository,
|
|
|
|
|
this.queueService,
|
|
|
|
|
actor,
|
|
|
|
|
activity,
|
|
|
|
|
);
|
|
|
|
|
manager.addFollowersRecipe();
|
|
|
|
|
await manager.execute();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Deliver activity to user
|
2023-07-05 03:17:52 +00:00
|
|
|
|
* @param actor
|
2022-09-17 18:27:08 +00:00
|
|
|
|
* @param activity Activity
|
|
|
|
|
* @param to Target user
|
|
|
|
|
*/
|
2022-12-04 06:03:09 +00:00
|
|
|
|
@bindThis
|
2023-07-05 03:17:52 +00:00
|
|
|
|
public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser) {
|
2022-09-17 18:27:08 +00:00
|
|
|
|
const manager = new DeliverManager(
|
|
|
|
|
this.userEntityService,
|
|
|
|
|
this.followingsRepository,
|
|
|
|
|
this.queueService,
|
|
|
|
|
actor,
|
|
|
|
|
activity,
|
|
|
|
|
);
|
|
|
|
|
manager.addDirectRecipe(to);
|
|
|
|
|
await manager.execute();
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-04 06:03:09 +00:00
|
|
|
|
@bindThis
|
2023-07-05 03:17:52 +00:00
|
|
|
|
public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null) {
|
2022-09-17 18:27:08 +00:00
|
|
|
|
return new DeliverManager(
|
|
|
|
|
this.userEntityService,
|
|
|
|
|
this.followingsRepository,
|
|
|
|
|
this.queueService,
|
|
|
|
|
|
2023-07-07 22:08:16 +00:00
|
|
|
|
actor,
|
2022-09-17 18:27:08 +00:00
|
|
|
|
activity,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class DeliverManager {
|
2023-07-05 03:15:48 +00:00
|
|
|
|
private actor: ThinUser;
|
2023-07-05 03:17:52 +00:00
|
|
|
|
private activity: IActivity | null;
|
2019-11-09 09:51:54 +00:00
|
|
|
|
private recipes: IRecipe[] = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor
|
2023-07-05 03:17:52 +00:00
|
|
|
|
* @param userEntityService
|
|
|
|
|
* @param followingsRepository
|
|
|
|
|
* @param queueService
|
2019-11-09 09:51:54 +00:00
|
|
|
|
* @param actor Actor
|
|
|
|
|
* @param activity Activity to deliver
|
|
|
|
|
*/
|
2022-09-17 18:27:08 +00:00
|
|
|
|
constructor(
|
|
|
|
|
private userEntityService: UserEntityService,
|
|
|
|
|
private followingsRepository: FollowingsRepository,
|
|
|
|
|
private queueService: QueueService,
|
|
|
|
|
|
|
|
|
|
actor: { id: User['id']; host: null; },
|
2023-07-05 03:17:52 +00:00
|
|
|
|
activity: IActivity | null,
|
2022-09-17 18:27:08 +00:00
|
|
|
|
) {
|
2023-07-05 03:15:48 +00:00
|
|
|
|
// 型で弾いてはいるが一応ローカルユーザーかチェック
|
|
|
|
|
if (actor.host != null) throw new Error('actor.host must be null');
|
|
|
|
|
|
|
|
|
|
// パフォーマンス向上のためキューに突っ込むのはidのみに絞る
|
|
|
|
|
this.actor = {
|
|
|
|
|
id: actor.id,
|
|
|
|
|
};
|
2019-11-09 09:51:54 +00:00
|
|
|
|
this.activity = activity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add recipe for followers deliver
|
|
|
|
|
*/
|
2022-12-04 06:03:09 +00:00
|
|
|
|
@bindThis
|
2019-11-09 09:51:54 +00:00
|
|
|
|
public addFollowersRecipe() {
|
|
|
|
|
const deliver = {
|
2021-12-09 14:58:30 +00:00
|
|
|
|
type: 'Followers',
|
2019-11-09 09:51:54 +00:00
|
|
|
|
} as IFollowersRecipe;
|
|
|
|
|
|
|
|
|
|
this.addRecipe(deliver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add recipe for direct deliver
|
|
|
|
|
* @param to To
|
|
|
|
|
*/
|
2022-12-04 06:03:09 +00:00
|
|
|
|
@bindThis
|
2023-02-13 06:50:22 +00:00
|
|
|
|
public addDirectRecipe(to: RemoteUser) {
|
2019-11-09 09:51:54 +00:00
|
|
|
|
const recipe = {
|
|
|
|
|
type: 'Direct',
|
2021-12-09 14:58:30 +00:00
|
|
|
|
to,
|
2019-11-09 09:51:54 +00:00
|
|
|
|
} as IDirectRecipe;
|
|
|
|
|
|
|
|
|
|
this.addRecipe(recipe);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add recipe
|
|
|
|
|
* @param recipe Recipe
|
|
|
|
|
*/
|
2022-12-04 06:03:09 +00:00
|
|
|
|
@bindThis
|
2019-11-09 09:51:54 +00:00
|
|
|
|
public addRecipe(recipe: IRecipe) {
|
|
|
|
|
this.recipes.push(recipe);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Execute delivers
|
|
|
|
|
*/
|
2022-12-04 06:03:09 +00:00
|
|
|
|
@bindThis
|
2019-11-09 09:51:54 +00:00
|
|
|
|
public async execute() {
|
2023-03-14 10:11:31 +00:00
|
|
|
|
// The value flags whether it is shared or not.
|
2023-07-05 03:15:48 +00:00
|
|
|
|
// key: inbox URL, value: whether it is sharedInbox
|
2023-03-14 10:11:31 +00:00
|
|
|
|
const inboxes = new Map<string, boolean>();
|
2019-11-09 09:51:54 +00:00
|
|
|
|
|
2022-04-02 06:16:35 +00:00
|
|
|
|
/*
|
|
|
|
|
build inbox list
|
|
|
|
|
|
|
|
|
|
Process follower recipes first to avoid duplication when processing
|
|
|
|
|
direct recipes later.
|
|
|
|
|
*/
|
2022-04-02 06:31:11 +00:00
|
|
|
|
if (this.recipes.some(r => isFollowers(r))) {
|
2022-04-02 06:16:35 +00:00
|
|
|
|
// followers deliver
|
|
|
|
|
// TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
|
|
|
|
|
// ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
|
2022-09-17 18:27:08 +00:00
|
|
|
|
const followers = await this.followingsRepository.find({
|
2022-04-02 06:16:35 +00:00
|
|
|
|
where: {
|
|
|
|
|
followeeId: this.actor.id,
|
|
|
|
|
followerHost: Not(IsNull()),
|
|
|
|
|
},
|
|
|
|
|
select: {
|
|
|
|
|
followerSharedInbox: true,
|
|
|
|
|
followerInbox: true,
|
|
|
|
|
},
|
|
|
|
|
}) as {
|
|
|
|
|
followerSharedInbox: string | null;
|
|
|
|
|
followerInbox: string;
|
|
|
|
|
}[];
|
|
|
|
|
|
|
|
|
|
for (const following of followers) {
|
2022-09-17 18:27:08 +00:00
|
|
|
|
const inbox = following.followerSharedInbox ?? following.followerInbox;
|
2023-04-09 01:19:57 +00:00
|
|
|
|
inboxes.set(inbox, following.followerSharedInbox != null);
|
2019-11-09 09:51:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-03 06:33:22 +00:00
|
|
|
|
this.recipes.filter((recipe): recipe is IDirectRecipe =>
|
2022-04-02 06:16:35 +00:00
|
|
|
|
// followers recipes have already been processed
|
|
|
|
|
isDirect(recipe)
|
|
|
|
|
// check that shared inbox has not been added yet
|
|
|
|
|
&& !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox))
|
|
|
|
|
// check that they actually have an inbox
|
2022-04-03 06:33:22 +00:00
|
|
|
|
&& recipe.to.inbox != null,
|
|
|
|
|
)
|
2023-03-14 10:11:31 +00:00
|
|
|
|
.forEach(recipe => inboxes.set(recipe.to.inbox!, false));
|
2022-04-02 06:16:35 +00:00
|
|
|
|
|
2019-11-09 09:51:54 +00:00
|
|
|
|
// deliver
|
2023-07-05 03:15:48 +00:00
|
|
|
|
this.queueService.deliverMany(this.actor, this.activity, inboxes);
|
2019-11-09 09:51:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|