From 9fc63dc95081ff7988ee758cd9da4c865904ec89 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 14 Jun 2025 01:06:56 -0400 Subject: [PATCH] don't unfollow anyone when suspending a user --- .../backend/src/core/UserSuspendService.ts | 84 ++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index f375dff862..ddadab7022 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not, IsNull } from 'typeorm'; import type { FollowingsRepository, FollowRequestsRepository, UsersRepository } from '@/models/_.js'; -import type { MiUser } from '@/models/User.js'; +import { MiUser } from '@/models/User.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -17,9 +17,15 @@ import { RelationshipJobData } from '@/queue/types.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { isSystemAccount } from '@/misc/is-system-account.js'; import { CacheService } from '@/core/CacheService.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import type Logger from '@/logger.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; +import { trackPromise } from '@/misc/promise-tracker.js'; @Injectable() export class UserSuspendService { + private readonly logger: Logger; + constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -36,7 +42,10 @@ export class UserSuspendService { private apRendererService: ApRendererService, private moderationLogService: ModerationLogService, private readonly cacheService: CacheService, + + loggerService: LoggerService, ) { + this.logger = loggerService.getLogger('user-suspend'); } @bindThis @@ -47,16 +56,16 @@ export class UserSuspendService { isSuspended: true, }); - this.moderationLogService.log(moderator, 'suspend', { + await this.moderationLogService.log(moderator, 'suspend', { userId: user.id, userUsername: user.username, userHost: user.host, }); - (async () => { - await this.postSuspend(user).catch(e => {}); - await this.unFollowAll(user).catch(e => {}); - })(); + trackPromise((async () => { + await this.postSuspend(user); + await this.freezeAll(user); + })().catch(e => this.logger.error(`Error suspending user ${user.id}: ${renderInlineError(e)}`))); } @bindThis @@ -65,33 +74,36 @@ export class UserSuspendService { isSuspended: false, }); - this.moderationLogService.log(moderator, 'unsuspend', { + await this.moderationLogService.log(moderator, 'unsuspend', { userId: user.id, userUsername: user.username, userHost: user.host, }); - (async () => { - await this.postUnsuspend(user).catch(e => {}); - })(); + trackPromise((async () => { + await this.postUnsuspend(user); + await this.unFreezeAll(user); + })().catch(e => this.logger.error(`Error un-suspending for user ${user.id}: ${renderInlineError(e)}`))); } @bindThis private async postSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); + /* this.followRequestsRepository.delete({ followeeId: user.id, }); this.followRequestsRepository.delete({ followerId: user.id, }); + */ if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxにDelete配信 const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); - const queue: string[] = []; + const queue = new Map(); const followings = await this.followingsRepository.find({ where: [ @@ -104,12 +116,12 @@ export class UserSuspendService { const inboxes = followings.map(x => x.followerSharedInbox ?? x.followeeSharedInbox); for (const inbox of inboxes) { - if (inbox != null && !queue.includes(inbox)) queue.push(inbox); + if (inbox != null) { + queue.set(inbox, true); + } } - for (const inbox of queue) { - this.queueService.deliver(user, content, inbox, true); - } + await this.queueService.deliverMany(user, content, queue); } } @@ -121,7 +133,7 @@ export class UserSuspendService { // 知り得る全SharedInboxにUndo Delete配信 const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user), user)); - const queue: string[] = []; + const queue = new Map(); const followings = await this.followingsRepository.find({ where: [ @@ -134,12 +146,12 @@ export class UserSuspendService { const inboxes = followings.map(x => x.followerSharedInbox ?? x.followeeSharedInbox); for (const inbox of inboxes) { - if (inbox != null && !queue.includes(inbox)) queue.push(inbox); + if (inbox != null) { + queue.set(inbox, true); + } } - for (const inbox of queue) { - this.queueService.deliver(user as any, content, inbox, true); - } + await this.queueService.deliverMany(user, content, queue); } } @@ -160,4 +172,36 @@ export class UserSuspendService { } this.queueService.createUnfollowJob(jobs); } + + @bindThis + private async freezeAll(user: MiUser): Promise { + // Freeze follow relations with all remote users + await this.followingsRepository + .createQueryBuilder('following') + .orWhere({ + followeeId: user.id, + followerHost: Not(IsNull()), + }) + .update({ + isFollowerHibernated: true, + }) + .execute(); + } + + @bindThis + private async unFreezeAll(user: MiUser): Promise { + // Restore follow relations with all remote users + await this.followingsRepository + .createQueryBuilder('following') + .innerJoin(MiUser, 'follower', 'user.id = following.followerId') + .andWhere('follower.isHibernated = false') // Don't unfreeze if the follower is *actually* frozen + .andWhere({ + followeeId: user.id, + followerHost: Not(IsNull()), + }) + .update({ + isFollowerHibernated: false, + }) + .execute(); + } }