fix: 特定文字列を含むノートを投稿できないようにする管理画面用設定項目を追加 (#13210)
* fix: 特定文字列を含むノートを投稿できないようにする管理画面用設定項目を追加 * Serviceでチェックするように変更
This commit is contained in:
parent
c0cb76f0ec
commit
614c9a0fc6
14 changed files with 191 additions and 29 deletions
|
@ -24,6 +24,8 @@
|
||||||
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
|
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
|
||||||
* すべてのリモートユーザーのリアクション一覧を見えないようにします
|
* すべてのリモートユーザーのリアクション一覧を見えないようにします
|
||||||
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
|
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
|
||||||
|
- Fix: 特定のキーワードを含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207
|
||||||
|
* デフォルトは空欄なので適用前と同等の動作になります
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: 新しいゲームを追加
|
- Feat: 新しいゲームを追加
|
||||||
|
|
12
locales/index.d.ts
vendored
12
locales/index.d.ts
vendored
|
@ -4180,6 +4180,18 @@ export interface Locale extends ILocale {
|
||||||
* スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
|
* スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
|
||||||
*/
|
*/
|
||||||
"sensitiveWordsDescription2": string;
|
"sensitiveWordsDescription2": string;
|
||||||
|
/**
|
||||||
|
* 禁止ワード
|
||||||
|
*/
|
||||||
|
"prohibitedWords": string;
|
||||||
|
/**
|
||||||
|
* 設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。
|
||||||
|
*/
|
||||||
|
"prohibitedWordsDescription": string;
|
||||||
|
/**
|
||||||
|
* スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
|
||||||
|
*/
|
||||||
|
"prohibitedWordsDescription2": string;
|
||||||
/**
|
/**
|
||||||
* 非表示ハッシュタグ
|
* 非表示ハッシュタグ
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1041,6 +1041,9 @@ resetPasswordConfirm: "パスワードリセットしますか?"
|
||||||
sensitiveWords: "センシティブワード"
|
sensitiveWords: "センシティブワード"
|
||||||
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
||||||
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
||||||
|
prohibitedWords: "禁止ワード"
|
||||||
|
prohibitedWordsDescription: "設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。"
|
||||||
|
prohibitedWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
||||||
hiddenTags: "非表示ハッシュタグ"
|
hiddenTags: "非表示ハッシュタグ"
|
||||||
hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
|
hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
|
||||||
notesSearchNotAvailable: "ノート検索は利用できません。"
|
notesSearchNotAvailable: "ノート検索は利用できません。"
|
||||||
|
|
16
packages/backend/migration/1707429690000-prohibited-words.js
Normal file
16
packages/backend/migration/1707429690000-prohibited-words.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class prohibitedWords1707429690000 {
|
||||||
|
name = 'prohibitedWords1707429690000'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "prohibitedWords" character varying(1024) array NOT NULL DEFAULT '{}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "prohibitedWords"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -163,7 +163,7 @@ export class HashtagService {
|
||||||
const instance = await this.metaService.fetch();
|
const instance = await this.metaService.fetch();
|
||||||
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
|
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
|
||||||
if (hiddenTags.includes(hashtag)) return;
|
if (hiddenTags.includes(hashtag)) return;
|
||||||
if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
|
if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
|
||||||
|
|
||||||
// YYYYMMDDHHmm (10分間隔)
|
// YYYYMMDDHHmm (10分間隔)
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
|
@ -151,6 +151,8 @@ type Option = {
|
||||||
export class NoteCreateService implements OnApplicationShutdown {
|
export class NoteCreateService implements OnApplicationShutdown {
|
||||||
#shutdownController = new AbortController();
|
#shutdownController = new AbortController();
|
||||||
|
|
||||||
|
public static ContainsProhibitedWordsError = class extends Error {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
@ -254,13 +256,19 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
if (data.visibility === 'public' && data.channel == null) {
|
if (data.visibility === 'public' && data.channel == null) {
|
||||||
const sensitiveWords = meta.sensitiveWords;
|
const sensitiveWords = meta.sensitiveWords;
|
||||||
if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!user.host) {
|
||||||
|
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
|
||||||
|
throw new NoteCreateService.ContainsProhibitedWordsError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
|
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
|
||||||
|
|
||||||
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
||||||
|
|
|
@ -43,13 +43,13 @@ export class UtilityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
|
public isKeyWordIncluded(text: string, keyWords: string[]): boolean {
|
||||||
if (sensitiveWords.length === 0) return false;
|
if (keyWords.length === 0) return false;
|
||||||
if (text === '') return false;
|
if (text === '') return false;
|
||||||
|
|
||||||
const regexpregexp = /^\/(.+)\/(.*)$/;
|
const regexpregexp = /^\/(.+)\/(.*)$/;
|
||||||
|
|
||||||
const matched = sensitiveWords.some(filter => {
|
const matched = keyWords.some(filter => {
|
||||||
// represents RegExp
|
// represents RegExp
|
||||||
const regexp = filter.match(regexpregexp);
|
const regexp = filter.match(regexpregexp);
|
||||||
// This should never happen due to input sanitisation.
|
// This should never happen due to input sanitisation.
|
||||||
|
|
|
@ -76,6 +76,11 @@ export class MiMeta {
|
||||||
})
|
})
|
||||||
public sensitiveWords: string[];
|
public sensitiveWords: string[];
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024, array: true, default: '{}',
|
||||||
|
})
|
||||||
|
public prohibitedWords: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 1024, array: true, default: '{}',
|
length: 1024, array: true, default: '{}',
|
||||||
})
|
})
|
||||||
|
|
|
@ -156,6 +156,13 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
prohibitedWords: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
bannedEmailDomains: {
|
bannedEmailDomains: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
optional: true, nullable: false,
|
optional: true, nullable: false,
|
||||||
|
@ -515,6 +522,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
blockedHosts: instance.blockedHosts,
|
blockedHosts: instance.blockedHosts,
|
||||||
silencedHosts: instance.silencedHosts,
|
silencedHosts: instance.silencedHosts,
|
||||||
sensitiveWords: instance.sensitiveWords,
|
sensitiveWords: instance.sensitiveWords,
|
||||||
|
prohibitedWords: instance.prohibitedWords,
|
||||||
preservedUsernames: instance.preservedUsernames,
|
preservedUsernames: instance.preservedUsernames,
|
||||||
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
||||||
mcaptchaSecretKey: instance.mcaptchaSecretKey,
|
mcaptchaSecretKey: instance.mcaptchaSecretKey,
|
||||||
|
|
|
@ -41,6 +41,11 @@ export const paramDef = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
prohibitedWords: {
|
||||||
|
type: 'array', nullable: true, items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
|
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
|
||||||
mascotImageUrl: { type: 'string', nullable: true },
|
mascotImageUrl: { type: 'string', nullable: true },
|
||||||
bannerUrl: { type: 'string', nullable: true },
|
bannerUrl: { type: 'string', nullable: true },
|
||||||
|
@ -177,6 +182,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (Array.isArray(ps.sensitiveWords)) {
|
if (Array.isArray(ps.sensitiveWords)) {
|
||||||
set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
|
set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(ps.prohibitedWords)) {
|
||||||
|
set.prohibitedWords = ps.prohibitedWords.filter(Boolean);
|
||||||
|
}
|
||||||
if (Array.isArray(ps.silencedHosts)) {
|
if (Array.isArray(ps.silencedHosts)) {
|
||||||
let lastValue = '';
|
let lastValue = '';
|
||||||
set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
|
set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
|
||||||
|
|
|
@ -17,6 +17,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -111,6 +113,12 @@ export const meta = {
|
||||||
code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
|
code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
|
||||||
id: '33510210-8452-094c-6227-4a6c05d99f00',
|
id: '33510210-8452-094c-6227-4a6c05d99f00',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
containsProhibitedWords: {
|
||||||
|
message: 'Cannot post because it contains prohibited words.',
|
||||||
|
code: 'CONTAINS_PROHIBITED_WORDS',
|
||||||
|
id: 'aa6e01d3-a85c-669d-758a-76aab43af334',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -340,6 +348,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
// 投稿を作成
|
// 投稿を作成
|
||||||
|
try {
|
||||||
const note = await this.noteCreateService.create(me, {
|
const note = await this.noteCreateService.create(me, {
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
files: files,
|
files: files,
|
||||||
|
@ -365,6 +374,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
return {
|
return {
|
||||||
createdNote: await this.noteEntityService.pack(note, me),
|
createdNote: await this.noteEntityService.pack(note, me),
|
||||||
};
|
};
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
|
||||||
|
if (e instanceof NoteCreateService.ContainsProhibitedWordsError) {
|
||||||
|
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,14 @@ describe('Note', () => {
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
|
let tom: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const connection = await initTestDb(true);
|
const connection = await initTestDb(true);
|
||||||
Notes = connection.getRepository(MiNote);
|
Notes = connection.getRepository(MiNote);
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
|
tom = await signup({ username: 'tom', host: 'example.com' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
test('投稿できる', async () => {
|
test('投稿できる', async () => {
|
||||||
|
@ -607,6 +609,77 @@ describe('Note', () => {
|
||||||
assert.strictEqual(note2.status, 200);
|
assert.strictEqual(note2.status, 200);
|
||||||
assert.strictEqual(note2.body.createdNote.visibility, 'home');
|
assert.strictEqual(note2.body.createdNote.visibility, 'home');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('禁止ワードを含む投稿はエラーになる (単語指定)', async () => {
|
||||||
|
const prohibited = await api('admin/update-meta', {
|
||||||
|
prohibitedWords: [
|
||||||
|
'test',
|
||||||
|
],
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(prohibited.status, 204);
|
||||||
|
|
||||||
|
await new Promise(x => setTimeout(x, 2));
|
||||||
|
|
||||||
|
const note1 = await api('/notes/create', {
|
||||||
|
text: 'hogetesthuge',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(note1.status, 400);
|
||||||
|
assert.strictEqual(note1.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('禁止ワードを含む投稿はエラーになる (正規表現)', async () => {
|
||||||
|
const prohibited = await api('admin/update-meta', {
|
||||||
|
prohibitedWords: [
|
||||||
|
'/Test/i',
|
||||||
|
],
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(prohibited.status, 204);
|
||||||
|
|
||||||
|
const note2 = await api('/notes/create', {
|
||||||
|
text: 'hogetesthuge',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(note2.status, 400);
|
||||||
|
assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('禁止ワードを含む投稿はエラーになる (スペースアンド)', async () => {
|
||||||
|
const prohibited = await api('admin/update-meta', {
|
||||||
|
prohibitedWords: [
|
||||||
|
'Test hoge',
|
||||||
|
],
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(prohibited.status, 204);
|
||||||
|
|
||||||
|
const note2 = await api('/notes/create', {
|
||||||
|
text: 'hogeTesthuge',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(note2.status, 400);
|
||||||
|
assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('禁止ワードを含んでいてもリモートノートはエラーにならない', async () => {
|
||||||
|
const prohibited = await api('admin/update-meta', {
|
||||||
|
prohibitedWords: [
|
||||||
|
'test',
|
||||||
|
],
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(prohibited.status, 204);
|
||||||
|
|
||||||
|
await new Promise(x => setTimeout(x, 2));
|
||||||
|
|
||||||
|
const note1 = await api('/notes/create', {
|
||||||
|
text: 'hogetesthuge',
|
||||||
|
}, tom);
|
||||||
|
|
||||||
|
assert.strictEqual(note1.status, 200);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('notes/delete', () => {
|
describe('notes/delete', () => {
|
||||||
|
|
|
@ -40,6 +40,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
|
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
|
|
||||||
|
<MkTextarea v-model="prohibitedWords">
|
||||||
|
<template #label>{{ i18n.ts.prohibitedWords }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
|
||||||
|
</MkTextarea>
|
||||||
|
|
||||||
<MkTextarea v-model="hiddenTags">
|
<MkTextarea v-model="hiddenTags">
|
||||||
<template #label>{{ i18n.ts.hiddenTags }}</template>
|
<template #label>{{ i18n.ts.hiddenTags }}</template>
|
||||||
<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
|
<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
|
||||||
|
@ -76,6 +81,7 @@ import FormLink from '@/components/form/link.vue';
|
||||||
const enableRegistration = ref<boolean>(false);
|
const enableRegistration = ref<boolean>(false);
|
||||||
const emailRequiredForSignup = ref<boolean>(false);
|
const emailRequiredForSignup = ref<boolean>(false);
|
||||||
const sensitiveWords = ref<string>('');
|
const sensitiveWords = ref<string>('');
|
||||||
|
const prohibitedWords = ref<string>('');
|
||||||
const hiddenTags = ref<string>('');
|
const hiddenTags = ref<string>('');
|
||||||
const preservedUsernames = ref<string>('');
|
const preservedUsernames = ref<string>('');
|
||||||
const tosUrl = ref<string | null>(null);
|
const tosUrl = ref<string | null>(null);
|
||||||
|
@ -86,6 +92,7 @@ async function init() {
|
||||||
enableRegistration.value = !meta.disableRegistration;
|
enableRegistration.value = !meta.disableRegistration;
|
||||||
emailRequiredForSignup.value = meta.emailRequiredForSignup;
|
emailRequiredForSignup.value = meta.emailRequiredForSignup;
|
||||||
sensitiveWords.value = meta.sensitiveWords.join('\n');
|
sensitiveWords.value = meta.sensitiveWords.join('\n');
|
||||||
|
prohibitedWords.value = meta.prohibitedWords.join('\n');
|
||||||
hiddenTags.value = meta.hiddenTags.join('\n');
|
hiddenTags.value = meta.hiddenTags.join('\n');
|
||||||
preservedUsernames.value = meta.preservedUsernames.join('\n');
|
preservedUsernames.value = meta.preservedUsernames.join('\n');
|
||||||
tosUrl.value = meta.tosUrl;
|
tosUrl.value = meta.tosUrl;
|
||||||
|
@ -99,6 +106,7 @@ function save() {
|
||||||
tosUrl: tosUrl.value,
|
tosUrl: tosUrl.value,
|
||||||
privacyPolicyUrl: privacyPolicyUrl.value,
|
privacyPolicyUrl: privacyPolicyUrl.value,
|
||||||
sensitiveWords: sensitiveWords.value.split('\n'),
|
sensitiveWords: sensitiveWords.value.split('\n'),
|
||||||
|
prohibitedWords: prohibitedWords.value.split('\n'),
|
||||||
hiddenTags: hiddenTags.value.split('\n'),
|
hiddenTags: hiddenTags.value.split('\n'),
|
||||||
preservedUsernames: preservedUsernames.value.split('\n'),
|
preservedUsernames: preservedUsernames.value.split('\n'),
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
|
@ -4659,6 +4659,7 @@ export type operations = {
|
||||||
hiddenTags: string[];
|
hiddenTags: string[];
|
||||||
blockedHosts: string[];
|
blockedHosts: string[];
|
||||||
sensitiveWords: string[];
|
sensitiveWords: string[];
|
||||||
|
prohibitedWords: string[];
|
||||||
bannedEmailDomains?: string[];
|
bannedEmailDomains?: string[];
|
||||||
preservedUsernames: string[];
|
preservedUsernames: string[];
|
||||||
hcaptchaSecretKey: string | null;
|
hcaptchaSecretKey: string | null;
|
||||||
|
@ -8413,6 +8414,7 @@ export type operations = {
|
||||||
hiddenTags?: string[] | null;
|
hiddenTags?: string[] | null;
|
||||||
blockedHosts?: string[] | null;
|
blockedHosts?: string[] | null;
|
||||||
sensitiveWords?: string[] | null;
|
sensitiveWords?: string[] | null;
|
||||||
|
prohibitedWords?: string[] | null;
|
||||||
themeColor?: string | null;
|
themeColor?: string | null;
|
||||||
mascotImageUrl?: string | null;
|
mascotImageUrl?: string | null;
|
||||||
bannerUrl?: string | null;
|
bannerUrl?: string | null;
|
||||||
|
|
Loading…
Reference in a new issue