2024-06-08 01:34:19 -05:00
|
|
|
|
/*
|
|
|
|
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
|
|
|
|
import { In } from 'typeorm';
|
|
|
|
|
import { DI } from '@/di-symbols.js';
|
|
|
|
|
import { bindThis } from '@/decorators.js';
|
|
|
|
|
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiUser, UsersRepository } from '@/models/_.js';
|
|
|
|
|
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
|
|
|
|
import { QueueService } from '@/core/QueueService.js';
|
|
|
|
|
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
|
|
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
|
|
|
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
|
|
|
|
import { IdService } from './IdService.js';
|
|
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class AbuseReportService {
|
|
|
|
|
constructor(
|
|
|
|
|
@Inject(DI.abuseUserReportsRepository)
|
|
|
|
|
private abuseUserReportsRepository: AbuseUserReportsRepository,
|
2024-10-05 02:20:15 -05:00
|
|
|
|
|
2024-06-08 01:34:19 -05:00
|
|
|
|
@Inject(DI.usersRepository)
|
|
|
|
|
private usersRepository: UsersRepository,
|
2024-10-05 02:20:15 -05:00
|
|
|
|
|
2024-06-08 01:34:19 -05:00
|
|
|
|
private idService: IdService,
|
|
|
|
|
private abuseReportNotificationService: AbuseReportNotificationService,
|
|
|
|
|
private queueService: QueueService,
|
|
|
|
|
private instanceActorService: InstanceActorService,
|
|
|
|
|
private apRendererService: ApRendererService,
|
|
|
|
|
private moderationLogService: ModerationLogService,
|
|
|
|
|
) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ユーザからの通報をDBに記録し、その内容を下記の手段で管理者各位に通知する.
|
|
|
|
|
* - 管理者用Redisイベント
|
|
|
|
|
* - EMail(モデレータ権限所有者ユーザ+metaテーブルに設定されているメールアドレス)
|
|
|
|
|
* - SystemWebhook
|
|
|
|
|
*
|
|
|
|
|
* @param params 通報内容. もし複数件の通報に対応した時のために、あらかじめ複数件を処理できる前提で考える
|
|
|
|
|
* @see AbuseReportNotificationService.notify
|
|
|
|
|
*/
|
|
|
|
|
@bindThis
|
|
|
|
|
public async report(params: {
|
|
|
|
|
targetUserId: MiAbuseUserReport['targetUserId'],
|
|
|
|
|
targetUserHost: MiAbuseUserReport['targetUserHost'],
|
|
|
|
|
reporterId: MiAbuseUserReport['reporterId'],
|
|
|
|
|
reporterHost: MiAbuseUserReport['reporterHost'],
|
|
|
|
|
comment: string,
|
|
|
|
|
}[]) {
|
|
|
|
|
const entities = params.map(param => {
|
|
|
|
|
return {
|
|
|
|
|
id: this.idService.gen(),
|
|
|
|
|
targetUserId: param.targetUserId,
|
|
|
|
|
targetUserHost: param.targetUserHost,
|
|
|
|
|
reporterId: param.reporterId,
|
|
|
|
|
reporterHost: param.reporterHost,
|
|
|
|
|
comment: param.comment,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const reports = Array.of<MiAbuseUserReport>();
|
|
|
|
|
for (const entity of entities) {
|
|
|
|
|
const report = await this.abuseUserReportsRepository.insertOne(entity);
|
|
|
|
|
reports.push(report);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
|
this.abuseReportNotificationService.notifyAdminStream(reports),
|
|
|
|
|
this.abuseReportNotificationService.notifySystemWebhook(reports, 'abuseReport'),
|
|
|
|
|
this.abuseReportNotificationService.notifyMail(reports),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 通報を解決し、その内容を下記の手段で管理者各位に通知する.
|
|
|
|
|
* - SystemWebhook
|
|
|
|
|
*
|
|
|
|
|
* @param params 通報内容. もし複数件の通報に対応した時のために、あらかじめ複数件を処理できる前提で考える
|
2024-10-05 02:20:15 -05:00
|
|
|
|
* @param moderator 通報を処理したユーザ
|
2024-06-08 01:34:19 -05:00
|
|
|
|
* @see AbuseReportNotificationService.notify
|
|
|
|
|
*/
|
|
|
|
|
@bindThis
|
|
|
|
|
public async resolve(
|
|
|
|
|
params: {
|
|
|
|
|
reportId: string;
|
2024-10-05 02:20:15 -05:00
|
|
|
|
resolvedAs: MiAbuseUserReport['resolvedAs'];
|
2024-06-08 01:34:19 -05:00
|
|
|
|
}[],
|
2024-10-05 02:20:15 -05:00
|
|
|
|
moderator: MiUser,
|
2024-06-08 01:34:19 -05:00
|
|
|
|
) {
|
|
|
|
|
const paramsMap = new Map(params.map(it => [it.reportId, it]));
|
|
|
|
|
const reports = await this.abuseUserReportsRepository.findBy({
|
|
|
|
|
id: In(params.map(it => it.reportId)),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (const report of reports) {
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
|
|
const ps = paramsMap.get(report.id)!;
|
|
|
|
|
|
|
|
|
|
await this.abuseUserReportsRepository.update(report.id, {
|
|
|
|
|
resolved: true,
|
2024-10-05 02:20:15 -05:00
|
|
|
|
assigneeId: moderator.id,
|
|
|
|
|
resolvedAs: ps.resolvedAs,
|
2024-06-08 01:34:19 -05:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.moderationLogService
|
2024-10-05 02:20:15 -05:00
|
|
|
|
.log(moderator, 'resolveAbuseReport', {
|
2024-06-08 01:34:19 -05:00
|
|
|
|
reportId: report.id,
|
|
|
|
|
report: report,
|
2024-10-05 02:20:15 -05:00
|
|
|
|
resolvedAs: ps.resolvedAs,
|
2024-10-13 06:32:02 -05:00
|
|
|
|
});
|
2024-06-08 01:34:19 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.abuseUserReportsRepository.findBy({ id: In(reports.map(it => it.id)) })
|
|
|
|
|
.then(reports => this.abuseReportNotificationService.notifySystemWebhook(reports, 'abuseReportResolved'));
|
|
|
|
|
}
|
2024-10-05 02:20:15 -05:00
|
|
|
|
|
|
|
|
|
@bindThis
|
|
|
|
|
public async forward(
|
|
|
|
|
reportId: MiAbuseUserReport['id'],
|
|
|
|
|
moderator: MiUser,
|
|
|
|
|
) {
|
|
|
|
|
const report = await this.abuseUserReportsRepository.findOneByOrFail({ id: reportId });
|
|
|
|
|
|
|
|
|
|
if (report.targetUserHost == null) {
|
|
|
|
|
throw new Error('The target user host is null.');
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-05 04:29:02 -05:00
|
|
|
|
if (report.forwarded) {
|
|
|
|
|
throw new Error('The report has already been forwarded.');
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-05 02:20:15 -05:00
|
|
|
|
await this.abuseUserReportsRepository.update(report.id, {
|
|
|
|
|
forwarded: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const actor = await this.instanceActorService.getInstanceActor();
|
|
|
|
|
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
|
|
|
|
|
|
|
|
|
|
const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
|
|
|
|
|
const contextAssignedFlag = this.apRendererService.addContext(flag);
|
|
|
|
|
this.queueService.deliver(actor, contextAssignedFlag, targetUser.inbox, false);
|
|
|
|
|
|
|
|
|
|
this.moderationLogService
|
|
|
|
|
.log(moderator, 'forwardAbuseReport', {
|
|
|
|
|
reportId: report.id,
|
|
|
|
|
report: report,
|
2024-10-13 06:32:02 -05:00
|
|
|
|
});
|
2024-10-05 02:20:15 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@bindThis
|
|
|
|
|
public async update(
|
|
|
|
|
reportId: MiAbuseUserReport['id'],
|
|
|
|
|
params: {
|
|
|
|
|
moderationNote?: MiAbuseUserReport['moderationNote'];
|
|
|
|
|
},
|
|
|
|
|
moderator: MiUser,
|
|
|
|
|
) {
|
|
|
|
|
const report = await this.abuseUserReportsRepository.findOneByOrFail({ id: reportId });
|
|
|
|
|
|
|
|
|
|
await this.abuseUserReportsRepository.update(report.id, {
|
|
|
|
|
moderationNote: params.moderationNote,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (params.moderationNote != null && report.moderationNote !== params.moderationNote) {
|
|
|
|
|
this.moderationLogService.log(moderator, 'updateAbuseReportNote', {
|
|
|
|
|
reportId: report.id,
|
|
|
|
|
report: report,
|
|
|
|
|
before: report.moderationNote,
|
|
|
|
|
after: params.moderationNote,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-08 01:34:19 -05:00
|
|
|
|
}
|