summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/UserFollowingService.ts
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2024-03-02 17:28:34 +0000
committerdakkar <dakkar@thenautilus.net>2024-03-02 17:28:34 +0000
commit23f476dbf32ef9a2fc7d2ed7aab9ce706a2409d0 (patch)
tree0b9e79c2f18f4a206811561fa255f2510f60c175 /packages/backend/src/core/UserFollowingService.ts
parentmerge: Add missing IMPORTANT_NOTES.md from Sharkey/OldJoinSharkey (!443) (diff)
parentmerge: put back the readme (!447) (diff)
downloadsharkey-23f476dbf32ef9a2fc7d2ed7aab9ce706a2409d0.tar.gz
sharkey-23f476dbf32ef9a2fc7d2ed7aab9ce706a2409d0.tar.bz2
sharkey-23f476dbf32ef9a2fc7d2ed7aab9ce706a2409d0.zip
Merge branch 'develop' into release/2024.3.1
Diffstat (limited to 'packages/backend/src/core/UserFollowingService.ts')
-rw-r--r--packages/backend/src/core/UserFollowingService.ts75
1 files changed, 56 insertions, 19 deletions
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index d600ffb9d9..deeecdeb1f 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
@@ -30,6 +30,7 @@ import type { Config } from '@/config.js';
import { AccountMoveService } from '@/core/AccountMoveService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
+import type { ThinUser } from '@/queue/types.js';
import Logger from '../logger.js';
const logger = new Logger('following/create');
@@ -95,20 +96,34 @@ export class UserFollowingService implements OnModuleInit {
}
@bindThis
+ public async deliverAccept(follower: MiRemoteUser, followee: MiPartialLocalUser, requestId?: string) {
+ const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
+ this.queueService.deliver(followee, content, follower.inbox, false);
+ }
+
+ @bindThis
public async follow(
- _follower: { id: MiUser['id'] },
- _followee: { id: MiUser['id'] },
+ _follower: ThinUser,
+ _followee: ThinUser,
{ requestId, silent = false, withReplies }: {
requestId?: string,
silent?: boolean,
withReplies?: boolean,
} = {},
): Promise<void> {
+ /**
+ * 必ず最新のユーザー情報を取得する
+ */
const [follower, followee] = await Promise.all([
this.usersRepository.findOneByOrFail({ id: _follower.id }),
this.usersRepository.findOneByOrFail({ id: _followee.id }),
]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser];
+ if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isRemoteUser(followee)) {
+ // What?
+ throw new Error('Remote user cannot follow remote user.');
+ }
+
// check blocking
const [blocking, blocked] = await Promise.all([
this.userBlockingService.checkBlocked(follower.id, followee.id),
@@ -129,6 +144,24 @@ export class UserFollowingService implements OnModuleInit {
if (blocked) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked');
}
+ if (await this.followingsRepository.exists({
+ where: {
+ followerId: follower.id,
+ followeeId: followee.id,
+ },
+ })) {
+ // すでにフォロー関係が存在している場合
+ if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
+ // リモート → ローカル: acceptを送り返しておしまい
+ this.deliverAccept(follower, followee, requestId);
+ return;
+ }
+ if (this.userEntityService.isLocalUser(follower)) {
+ // ローカル → リモート/ローカル: 例外
+ throw new IdentifiableError('ec3f65c0-a9d1-47d9-8791-b2e7b9dcdced', 'already following');
+ }
+ }
+
const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id });
// フォロー対象が鍵アカウントである or
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
@@ -144,7 +177,7 @@ export class UserFollowingService implements OnModuleInit {
let autoAccept = false;
// 鍵アカウントであっても、既にフォローされていた場合はスルー
- const isFollowing = await this.followingsRepository.exist({
+ const isFollowing = await this.followingsRepository.exists({
where: {
followerId: follower.id,
followeeId: followee.id,
@@ -156,7 +189,7 @@ export class UserFollowingService implements OnModuleInit {
// フォローしているユーザーは自動承認オプション
if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
- const isFollowed = await this.followingsRepository.exist({
+ const isFollowed = await this.followingsRepository.exists({
where: {
followerId: followee.id,
followeeId: follower.id,
@@ -170,7 +203,7 @@ export class UserFollowingService implements OnModuleInit {
if (followee.isLocked && !autoAccept) {
autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
follower,
- (oldSrc, newSrc) => this.followingsRepository.exist({
+ (oldSrc, newSrc) => this.followingsRepository.exists({
where: {
followeeId: followee.id,
followerId: newSrc.id,
@@ -189,8 +222,7 @@ export class UserFollowingService implements OnModuleInit {
await this.insertFollowingDoc(followee, follower, silent, withReplies);
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
- const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
- this.queueService.deliver(followee, content, follower.inbox, false);
+ this.deliverAccept(follower, followee, requestId);
}
}
@@ -233,7 +265,7 @@ export class UserFollowingService implements OnModuleInit {
this.cacheService.userFollowingsCache.refresh(follower.id);
- const requestExist = await this.followRequestsRepository.exist({
+ const requestExist = await this.followRequestsRepository.exists({
where: {
followeeId: followee.id,
followerId: follower.id,
@@ -293,9 +325,9 @@ export class UserFollowingService implements OnModuleInit {
if (this.userEntityService.isLocalUser(follower) && !silent) {
// Publish follow event
this.userEntityService.pack(followee.id, follower, {
- detail: true,
+ schema: 'UserDetailedNotMe',
}).then(async packed => {
- this.globalEventService.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>);
+ this.globalEventService.publishMainStream(follower.id, 'follow', packed);
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
for (const webhook of webhooks) {
@@ -360,7 +392,7 @@ export class UserFollowingService implements OnModuleInit {
if (!silent && this.userEntityService.isLocalUser(follower)) {
// Publish unfollow event
this.userEntityService.pack(followee.id, follower, {
- detail: true,
+ schema: 'UserDetailedNotMe',
}).then(async packed => {
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
@@ -479,6 +511,12 @@ export class UserFollowingService implements OnModuleInit {
if (blocking) throw new Error('blocking');
if (blocked) throw new Error('blocked');
+ // Remove old follow requests before creating a new one.
+ await this.followRequestsRepository.delete({
+ followeeId: followee.id,
+ followerId: follower.id,
+ });
+
const followRequest = await this.followRequestsRepository.insert({
id: this.idService.gen(),
followerId: follower.id,
@@ -500,7 +538,7 @@ export class UserFollowingService implements OnModuleInit {
this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed));
this.userEntityService.pack(followee.id, followee, {
- detail: true,
+ schema: 'MeDetailed',
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
// 通知を作成
@@ -531,7 +569,7 @@ export class UserFollowingService implements OnModuleInit {
}
}
- const requestExist = await this.followRequestsRepository.exist({
+ const requestExist = await this.followRequestsRepository.exists({
where: {
followeeId: followee.id,
followerId: follower.id,
@@ -548,7 +586,7 @@ export class UserFollowingService implements OnModuleInit {
});
this.userEntityService.pack(followee.id, followee, {
- detail: true,
+ schema: 'MeDetailed',
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
}
@@ -571,12 +609,11 @@ export class UserFollowingService implements OnModuleInit {
await this.insertFollowingDoc(followee, follower, false, request.withReplies);
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
- const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee));
- this.queueService.deliver(followee, content, follower.inbox, false);
+ this.deliverAccept(follower, followee as MiPartialLocalUser, request.requestId ?? undefined);
}
this.userEntityService.pack(followee.id, followee, {
- detail: true,
+ schema: 'MeDetailed',
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
}
@@ -696,7 +733,7 @@ export class UserFollowingService implements OnModuleInit {
@bindThis
private async publishUnfollow(followee: Both, follower: Local): Promise<void> {
const packedFollowee = await this.userEntityService.pack(followee.id, follower, {
- detail: true,
+ schema: 'UserDetailedNotMe',
});
this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee);