Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 通報のカテゴリー化 #288

Merged
merged 7 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,28 @@ export interface Locale {
"decorate": string;
"addMfmFunction": string;
"enableQuickAddMfmFunction": string;
"abuseReportCategory": string;
"selectCategory": string;
"reportComplete": string;
"blockThisUser": string;
"muteThisUser": string;
"_abuseReportMsgs": {
"rightsAbuseCantAccept": string;
};
"_abuseReportCategory": {
"nsfw": string;
"spam": string;
"explicit": string;
"phishing": string;
"personalinfoleak": string;
"selfharm": string;
"criticalbreach": string;
"otherbreach": string;
"violationrights": string;
"violationrightsother": string;
"notlike": string;
"other": string;
};
"_announcement": {
"forExistingUsers": string;
"forExistingUsersDescription": string;
Expand Down
22 changes: 22 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,28 @@ seasonalScreenEffect: "季節に応じた画面の演出"
decorate: "デコる"
addMfmFunction: "装飾を追加"
enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
abuseReportCategory: "通報の種類"
selectCategory: "カテゴリを選択"
reportComplete: "通報完了"
u1-liquid marked this conversation as resolved.
Show resolved Hide resolved
blockThisUser: "このユーザーをブロックする"
muteThisUser: "このユーザーをミュートする"

_abuseReportMsgs:
rightsAbuseCantAccept: "申し訳ございません。権利侵害の通報は権利者ご本人からのみ受け付けております。"

_abuseReportCategory:
nsfw: "センシティブなコンテンツを含む投稿"
spam: "スパム"
explicit: "暴力もしくは攻撃的な投稿"
phishing: "フィッシングもしくは詐欺行為"
personalinfoleak: "本人もしくは他人の個人情報の漏えい"
selfharm: "自殺もしくは自害など生命に関わる問題"
criticalbreach: "重大な規約違反"
otherbreach: "その他の規約違反"
violationrights: "権利侵害もしくはなりすまし(本人)"
violationrightsother: "権利侵害(他人)"
notlike: "この人が気に入らない"
other: "その他"

_announcement:
forExistingUsers: "既存ユーザーのみ"
Expand Down
Original file line number Diff line number Diff line change
@@ -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"`);
u1-liquid marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class AbuseUserReportEntityService {
detail: true,
}) : null,
forwarded: report.forwarded,
category: report.category,
});
}

Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/models/AbuseUserReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/models/json-schema/abuse-user-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export const meta = {
nullable: true, optional: true,
ref: 'User',
},
category: {
type: 'string',
nullable: false, optional: false,
}
},
},
},
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/components/MkAbuseReport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
92 changes: 84 additions & 8 deletions packages/frontend/src/components/MkAbuseReportWindow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand All @@ -15,26 +15,62 @@ 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>
<template #caption>{{ i18n.ts.fillAbuseReportDescription }}</template>
</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>
</MkWindow>
</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';

Expand All @@ -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() {
if (category.value === 'violationrightsother') {
os.alert({
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.alert({
type: 'success',
text: i18n.ts.abuseReported,
os.api('users/show', { userId: props.user.id })
.then((res) => {
fullUserInfo.value = res;
uiWindow.value?.close();
page.value = 2;
});
uiWindow.value?.close();
emit('closed');
});
}
</script>
Expand Down
Loading