From f1889d3c5ba58aa22f49b7da9e3d5d54eae304ba Mon Sep 17 00:00:00 2001 From: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com> Date: Sat, 17 Jun 2023 13:01:22 +0900 Subject: [PATCH 01/27] =?UTF-8?q?feat:=20=E9=80=9A=E5=A0=B1=E3=81=AE?= =?UTF-8?q?=E5=8D=B3=E6=99=82=E8=A7=A3=E6=B1=BA=E6=A9=9F=E8=83=BD=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 19 ++ locales/ja-JP.yml | 18 ++ .../1686908762393-AbuseReportResolver.js | 19 ++ packages/backend/src/core/QueueService.ts | 6 + packages/backend/src/di-symbols.ts | 1 + .../backend/src/models/RepositoryModule.ts | 10 +- .../models/entities/AbuseReportResolver.ts | 58 ++++++ packages/backend/src/models/index.ts | 3 + packages/backend/src/postgres.ts | 2 + .../backend/src/queue/QueueProcessorModule.ts | 2 + .../src/queue/QueueProcessorService.ts | 3 + .../processors/ReportAbuseProcessorService.ts | 112 ++++++++++++ packages/backend/src/queue/types.ts | 3 + .../backend/src/server/api/EndpointsModule.ts | 16 ++ packages/backend/src/server/api/endpoints.ts | 8 + .../admin/abuse-report-resolver/create.ts | 135 ++++++++++++++ .../admin/abuse-report-resolver/delete.ts | 47 +++++ .../admin/abuse-report-resolver/list.ts | 77 ++++++++ .../admin/abuse-report-resolver/update.ts | 110 +++++++++++ .../api/endpoints/users/report-abuse.ts | 30 +-- .../src/components/MkAbuseReportResolver.vue | 145 +++++++++++++++ packages/frontend/src/components/MkFolder.vue | 4 + packages/frontend/src/components/MkInput.vue | 8 +- packages/frontend/src/components/MkSelect.vue | 6 +- packages/frontend/src/pages/admin/abuses.vue | 171 +++++++++++++++++- packages/misskey-js/etc/misskey-js.api.md | 18 +- packages/misskey-js/src/api.types.ts | 4 + 27 files changed, 993 insertions(+), 42 deletions(-) create mode 100644 packages/backend/migration/1686908762393-AbuseReportResolver.js create mode 100644 packages/backend/src/models/entities/AbuseReportResolver.ts create mode 100644 packages/backend/src/queue/processors/ReportAbuseProcessorService.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/create.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/delete.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/list.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/update.ts create mode 100644 packages/frontend/src/components/MkAbuseReportResolver.vue diff --git a/locales/index.d.ts b/locales/index.d.ts index eed29f408c..7b8dccaee5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2144,6 +2144,25 @@ export interface Locale { "mention": string; }; }; + "_abuse": { + "_resolver": { + "1hour": string; + "12hours": string; + "1day": string; + "1week": string; + "1month": string; + "3months": string; + "6months": string; + "1year": string; + "indefinitely": string; + "expiresAt": string; + "targetUserPattern": string; + "reporterPattern": string; + "reportContentPattern": string; + "list": string; + "resolver": string; + }; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8004e53575..0a2f8eec10 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2057,3 +2057,21 @@ _webhookSettings: renote: "Renoteされたとき" reaction: "リアクションがあったとき" mention: "メンションされたとき" + +_abuse: + _resolver: + 1hour: "一時間" + 12hours: "半日" + 1day: "一日" + 1week: "一週間" + 1month: "一ヶ月" + 3months: "三ヶ月" + 6months: "六ヶ月" + 1year: "一年" + indefinitely: "無期限" + expiresAt: "この条件の有効期限" + targetUserPattern: "通報先のパターン" + reporterPattern: "通報元のパターン" + reportContentPattern: "通報内容のパターン" + list: "一覧" + resolver: "リソルバー" diff --git a/packages/backend/migration/1686908762393-AbuseReportResolver.js b/packages/backend/migration/1686908762393-AbuseReportResolver.js new file mode 100644 index 0000000000..2550a7c1f3 --- /dev/null +++ b/packages/backend/migration/1686908762393-AbuseReportResolver.js @@ -0,0 +1,19 @@ +export class AbuseReportResolver1686908762393 { + name = 'AbuseReportResolver1686908762393' + + async up(queryRunner) { + await queryRunner.query(`CREATE TYPE "public"."abuse_report_resolver_expiresat_enum" AS ENUM('1hour', '12hours', '1day', '1week', '1month', '3months', '6months', '1year', 'indefinitely')`); + await queryRunner.query(`CREATE TABLE "abuse_report_resolver" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(256) NOT NULL, "targetUserPattern" character varying(1024), "reporterPattern" character varying(1024), "reportContentPattern" character varying(1024), "expirationDate" TIMESTAMP WITH TIME ZONE, "expiresAt" "public"."abuse_report_resolver_expiresat_enum" NOT NULL, "forward" boolean NOT NULL, CONSTRAINT "PK_093500bf1bb38880d38b1bb41dc" PRIMARY KEY ("id")); COMMENT ON COLUMN "abuse_report_resolver"."createdAt" IS 'The created date of AbuseReportResolver'; COMMENT ON COLUMN "abuse_report_resolver"."updatedAt" IS 'The updated date of AbuseReportResolver'; COMMENT ON COLUMN "abuse_report_resolver"."expirationDate" IS 'The expiration date of AbuseReportResolver'`); + await queryRunner.query(`CREATE INDEX "IDX_fdd74ab625ed0f6a30c47b00e0" ON "abuse_report_resolver" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_d90c2c0e555b1eb2e4f19c9ad4" ON "abuse_report_resolver" ("updatedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_e83a32a146021c72ba9bde6675" ON "abuse_report_resolver" ("expirationDate") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_e83a32a146021c72ba9bde6675"`); + await queryRunner.query(`DROP INDEX "public"."IDX_d90c2c0e555b1eb2e4f19c9ad4"`); + await queryRunner.query(`DROP INDEX "public"."IDX_fdd74ab625ed0f6a30c47b00e0"`); + await queryRunner.query(`DROP TABLE "abuse_report_resolver"`); + await queryRunner.query(`DROP TYPE "public"."abuse_report_resolver_expiresat_enum"`); + } +} diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 2ae8a2b754..467e9851f6 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { v4 as uuid } from 'uuid'; import type { IActivity } from '@/core/activitypub/type.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; +import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; @@ -302,6 +303,11 @@ export class QueueService { }); } + @bindThis + public createReportAbuseJob(report: AbuseUserReport) { + return this.dbQueue.add('reportAbuse', report); + } + @bindThis public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean }[]) { const jobs = followings.map(rel => this.generateRelationshipJobData('follow', rel)); diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 4a073f102f..d80677548a 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -9,6 +9,7 @@ export const DI = { //#region Repositories usersRepository: Symbol('usersRepository'), notesRepository: Symbol('notesRepository'), + abuseReportResolversRepository: Symbol('abuseReportResolversRepository'), announcementsRepository: Symbol('announcementsRepository'), announcementReadsRepository: Symbol('announcementReadsRepository'), appsRepository: Symbol('appsRepository'), diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 4231acc046..7040196ee6 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js'; +import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite, AbuseReportResolver } from './index.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -400,6 +400,12 @@ const $userMemosRepository: Provider = { inject: [DI.db], }; +const $abuseReportResolversRepository: Provider = { + provide: DI.abuseReportResolversRepository, + useFactory: (db: DataSource) => db.getRepository(AbuseReportResolver), + inject: [DI.db], +}; + @Module({ imports: [ ], @@ -470,6 +476,7 @@ const $userMemosRepository: Provider = { $flashsRepository, $flashLikesRepository, $userMemosRepository, + $abuseReportResolversRepository, ], exports: [ $usersRepository, @@ -538,6 +545,7 @@ const $userMemosRepository: Provider = { $flashsRepository, $flashLikesRepository, $userMemosRepository, + $abuseReportResolversRepository, ], }) export class RepositoryModule {} diff --git a/packages/backend/src/models/entities/AbuseReportResolver.ts b/packages/backend/src/models/entities/AbuseReportResolver.ts new file mode 100644 index 0000000000..0299caf33c --- /dev/null +++ b/packages/backend/src/models/entities/AbuseReportResolver.ts @@ -0,0 +1,58 @@ +import { Column, Entity, PrimaryColumn, Index } from 'typeorm'; +import { id } from '../id.js'; + +@Entity() +export class AbuseReportResolver { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of AbuseReportResolver', + }) + public createdAt: Date; + + @Index() + @Column('timestamp with time zone', { + comment: 'The updated date of AbuseReportResolver', + }) + public updatedAt: Date; + + @Column('varchar', { + length: 256, + }) + public name: string; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public targetUserPattern: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public reporterPattern: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public reportContentPattern: string | null; + + @Index() + @Column('timestamp with time zone', { + comment: 'The expiration date of AbuseReportResolver', + nullable: true, + }) + public expirationDate: Date | null; + + @Column('enum', { + enum: ['1hour', '12hours', '1day', '1week', '1month', '3months', '6months', '1year', 'indefinitely'] + }) + public expiresAt: string; + + @Column('boolean') + public forward: boolean; +} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 4b230ab742..72cb840bb2 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -1,3 +1,4 @@ +import { AbuseReportResolver } from '@/models/entities/AbuseReportResolver.js'; import { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; import { AccessToken } from '@/models/entities/AccessToken.js'; import { Ad } from '@/models/entities/Ad.js'; @@ -67,6 +68,7 @@ import { FlashLike } from '@/models/entities/FlashLike.js'; import type { Repository } from 'typeorm'; export { + AbuseReportResolver, AbuseUserReport, AccessToken, Ad, @@ -135,6 +137,7 @@ export { UserMemo, }; +export type AbuseReportResolversRepository = Repository; export type AbuseUserReportsRepository = Repository; export type AccessTokensRepository = Repository; export type AdsRepository = Repository; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 488979c409..b3ac33cb23 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -6,6 +6,7 @@ import { DataSource, Logger } from 'typeorm'; import * as highlight from 'cli-highlight'; import { entities as charts } from '@/core/chart/entities.js'; +import { AbuseReportResolver } from '@/models/entities/AbuseReportResolver.js'; import { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; import { AccessToken } from '@/models/entities/AccessToken.js'; import { Ad } from '@/models/entities/Ad.js'; @@ -121,6 +122,7 @@ class MyCustomLogger implements Logger { } export const entities = [ + AbuseReportResolver, Announcement, AnnouncementRead, Meta, diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index e1c6b93d9b..c3c84ddb4c 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -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, diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 42f9c1af7d..cb5c332abe 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -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`); } }, { diff --git a/packages/backend/src/queue/processors/ReportAbuseProcessorService.ts b/packages/backend/src/queue/processors/ReportAbuseProcessorService.ts new file mode 100644 index 0000000000..f66601918a --- /dev/null +++ b/packages/backend/src/queue/processors/ReportAbuseProcessorService.ts @@ -0,0 +1,112 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { MoreThan, IsNull } from 'typeorm'; +import RE2 from 're2'; +import * as 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): Promise { + 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) { + const isTargetUserPatternMatched = resolver.targetUserPattern ? new RE2(resolver.targetUserPattern).test(targetUserAcct) : false; + const isReporterPatternMatched = resolver.reporterPattern ? new RE2(resolver.reporterPattern).test(reporterAcct) : false; + const isReportContentPatternMatched = resolver.reportContentPattern ? new RE2(resolver.reportContentPattern).test(job.data.comment) : false; + + if (isTargetUserPatternMatched || isReporterPatternMatched || isReportContentPatternMatched) { + if (resolver.forward && job.data.targetUserHost !== null) { + const targetUser = await this.usersRepository.findOneByOrFail({ id: job.data.targetUserId }); + 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)); + } + }); + } +} diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 776dd3aa12..c44eec6ada 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -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; export type ObjectStorageFileJobData = { diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 1e32e9988d..9b7c992ea4 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -13,6 +13,10 @@ import * as ep___admin_announcements_create from './endpoints/admin/announcement import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js'; import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js'; import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js'; +import * as ep___admin_abuseReportResolver_create from './endpoints/admin/abuse-report-resolver/create.js'; +import * as ep___admin_abuseReportResolver_update from './endpoints/admin/abuse-report-resolver/update.js'; +import * as ep___admin_abuseReportResolver_delete from './endpoints/admin/abuse-report-resolver/delete.js'; +import * as ep___admin_abuseReportResolver_list from './endpoints/admin/abuse-report-resolver/list.js'; import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js'; import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js'; @@ -354,6 +358,10 @@ const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default }; const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default }; const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default }; +const $admin_abuseReportResolver_create: Provider = { provide: 'ep:admin/abuse-report-resolver/create', useClass: ep___admin_abuseReportResolver_create.default }; +const $admin_abuseReportResolver_update: Provider = { provide: 'ep:admin/abuse-report-resolver/update', useClass: ep___admin_abuseReportResolver_update.default }; +const $admin_abuseReportResolver_list: Provider = { provide: 'ep:admin/abuse-report-resolver/list', useClass: ep___admin_abuseReportResolver_list.default }; +const $admin_abuseReportResolver_delete: Provider = { provide: 'ep:admin/abuse-report-resolver/delete', useClass: ep___admin_abuseReportResolver_delete.default }; const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default }; const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default }; const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default }; @@ -699,6 +707,10 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_announcements_delete, $admin_announcements_list, $admin_announcements_update, + $admin_abuseReportResolver_create, + $admin_abuseReportResolver_delete, + $admin_abuseReportResolver_list, + $admin_abuseReportResolver_update, $admin_deleteAllFilesOfAUser, $admin_drive_cleanRemoteFiles, $admin_drive_cleanup, @@ -1038,6 +1050,10 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_announcements_delete, $admin_announcements_list, $admin_announcements_update, + $admin_abuseReportResolver_create, + $admin_abuseReportResolver_delete, + $admin_abuseReportResolver_list, + $admin_abuseReportResolver_update, $admin_deleteAllFilesOfAUser, $admin_drive_cleanRemoteFiles, $admin_drive_cleanup, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 7e678a6404..322d923c85 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -13,6 +13,10 @@ import * as ep___admin_announcements_create from './endpoints/admin/announcement import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js'; import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js'; import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js'; +import * as ep___admin_abuseReportResolver_create from './endpoints/admin/abuse-report-resolver/create.js'; +import * as ep___admin_abuseReportResolver_update from './endpoints/admin/abuse-report-resolver/update.js'; +import * as ep___admin_abuseReportResolver_delete from './endpoints/admin/abuse-report-resolver/delete.js'; +import * as ep___admin_abuseReportResolver_list from './endpoints/admin/abuse-report-resolver/list.js'; import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js'; import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js'; @@ -352,6 +356,10 @@ const eps = [ ['admin/announcements/delete', ep___admin_announcements_delete], ['admin/announcements/list', ep___admin_announcements_list], ['admin/announcements/update', ep___admin_announcements_update], + ['admin/abuse-report-resolver/create', ep___admin_abuseReportResolver_create], + ['admin/abuse-report-resolver/list', ep___admin_abuseReportResolver_list], + ['admin/abuse-report-resolver/delete', ep___admin_abuseReportResolver_delete], + ['admin/abuse-report-resolver/update', ep___admin_abuseReportResolver_update], ['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser], ['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles], ['admin/drive/cleanup', ep___admin_drive_cleanup], diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/create.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/create.ts new file mode 100644 index 0000000000..cd0b0350a8 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/create.ts @@ -0,0 +1,135 @@ +import { Injectable, Inject } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import type { AbuseReportResolversRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + + requireAdmin: true, + + res: { + type: 'object', + properties: { + name: { + type: 'string', + nullable: false, optional: false, + }, + targetUserPattern: { + type: 'string', + nullable: true, optional: false, + }, + reporterPattern: { + type: 'string', + nullable: true, optional: false, + }, + reportContentPattern: { + type: 'string', + nullable: true, optional: false, + }, + expiresAt: { + type: 'string', + nullable: false, optional: false, + }, + forward: { + type: 'boolean', + nullable: false, optional: false, + }, + }, + }, + + errors: { + invalidRegularExpressionForTargetUser: { + message: 'Invalid regular expression for target user.', + code: 'INVALID_REGULAR_EXPRESSION_FOR_TARGET_USER', + id: 'c008484a-0a14-4e74-86f4-b176dc49fcaa', + }, + invalidRegularExpressionForReporter: { + message: 'Invalid regular expression for reporter.', + code: 'INVALID_REGULAR_EXPRESSION_FOR_REPORTER', + id: '399b4062-257f-44c8-87cc-4ffae2527fbc', + }, + invalidRegularExpressionForReportContent: { + message: 'Invalid regular expression for report content.', + code: 'INVALID_REGULAR_EXPRESSION_FOR_REPORT_CONTENT', + id: '88c124d8-f517-4c63-a464-0abc274168b', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + name: { type: 'string', minLength: 1 }, + targetUserPattern: { type: 'string', nullable: true }, + reporterPattern: { type: 'string', nullable: true }, + reportContentPattern: { type: 'string', nullable: true }, + expiresAt: { type: 'string', enum: ['1hour', '12hours', '1day', '1week', '1month', '3months', '6months', '1year', 'indefinitely'] }, + forward: { type: 'boolean' }, + }, + required: ['name', 'targetUserPattern', 'reporterPattern', 'reportContentPattern', 'expiresAt', 'forward'], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint { + constructor( + @Inject(DI.abuseReportResolversRepository) + private abuseReportResolverRepository: AbuseReportResolversRepository, + + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + if (ps.targetUserPattern) { + try { + new RegExp(ps.targetUserPattern); + } catch (e) { + throw new ApiError(meta.errors.invalidRegularExpressionForTargetUser); + } + } + if (ps.reporterPattern) { + try { + new RegExp(ps.reporterPattern); + } catch (e) { + throw new ApiError(meta.errors.invalidRegularExpressionForReporter); + } + } + if (ps.reportContentPattern) { + try { + new RegExp(ps.reportContentPattern); + } catch (e) { + throw new ApiError(meta.errors.invalidRegularExpressionForReportContent); + } + } + const now = new Date(); + let expirationDate: Date | null = new Date(); + + (ps.expiresAt === '1hour' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('1 hour')); } : + ps.expiresAt === '12hours' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('12 hours')); } : + ps.expiresAt === '1day' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('1 day')); } : + ps.expiresAt === '1week' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('1 week')); } : + ps.expiresAt === '1month' ? function () { expirationDate!.setUTCMonth((expirationDate!.getUTCMonth() + 1) % 12); expirationDate!.setUTCFullYear(expirationDate!.getUTCFullYear() + (Math.floor((expirationDate!.getUTCMonth() + 1) / 12))); } : + ps.expiresAt === '3months' ? function () {expirationDate!.setUTCMonth((expirationDate!.getUTCMonth() + 3) % 12); expirationDate!.setUTCFullYear(expirationDate!.getUTCFullYear() + (Math.floor((expirationDate!.getUTCMonth() + 3) / 12))); } : + ps.expiresAt === '6months' ? function () { expirationDate!.setUTCMonth((expirationDate!.getUTCMonth() + 6) % 12); expirationDate!.setUTCFullYear(expirationDate!.getUTCFullYear() + (Math.floor((expirationDate!.getUTCMonth() + 6) / 12))); } : + ps.expiresAt === '1year' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('1 year')); } : function () { expirationDate = null; })(); + + return await this.abuseReportResolverRepository.insert({ + id: this.idService.genId(), + createdAt: now, + updatedAt: now, + name: ps.name, + targetUserPattern: ps.targetUserPattern, + reporterPattern: ps.reporterPattern, + reportContentPattern: ps.reportContentPattern, + expirationDate, + expiresAt: ps.expiresAt, + forward: ps.forward, + }).then(x => this.abuseReportResolverRepository.findOneByOrFail(x.identifiers[0])); + }); + } +} + diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/delete.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/delete.ts new file mode 100644 index 0000000000..b5829c41c8 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/delete.ts @@ -0,0 +1,47 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import type { AbuseReportResolversRepository } from '@/models/index.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + requireCrendential: true, + + requireAdmin: true, + + errors: { + resolverNotFound: { + message: 'Resolver not found.', + code: 'RESOLVER_NOT_FOUND', + id: '121fbea9-3e49-4456-998a-d4095c7ac5c5', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + resolverId: { type: 'string', format: 'misskey:id' }, + }, + required: ['resolverId'], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint { + constructor( + @Inject(DI.abuseReportResolversRepository) + private abuseReportResolversRepository: AbuseReportResolversRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const resolver = await this.abuseReportResolversRepository.findOneBy({ + id: ps.resolverId, + }); + + if (resolver === null) { + throw new ApiError(meta.errors.resolverNotFound); + } + + await this.abuseReportResolversRepository.delete(ps.resolverId); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/list.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/list.ts new file mode 100644 index 0000000000..7c381923e3 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/list.ts @@ -0,0 +1,77 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { Brackets } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { QueryService } from '@/core/QueryService.js'; +import type { AbuseReportResolversRepository } from '@/models/index.js'; + +export const meta = { + requireCredential: true, + + requireAdmin: true, + + res: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + nullable: false, optional: false, + }, + targetUserPattern: { + type: 'string', + nullable: true, optional: false, + }, + reporterPattern: { + type: 'string', + nullable: true, optional: false, + }, + reportContentPattern: { + type: 'string', + nullable: true, optional: false, + }, + expiresAt: { + type: 'string', + nullable: false, optional: false, + }, + forward: { + type: 'boolean', + nullable: false, optional: false, + }, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'number', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + }, + required: [], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint { + constructor( + @Inject(DI.abuseReportResolversRepository) + private abuseReportResolversRepository: AbuseReportResolversRepository, + + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.abuseReportResolversRepository.createQueryBuilder('abuseReportResolvers'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { + qb.where('abuseReportResolvers.expirationDate > :date', { date: new Date() }); + qb.orWhere('abuseReportResolvers.expirationDate IS NULL'); + })) + .take(ps.limit); + + return await query.getMany(); + }); + } +} + diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/update.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/update.ts new file mode 100644 index 0000000000..53a447246d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/abuse-report-resolver/update.ts @@ -0,0 +1,110 @@ +import { Injectable, Inject } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import type { AbuseReportResolversRepository, AbuseReportResolver } from '@/models/index.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + requireCredential: true, + + requireAdmin: true, + + errors: { + resolverNotFound: { + message: 'Resolver not found.', + id: 'fd32710e-75e1-4d20-bbd2-f89029acb16e', + code: 'RESOLVER_NOT_FOUND', + }, + invalidRegularExpressionForTargetUser: { + message: 'Invalid regular expression for target user.', + code: 'INVALID_REGULAR_EXPRESSION_FOR_TARGET_USER', + id: 'c008484a-0a14-4e74-86f4-b176dc49fcaa', + }, + invalidRegularExpressionForReporter: { + message: 'Invalid regular expression for reporter.', + code: 'INVALID_REGULAR_EXPRESSION_FOR_REPORTER', + id: '399b4062-257f-44c8-87cc-4ffae2527fbc', + }, + invalidRegularExpressionForReportContent: { + message: 'Invalid regular expression for report content.', + code: 'INVALID_REGULAR_EXPRESSION_FOR_REPORT_CONTENT', + id: '88c124d8-f517-4c63-a464-0abc274168b', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + resolverId: { type: 'string', format: 'misskey:id' }, + name: { type: 'string' }, + targetUserPattern: { type: 'string', nullable: true }, + reporterPattern: { type: 'string', nullable: true }, + reportContentPattern: { type: 'string', nullable: true }, + expiresAt: { type: 'string', enum: ['1hour', '12hours', '1day', '1week', '1month', '3months', '6months', '1year', 'indefinitely'] }, + forward: { type: 'boolean' }, + }, + required: ['resolverId'], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint { + constructor( + @Inject(DI.abuseReportResolversRepository) + private abuseReportResolversRepository: AbuseReportResolversRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const properties: Partial> = {}; + const resolver = await this.abuseReportResolversRepository.findOneBy({ + id: ps.resolverId, + }); + if (resolver === null) throw new ApiError(meta.errors.resolverNotFound); + if (ps.name) properties.name = ps.name; + if (ps.targetUserPattern) { + try { + new RegExp(ps.targetUserPattern); + } catch (e) { + throw new ApiError(meta.errors.invalidRegularExpressionForTargetUser); + } + properties.targetUserPattern = ps.targetUserPattern; + } + if (ps.reporterPattern) { + try { + new RegExp(ps.reporterPattern); + } catch (e) { + throw new ApiError(meta.errors.invalidRegularExpressionForReporter); + } + properties.reporterPattern = ps.reporterPattern; + } + if (ps.reportContentPattern) { + try { + new RegExp(ps.reportContentPattern); + } catch (e) { + throw new ApiError(meta.errors.invalidRegularExpressionForReportContent); + } + properties.reportContentPattern = ps.reportContentPattern; + } + if (ps.forward) properties.forward = ps.forward; + if (ps.expiresAt) { + let expirationDate: Date | null = new Date(); + (ps.expiresAt === '1hour' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('1 hour')); } : + ps.expiresAt === '12hours' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('12 hours')); } : + ps.expiresAt === '1day' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('1 day')); } : + ps.expiresAt === '1week' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('1 week')); } : + ps.expiresAt === '1month' ? function () { expirationDate!.setUTCMonth((expirationDate!.getUTCMonth() + 1) % 12); expirationDate!.setUTCFullYear(expirationDate!.getUTCFullYear() + (Math.floor((expirationDate!.getUTCMonth() + 1) / 12))); } : + ps.expiresAt === '3months' ? function () {expirationDate!.setUTCMonth((expirationDate!.getUTCMonth() + 3) % 12); expirationDate!.setUTCFullYear(expirationDate!.getUTCFullYear() + (Math.floor((expirationDate!.getUTCMonth() + 3) / 12))); } : + ps.expiresAt === '6months' ? function () { expirationDate!.setUTCMonth((expirationDate!.getUTCMonth() + 6) % 12); expirationDate!.setUTCFullYear(expirationDate!.getUTCFullYear() + (Math.floor((expirationDate!.getUTCMonth() + 6) / 12))); } : + ps.expiresAt === '1year' ? function () { expirationDate!.setTime(expirationDate!.getTime() + ms('1 year')); } : function () { expirationDate = null; })(); + + properties.expiresAt = ps.expiresAt; + properties.expirationDate = expirationDate; + } + + await this.abuseReportResolversRepository.update(ps.resolverId, { + ...properties, + updatedAt: new Date(), + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index d19d4007d6..5dc59ec295 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,14 +1,11 @@ -import * as sanitizeHtml from 'sanitize-html'; import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository, AbuseUserReportsRepository } from '@/models/index.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { MetaService } from '@/core/MetaService.js'; -import { EmailService } from '@/core/EmailService.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { RoleService } from '@/core/RoleService.js'; +import { QueueService } from '@/core/QueueService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -59,11 +56,9 @@ export default class extends Endpoint { private abuseUserReportsRepository: AbuseUserReportsRepository, private idService: IdService, - private metaService: MetaService, - private emailService: EmailService, private getterService: GetterService, private roleService: RoleService, - private globalEventService: GlobalEventService, + private queueService: QueueService, ) { super(meta, paramDef, async (ps, me) => { // Lookup user @@ -90,26 +85,7 @@ export default class extends Endpoint { comment: ps.comment, }).then(x => this.abuseUserReportsRepository.findOneByOrFail(x.identifiers[0])); - // Publish event to moderators - setImmediate(async () => { - const moderators = await this.roleService.getModerators(); - - for (const moderator of moderators) { - this.globalEventService.publishAdminStream(moderator.id, 'newAbuseUserReport', { - id: report.id, - targetUserId: report.targetUserId, - reporterId: report.reporterId, - comment: report.comment, - }); - } - - const meta = await this.metaService.fetch(); - if (meta.email) { - this.emailService.sendEmail(meta.email, 'New abuse report', - sanitizeHtml(ps.comment), - sanitizeHtml(ps.comment)); - } - }); + this.queueService.createReportAbuseJob(report); }); } } diff --git a/packages/frontend/src/components/MkAbuseReportResolver.vue b/packages/frontend/src/components/MkAbuseReportResolver.vue new file mode 100644 index 0000000000..3cf498f715 --- /dev/null +++ b/packages/frontend/src/components/MkAbuseReportResolver.vue @@ -0,0 +1,145 @@ + + + + diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 70f0cc5cda..207661ba85 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -108,6 +108,10 @@ onMounted(() => { const myBg = computedStyle.getPropertyValue('--panel'); bgSame = parentBg === myBg; }); + +defineExpose({ + toggle, +}); diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index eddc5cf90c..48aa5f870a 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -423,6 +423,22 @@ export type Endpoints = { req: TODO; res: TODO; }; + 'admin/abuse-report-resolver/create': { + req: TODO; + res: TODO; + }; + 'admin/abuse-report-resolver/list': { + req: TODO; + res: TODO; + }; + 'admin/abuse-report-resolver/update': { + req: TODO; + res: TODO; + }; + 'admin/abuse-report-resolver/delete': { + req: TODO; + res: TODO; + }; 'admin/drive/clean-remote-files': { req: TODO; res: TODO; @@ -2723,7 +2739,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u // // src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts -// src/api.types.ts:596:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts +// src/api.types.ts:600:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index cc88c4b1a4..3a17e14d88 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -43,6 +43,10 @@ export type Endpoints = { 'admin/announcements/delete': { req: { id: Announcement['id'] }; res: null; }; 'admin/announcements/list': { req: TODO; res: TODO; }; 'admin/announcements/update': { req: TODO; res: TODO; }; + 'admin/abuse-report-resolver/create': { req: TODO; res: TODO; }; + 'admin/abuse-report-resolver/list': { req: TODO; res: TODO; }; + 'admin/abuse-report-resolver/update': { req: TODO; res: TODO; }; + 'admin/abuse-report-resolver/delete': { req: TODO; res: TODO; }; 'admin/drive/clean-remote-files': { req: TODO; res: TODO; }; 'admin/drive/cleanup': { req: TODO; res: TODO; }; 'admin/drive/files': { req: TODO; res: TODO; }; From 9b00b3556ec35fc34eb6f88d4da1a11f375b0ce5 Mon Sep 17 00:00:00 2001 From: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com> Date: Mon, 19 Jun 2023 17:01:10 +0900 Subject: [PATCH 02/27] =?UTF-8?q?fix:=20=E6=9D=A1=E4=BB=B6=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E6=99=82=E3=81=AB=E6=9C=89=E5=8A=B9=E6=9C=9F=E9=99=90?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=81=AE=E3=81=AB=E5=8B=9D=E6=89=8B=E3=81=AB=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E3=81=95=E3=82=8C=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/MkAbuseReportResolver.vue | 12 +++++++++--- packages/frontend/src/pages/admin/abuses.vue | 5 ++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/components/MkAbuseReportResolver.vue b/packages/frontend/src/components/MkAbuseReportResolver.vue index 3cf498f715..b970032d49 100644 --- a/packages/frontend/src/components/MkAbuseReportResolver.vue +++ b/packages/frontend/src/components/MkAbuseReportResolver.vue @@ -56,6 +56,7 @@ const props = defineProps<{ expiresAt: string; forward: boolean; expirationDate: string; + beforeExpiresAt?: string; } editable: boolean; data?: { @@ -77,9 +78,6 @@ const emit = defineEmits(['update:modelValue']); const value = computed({ get() { - if (props.modelValue && props.editable) { - emit('update:modelValue', props.data ?? props.modelValue); - } const data = props.data ?? props.modelValue ?? { name: '', targetUserPattern: '', @@ -93,6 +91,9 @@ const value = computed({ data[key] = ''; } } + if (props.modelValue && props.editable) { + emit('update:modelValue', data); + } return data as unknown as Exclude, undefined>; }, set(updateValue) { @@ -116,6 +117,11 @@ function renderExpirationDate(empty = false) { watch(() => value.value.expirationDate, () => renderExpirationDate(), { immediate: true }); watch(() => value.value.expiresAt, () => renderExpirationDate(true)); +watch(() => props.editable, () => { + if (props.editable) { + value.value.beforeExpiresAt = value.value.expiresAt; + } +});