feat: 通報のカテゴリー化 (MisskeyIO#288)
This commit is contained in:
		| @@ -0,0 +1,18 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and other misskey contributors | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
|  | ||||
| export class AbuseUserReportCategory1703250468098 { | ||||
|     name = 'AbuseUserReportCategory1703250468098' | ||||
|  | ||||
|     async up(queryRunner) { | ||||
| 			await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "category" character varying(20) NOT NULL DEFAULT 'other'`); | ||||
| 			await queryRunner.query(`CREATE INDEX "IDX_5b9acc09094daeb8683e362778" ON "abuse_user_report" ("category") `); | ||||
| 	} | ||||
|  | ||||
| 	async down(queryRunner) { | ||||
| 			await queryRunner.query(`DROP INDEX "public"."IDX_5b9acc09094daeb8683e362778"`); | ||||
| 			await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "category"`); | ||||
| 	} | ||||
| } | ||||
| @@ -50,6 +50,7 @@ export class AbuseUserReportEntityService { | ||||
| 				detail: true, | ||||
| 			}) : null, | ||||
| 			forwarded: report.forwarded, | ||||
| 			category: report.category, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -67,6 +67,13 @@ export class MiAbuseUserReport { | ||||
| 	}) | ||||
| 	public comment: string; | ||||
|  | ||||
| 	@Index() | ||||
| 	@Column('varchar', { | ||||
| 		length: 20, nullable: false, | ||||
| 		default: 'other', | ||||
| 	}) | ||||
| 	public category: string; | ||||
|  | ||||
| 	//#region Denormalized fields | ||||
| 	@Index() | ||||
| 	@Column('varchar', { | ||||
|   | ||||
| @@ -12,6 +12,10 @@ export const packedAbuseUserReportSchema = { | ||||
| 			format: 'id', | ||||
| 			example: 'xxxxxxxxxx', | ||||
| 		}, | ||||
| 		category: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
| 		}, | ||||
| 		createdAt: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: false, | ||||
|   | ||||
| @@ -74,6 +74,10 @@ export const meta = { | ||||
| 					nullable: true, optional: true, | ||||
| 					ref: 'User', | ||||
| 				}, | ||||
| 				category: { | ||||
| 					type: 'string', | ||||
| 					nullable: false, optional: false, | ||||
| 				} | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| @@ -89,6 +93,7 @@ export const paramDef = { | ||||
| 		reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, | ||||
| 		targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, | ||||
| 		forwarded: { type: 'boolean', default: false }, | ||||
| 		category: { type: 'string', nullable: true, default: null }, | ||||
| 	}, | ||||
| 	required: [], | ||||
| } as const; | ||||
| @@ -120,6 +125,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break; | ||||
| 			} | ||||
|  | ||||
| 			if (ps.category) { | ||||
| 				query.andWhere('report.category = :category', { category: ps.category }); | ||||
| 			} | ||||
|  | ||||
| 			const reports = await query.limit(ps.limit).getMany(); | ||||
|  | ||||
| 			return await this.abuseUserReportEntityService.packMany(reports, me); | ||||
|   | ||||
| @@ -48,6 +48,7 @@ export const paramDef = { | ||||
| 	properties: { | ||||
| 		userId: { type: 'string', format: 'misskey:id' }, | ||||
| 		comment: { type: 'string', minLength: 1, maxLength: 2048 }, | ||||
| 		category: { type: 'string', minLength: 1, maxLength: 20, default: 'other' }, | ||||
| 	}, | ||||
| 	required: ['userId', 'comment'], | ||||
| } as const; | ||||
| @@ -85,6 +86,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||
| 				reporterId: me.id, | ||||
| 				reporterHost: null, | ||||
| 				comment: ps.comment, | ||||
| 				category: ps.category, | ||||
| 			}).then(x => this.abuseUserReportsRepository.findOneByOrFail(x.identifiers[0])); | ||||
|  | ||||
| 			this.queueService.createReportAbuseJob(report); | ||||
|   | ||||
| @@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 			{{ i18n.ts.moderator }}: | ||||
| 			<MkAcct :user="report.assignee"/> | ||||
| 		</div> | ||||
| 		<div v-if="report.category">カテゴリ: {{ i18n.t(`_abuseReportCategory.${report.category}`) }}</div> | ||||
| 		<div><MkTime :time="report.createdAt"/></div> | ||||
| 		<div class="action"> | ||||
| 			<MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved"> | ||||
|   | ||||
| @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| --> | ||||
|  | ||||
| <template> | ||||
| <MkWindow ref="uiWindow" :initialWidth="400" :initialHeight="500" :canResize="true" @closed="emit('closed')"> | ||||
| <MkWindow v-if="page === 1" ref="uiWindow" :initialWidth="400" :initialHeight="500" :canResize="true" @closed="emit('closed')"> | ||||
| 	<template #header> | ||||
| 		<i class="ti ti-exclamation-circle" style="margin-right: 0.5em;"></i> | ||||
| 		<I18n :src="i18n.ts.reportAbuseOf" tag="span"> | ||||
| @@ -15,6 +15,24 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 	</template> | ||||
| 	<MkSpacer :marginMin="20" :marginMax="28"> | ||||
| 		<div class="_gaps_m" :class="$style.root"> | ||||
| 			<div> | ||||
| 				<MkSelect v-model="category" :required="true"> | ||||
| 					<template #label>{{ i18n.ts.abuseReportCategory }}</template> | ||||
| 					<option value="" selected disabled>{{ i18n.ts.selectCategory }}</option> | ||||
| 					<option value="nsfw">{{ i18n.ts._abuseReportCategory.nsfw }}</option> | ||||
| 					<option value="spam">{{ i18n.ts._abuseReportCategory.spam }}</option> | ||||
| 					<option value="explicit">{{ i18n.ts._abuseReportCategory.explicit }}</option> | ||||
| 					<option value="phishing">{{ i18n.ts._abuseReportCategory.phishing }}</option> | ||||
| 					<option value="personalinfoleak">{{ i18n.ts._abuseReportCategory.personalinfoleak }}</option> | ||||
| 					<option value="selfharm">{{ i18n.ts._abuseReportCategory.selfharm }}</option> | ||||
| 					<option value="criticalbreach">{{ i18n.ts._abuseReportCategory.criticalbreach }}</option> | ||||
| 					<option value="otherbreach">{{ i18n.ts._abuseReportCategory.otherbreach }}</option> | ||||
| 					<option value="violationrights">{{ i18n.ts._abuseReportCategory.violationrights }}</option> | ||||
| 					<option value="violationrightsother">{{ i18n.ts._abuseReportCategory.violationrightsother }}</option> | ||||
| 					<option value="notlike">{{ i18n.ts._abuseReportCategory.notlike }}</option> | ||||
| 					<option value="other">{{ i18n.ts._abuseReportCategory.other }}</option> | ||||
| 				</MkSelect> | ||||
| 			</div> | ||||
| 			<div class=""> | ||||
| 				<MkTextarea v-model="comment"> | ||||
| 					<template #label>{{ i18n.ts.details }}</template> | ||||
| @@ -22,7 +40,24 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 				</MkTextarea> | ||||
| 			</div> | ||||
| 			<div class=""> | ||||
| 				<MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.ts.send }}</MkButton> | ||||
| 				<MkButton primary full :disabled="comment.length === 0 || category.length === 0" @click="send">{{ i18n.ts.send }}</MkButton> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</MkSpacer> | ||||
| </MkWindow> | ||||
|  | ||||
| <MkWindow v-if="page === 2" ref="uiWindow2" :initialWidth="450" :initialHeight="250" :canResize="true" @closed="emit('closed')"> | ||||
| 	<template #header> | ||||
| 		<i class="ti ti-circle-check" style="margin-right: 0.5em;"></i> | ||||
| 		<span><MkAcct :user="props.user"/> {{ i18n.ts.reportComplete }}</span> | ||||
| 	</template> | ||||
| 	<MkSpacer :marginMin="20" :marginMax="28"> | ||||
| 		<div class="_gaps_m" :class="$style.root"> | ||||
| 			<div> | ||||
| 				<p style="margin-bottom: 20px;">{{ i18n.ts.abuseReported }}</p> | ||||
| 				<MkButton :disabled="fullUserInfo?.isBlocking" @click="blockUser">{{ i18n.ts.blockThisUser }}</MkButton> | ||||
| 				<br> | ||||
| 				<MkButton :disabled="fullUserInfo?.isMuted" @click="muteUser">{{ i18n.ts.muteThisUser }}</MkButton> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</MkSpacer> | ||||
| @@ -30,11 +65,12 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, shallowRef } from 'vue'; | ||||
| import { ref, shallowRef, Ref } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkWindow from '@/components/MkWindow.vue'; | ||||
| import MkTextarea from '@/components/MkTextarea.vue'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import MkSelect from '@/components/MkSelect.vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | ||||
| @@ -48,19 +84,59 @@ const emit = defineEmits<{ | ||||
| }>(); | ||||
|  | ||||
| const uiWindow = shallowRef<InstanceType<typeof MkWindow>>(); | ||||
| const uiWindow2 = shallowRef<InstanceType<typeof MkWindow>>(); | ||||
| const comment = ref(props.initialComment ?? ''); | ||||
| const category = ref(''); | ||||
| const page = ref(1); | ||||
| const fullUserInfo: Ref<Misskey.entities.UserDetailed | null> = ref(null); | ||||
|  | ||||
| function blockUser() { | ||||
| 	os.confirm({ | ||||
| 		type: 'warning', | ||||
| 		title: i18n.ts.block, | ||||
| 		text: i18n.ts.blockConfirm, | ||||
| 	}).then((v) => { | ||||
| 		if (v.canceled) return; | ||||
| 		os.apiWithDialog('blocking/create', { userId: props.user.id }).then(refreshUserInfo); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function muteUser() { | ||||
| 	os.apiWithDialog('mute/create', { userId: props.user.id }).then(refreshUserInfo); | ||||
| } | ||||
|  | ||||
| function refreshUserInfo() { | ||||
| 	os.api('users/show', { userId: props.user.id }) | ||||
| 	.then((res) => { | ||||
| 		fullUserInfo.value = res; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| function send() { | ||||
| 	os.apiWithDialog('users/report-abuse', { | ||||
| 		userId: props.user.id, | ||||
| 		comment: comment.value, | ||||
| 	}, undefined).then(res => { | ||||
| 	if (category.value === 'violationrightsother') { | ||||
| 		os.alert({ | ||||
| 			type: 'success', | ||||
| 			text: i18n.ts.abuseReported, | ||||
| 			type: 'info', | ||||
| 			text: i18n.ts._abuseReportMsgs.rightsAbuseCantAccept | ||||
| 		}); | ||||
| 		uiWindow.value?.close(); | ||||
| 		emit('closed'); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (category.value === 'notlike') { | ||||
| 		uiWindow.value?.close(); | ||||
| 		page.value = 2; | ||||
| 	} | ||||
| 	os.apiWithDialog('users/report-abuse', { | ||||
| 		userId: props.user.id, | ||||
| 		comment: comment.value, | ||||
| 		category: category.value, | ||||
| 	}, undefined).then(res => { | ||||
| 		os.api('users/show', { userId: props.user.id }) | ||||
| 		.then((res) => { | ||||
| 			fullUserInfo.value = res; | ||||
| 			uiWindow.value?.close(); | ||||
| 			page.value = 2; | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
| </script> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 CyberRex
					CyberRex