feat: 通報の即時解決機能を追加 (#113)
* feat: 通報の即時解決機能を追加 * fix: 条件変更時に有効期限を変更していないのに勝手に更新される問題を修正 * fix: 条件のパターンの削除ができない問題を修正 * fix: リソルバーの通報を解決する判定基準が間違っていたのを修正 * fix: 変更する変数が間違っていたのを修正 * fix: getUTCMonthはゼロ始まりかも * enhance: Storybookのストーリーを作成 * fix: 色々修正 * fix: 型エラーを修正 * [ci skip] Update CHANGELOG.md * [ci skip] Update CHANGELOG.md * Update CHANGELOG.md * リファクタリング * refactor: 型定義をよりよくした * refactor: beforeExpiresAtの初期値はundefinedの方がいい * refactor: 変数の名前を変更 * Fix: リモートサーバーから転送された通報も対象に追加 * Update CHANGELOG.md * take review --------- Co-authored-by: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com>
This commit is contained in:
		| @@ -28,6 +28,7 @@ import { ImportMutingProcessorService } from './processors/ImportMutingProcessor | ||||
| import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js'; | ||||
| import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js'; | ||||
| import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; | ||||
| import { ReportAbuseProcessorService } from './processors/ReportAbuseProcessorService.js'; | ||||
| import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js'; | ||||
| import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; | ||||
| import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js'; | ||||
| @@ -64,6 +65,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor | ||||
| 		DeleteFileProcessorService, | ||||
| 		CleanRemoteFilesProcessorService, | ||||
| 		RelationshipProcessorService, | ||||
| 		ReportAbuseProcessorService, | ||||
| 		WebhookDeliverProcessorService, | ||||
| 		EndedPollNotificationProcessorService, | ||||
| 		DeliverProcessorService, | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import { ExportFavoritesProcessorService } from './processors/ExportFavoritesPro | ||||
| import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; | ||||
| import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js'; | ||||
| import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js'; | ||||
| import { ReportAbuseProcessorService } from './processors/ReportAbuseProcessorService.js'; | ||||
| import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js'; | ||||
| import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; | ||||
| import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; | ||||
| @@ -102,6 +103,7 @@ export class QueueProcessorService implements OnApplicationShutdown { | ||||
| 		private deleteFileProcessorService: DeleteFileProcessorService, | ||||
| 		private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService, | ||||
| 		private relationshipProcessorService: RelationshipProcessorService, | ||||
| 		private reportAbuseProcessorService: ReportAbuseProcessorService, | ||||
| 		private tickChartsProcessorService: TickChartsProcessorService, | ||||
| 		private resyncChartsProcessorService: ResyncChartsProcessorService, | ||||
| 		private cleanChartsProcessorService: CleanChartsProcessorService, | ||||
| @@ -174,6 +176,7 @@ export class QueueProcessorService implements OnApplicationShutdown { | ||||
| 				case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job); | ||||
| 				case 'importAntennas': return this.importAntennasProcessorService.process(job); | ||||
| 				case 'deleteAccount': return this.deleteAccountProcessorService.process(job); | ||||
| 				case 'reportAbuse': return this.reportAbuseProcessorService.process(job); | ||||
| 				default: throw new Error(`unrecognized job type ${job.name} for db`); | ||||
| 			} | ||||
| 		}, { | ||||
|   | ||||
| @@ -0,0 +1,114 @@ | ||||
| import { Injectable, Inject } from '@nestjs/common'; | ||||
| import { MoreThan, IsNull } from 'typeorm'; | ||||
| import RE2 from 're2'; | ||||
| import sanitizeHtml from 'sanitize-html'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type Logger from '@/logger.js'; | ||||
| import { RoleService } from '@/core/RoleService.js'; | ||||
| import { MetaService } from '@/core/MetaService.js'; | ||||
| import { EmailService } from '@/core/EmailService.js'; | ||||
| import { GlobalEventService } from '@/core/GlobalEventService.js'; | ||||
| import { InstanceActorService } from '@/core/InstanceActorService.js'; | ||||
| import type { AbuseReportResolversRepository, AbuseUserReportsRepository, UsersRepository } from '@/models/index.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; | ||||
| import { QueueService } from '@/core/QueueService.js'; | ||||
| import { QueueLoggerService } from '../QueueLoggerService.js'; | ||||
| import type { DbAbuseReportJobData } from '../types.js'; | ||||
| import type * as Bull from 'bullmq'; | ||||
|  | ||||
| @Injectable() | ||||
| export class ReportAbuseProcessorService { | ||||
| 	private logger: Logger; | ||||
|  | ||||
| 	constructor( | ||||
| 		@Inject(DI.abuseReportResolversRepository) | ||||
| 		private abuseReportResolversRepository: AbuseReportResolversRepository, | ||||
|  | ||||
| 		@Inject(DI.abuseUserReportsRepository) | ||||
| 		private abuseUserReportsRepository: AbuseUserReportsRepository, | ||||
|  | ||||
| 		@Inject(DI.usersRepository) | ||||
| 		private usersRepository: UsersRepository, | ||||
|  | ||||
| 		private queueLoggerService: QueueLoggerService, | ||||
| 		private globalEventService: GlobalEventService, | ||||
| 		private instanceActorService: InstanceActorService, | ||||
| 		private apRendererService: ApRendererService, | ||||
| 		private roleService: RoleService, | ||||
| 		private metaService: MetaService, | ||||
| 		private emailService: EmailService, | ||||
| 		private queueService: QueueService, | ||||
| 	) { | ||||
| 		this.logger = this.queueLoggerService.logger.createSubLogger('report-abuse'); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async process(job: Bull.Job<DbAbuseReportJobData>): Promise<void> { | ||||
| 		this.logger.info('Running...'); | ||||
|  | ||||
| 		const resolvers = await this.abuseReportResolversRepository.find({ | ||||
| 			where: [ | ||||
| 				{ expirationDate: MoreThan(new Date()) }, | ||||
| 				{ expirationDate: IsNull() }, | ||||
| 			], | ||||
| 		}); | ||||
|  | ||||
| 		const targetUser = await this.usersRepository.findOneByOrFail({ | ||||
| 			id: job.data.targetUserId, | ||||
| 		}); | ||||
|  | ||||
| 		const reporter = await this.usersRepository.findOneByOrFail({ | ||||
| 			id: job.data.reporterId, | ||||
| 		}); | ||||
|  | ||||
| 		const actor = await this.instanceActorService.getInstanceActor(); | ||||
|  | ||||
| 		const targetUserAcct = targetUser.host ? `${targetUser.username.toLowerCase()}@${targetUser.host}` : targetUser.username.toLowerCase(); | ||||
| 		const reporterAcct = reporter.host ? `${reporter.username.toLowerCase()}@${reporter.host}` : reporter.username.toLowerCase(); | ||||
|  | ||||
| 		for (const resolver of resolvers) { | ||||
| 			if (!(resolver.targetUserPattern || resolver.reporterPattern || resolver.reportContentPattern)) { | ||||
| 				continue; | ||||
| 			} | ||||
| 			const isTargetUserPatternMatched = resolver.targetUserPattern ? new RE2(resolver.targetUserPattern).test(targetUserAcct) : true; | ||||
| 			const isReporterPatternMatched = resolver.reporterPattern ? new RE2(resolver.reporterPattern).test(reporterAcct) : true; | ||||
| 			const isReportContentPatternMatched = resolver.reportContentPattern ? new RE2(resolver.reportContentPattern).test(job.data.comment) : true; | ||||
|  | ||||
| 			if (isTargetUserPatternMatched && isReporterPatternMatched && isReportContentPatternMatched) { | ||||
| 				if (resolver.forward && job.data.targetUserHost !== null) { | ||||
| 					this.queueService.deliver(actor, this.apRendererService.addContext(this.apRendererService.renderFlag(actor, targetUser.uri!, job.data.comment)), targetUser.inbox, false); | ||||
| 				} | ||||
|  | ||||
| 				await this.abuseUserReportsRepository.update(job.data.id, { | ||||
| 					resolved: true, | ||||
| 					assigneeId: actor.id, | ||||
| 					forwarded: resolver.forward && job.data.targetUserHost !== null, | ||||
| 				}); | ||||
| 				 | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Publish event to moderators | ||||
| 		setImmediate(async () => { | ||||
| 			const moderators = await this.roleService.getModerators(); | ||||
|  | ||||
| 			for (const moderator of moderators) { | ||||
| 				this.globalEventService.publishAdminStream(moderator.id, 'newAbuseUserReport', { | ||||
| 					id: job.data.id, | ||||
| 					targetUserId: job.data.targetUserId, | ||||
| 					reporterId: job.data.reporterId, | ||||
| 					comment: job.data.comment, | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			const meta = await this.metaService.fetch(); | ||||
| 			if (meta.email) { | ||||
| 				this.emailService.sendEmail(meta.email, 'New abuse report', | ||||
| 					sanitizeHtml(job.data.comment), | ||||
| 					sanitizeHtml(job.data.comment)); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| @@ -2,6 +2,7 @@ import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; | ||||
| import type { DriveFile } from '@/models/entities/DriveFile.js'; | ||||
| import type { Note } from '@/models/entities/Note.js'; | ||||
| import type { User } from '@/models/entities/User.js'; | ||||
| import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; | ||||
| import type { Webhook } from '@/models/entities/Webhook.js'; | ||||
| import type { IActivity } from '@/core/activitypub/type.js'; | ||||
| import type httpSignature from '@peertube/http-signature'; | ||||
| @@ -86,6 +87,8 @@ export type DbUserImportToDbJobData = { | ||||
| 	target: string; | ||||
| }; | ||||
|  | ||||
| export type DbAbuseReportJobData = AbuseUserReport; | ||||
|  | ||||
| export type ObjectStorageJobData = ObjectStorageFileJobData | Record<string, unknown>; | ||||
|  | ||||
| export type ObjectStorageFileJobData = { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 まっちゃとーにゅ
					まっちゃとーにゅ