Skip to content

Commit

Permalink
feat: sensitive word
Browse files Browse the repository at this point in the history
  • Loading branch information
syuilo committed Mar 13, 2023
1 parent b18df99 commit 7f16b50
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ You should also include the user name that made the change.
- ユーザーごとにRenoteをミュートできるように
- ノートごとに絵文字リアクションを受け取るか設定できるように
- ロールの並び順を設定可能に
- 指定した文字列を含む投稿の公開範囲をホームにできるように
- enhance(client): 設定から自分のロールを確認できるように
- enhance(client): DM作成時にメンションも含むように
- enhance(client): フォロー申請のボタンのデザインを改善
Expand Down
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,8 @@ likeOnly: "いいねのみ"
likeOnlyForRemote: "リモートからはいいねのみ"
rolesAssignedToMe: "自分に割り当てられたロール"
resetPasswordConfirm: "パスワードリセットしますか?"
sensitiveWords: "センシティブワード"
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"

_achievements:
earnedAt: "獲得日時"
Expand Down
11 changes: 11 additions & 0 deletions packages/backend/migration/1678694614599-sensitive-words.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class sensitiveWords1678694614599 {
name = 'sensitiveWords1678694614599'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveWords" character varying(1024) array NOT NULL DEFAULT '{}'`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sensitiveWords"`);
}
}
6 changes: 5 additions & 1 deletion packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';

const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);

Expand Down Expand Up @@ -192,6 +193,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService,
private roleService: RoleService,
private metaService: MetaService,
private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart,
Expand Down Expand Up @@ -230,7 +232,9 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true;

if (data.visibility === 'public' && data.channel == null) {
if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
if ((data.text != null) && (await this.metaService.fetch()).sensitiveWords.some(w => data.text!.includes(w))) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home';
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/entities/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export class Meta {
})
public blockedHosts: string[];

@Column('varchar', {
length: 1024, array: true, default: '{}',
})
public sensitiveWords: string[];

@Column('varchar', {
length: 1024,
nullable: true,
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ export const meta = {
optional: false, nullable: false,
},
},
sensitiveWords: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
hcaptchaSecretKey: {
type: 'string',
optional: true, nullable: true,
Expand Down Expand Up @@ -295,6 +303,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
pinnedUsers: instance.pinnedUsers,
hiddenTags: instance.hiddenTags,
blockedHosts: instance.blockedHosts,
sensitiveWords: instance.sensitiveWords,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
turnstileSecretKey: instance.turnstileSecretKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export const paramDef = {
blockedHosts: { type: 'array', nullable: true, items: {
type: 'string',
} },
sensitiveWords: { type: 'array', nullable: true, items: {
type: 'string',
} },
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
mascotImageUrl: { type: 'string', nullable: true },
bannerUrl: { type: 'string', nullable: true },
Expand Down Expand Up @@ -127,6 +130,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
set.blockedHosts = ps.blockedHosts.filter(Boolean).map(x => x.toLowerCase());
}

if (Array.isArray(ps.sensitiveWords)) {
set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
}

if (ps.themeColor !== undefined) {
set.themeColor = ps.themeColor;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/frontend/src/pages/admin/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ const menuDef = $computed(() => [{
text: i18n.ts.general,
to: '/admin/settings',
active: currentPage?.route.name === 'settings',
}, {
icon: 'ti ti-shield',
text: i18n.ts.moderation,
to: '/admin/moderation',
active: currentPage?.route.name === 'moderation',
}, {
icon: 'ti ti-mail',
text: i18n.ts.emailServer,
Expand Down
73 changes: 73 additions & 0 deletions packages/frontend/src/pages/admin/moderation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template>
<div>
<MkStickyContainer>
<template #header><XHeader :tabs="headerTabs"/></template>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<div class="_gaps_m">
<FormSection first>
<div class="_gaps_m">
<MkTextarea v-model="sensitiveWords">
<template #label>{{ i18n.ts.sensitiveWords }}</template>
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}</template>
</MkTextarea>
</div>
</FormSection>
</div>
</FormSuspense>
</MkSpacer>
<template #footer>
<div :class="$style.footer">
<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
</MkSpacer>
</div>
</template>
</MkStickyContainer>
</div>
</template>

<script lang="ts" setup>
import { } from 'vue';
import XHeader from './_header_.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import FormSection from '@/components/form/section.vue';
import FormSplit from '@/components/form/split.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkButton from '@/components/MkButton.vue';
let sensitiveWords: string = $ref('');
async function init() {
const meta = await os.api('admin/meta');
sensitiveWords = meta.pinnedUsers.join('\n');
}
function save() {
os.apiWithDialog('admin/update-meta', {
sensitiveWords: sensitiveWords.split('\n'),
}).then(() => {
fetchInstance();
});
}
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.moderation,
icon: 'ti ti-shield',
});
</script>

<style lang="scss" module>
.footer {
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
}
</style>
4 changes: 4 additions & 0 deletions packages/frontend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,10 @@ export const routes = [{
path: '/settings',
name: 'settings',
component: page(() => import('./pages/admin/settings.vue')),
}, {
path: '/moderation',
name: 'moderation',
component: page(() => import('./pages/admin/moderation.vue')),
}, {
path: '/email-settings',
name: 'email-settings',
Expand Down

0 comments on commit 7f16b50

Please sign in to comment.