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: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
 | 
			
		||||
  * すべてのリモートユーザーのリアクション一覧を見えないようにします
 | 
			
		||||
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
 | 
			
		||||
- Fix: 特定のキーワードを含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207
 | 
			
		||||
  * デフォルトは空欄なので適用前と同等の動作になります
 | 
			
		||||
 | 
			
		||||
### Client
 | 
			
		||||
- Feat: 新しいゲームを追加
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -4180,6 +4180,18 @@ export interface Locale extends ILocale {
 | 
			
		|||
     * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
 | 
			
		||||
     */
 | 
			
		||||
    "sensitiveWordsDescription2": string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 禁止ワード
 | 
			
		||||
     */
 | 
			
		||||
    "prohibitedWords": string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。
 | 
			
		||||
     */
 | 
			
		||||
    "prohibitedWordsDescription": string;
 | 
			
		||||
    /**
 | 
			
		||||
     * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
 | 
			
		||||
     */
 | 
			
		||||
    "prohibitedWordsDescription2": string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 非表示ハッシュタグ
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1041,6 +1041,9 @@ resetPasswordConfirm: "パスワードリセットしますか?"
 | 
			
		|||
sensitiveWords: "センシティブワード"
 | 
			
		||||
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
 | 
			
		||||
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
 | 
			
		||||
prohibitedWords: "禁止ワード"
 | 
			
		||||
prohibitedWordsDescription: "設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。"
 | 
			
		||||
prohibitedWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
 | 
			
		||||
hiddenTags: "非表示ハッシュタグ"
 | 
			
		||||
hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
 | 
			
		||||
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 hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
 | 
			
		||||
		if (hiddenTags.includes(hashtag)) return;
 | 
			
		||||
		if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
 | 
			
		||||
		if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
 | 
			
		||||
 | 
			
		||||
		// YYYYMMDDHHmm (10分間隔)
 | 
			
		||||
		const now = new Date();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -151,6 +151,8 @@ type Option = {
 | 
			
		|||
export class NoteCreateService implements OnApplicationShutdown {
 | 
			
		||||
	#shutdownController = new AbortController();
 | 
			
		||||
 | 
			
		||||
	public static ContainsProhibitedWordsError = class extends Error {};
 | 
			
		||||
 | 
			
		||||
	constructor(
 | 
			
		||||
		@Inject(DI.config)
 | 
			
		||||
		private config: Config,
 | 
			
		||||
| 
						 | 
				
			
			@ -254,13 +256,19 @@ export class NoteCreateService implements OnApplicationShutdown {
 | 
			
		|||
 | 
			
		||||
		if (data.visibility === 'public' && data.channel == null) {
 | 
			
		||||
			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';
 | 
			
		||||
			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
 | 
			
		||||
				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);
 | 
			
		||||
 | 
			
		||||
		if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,13 +43,13 @@ export class UtilityService {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
 | 
			
		||||
		if (sensitiveWords.length === 0) return false;
 | 
			
		||||
	public isKeyWordIncluded(text: string, keyWords: string[]): boolean {
 | 
			
		||||
		if (keyWords.length === 0) return false;
 | 
			
		||||
		if (text === '') return false;
 | 
			
		||||
 | 
			
		||||
		const regexpregexp = /^\/(.+)\/(.*)$/;
 | 
			
		||||
 | 
			
		||||
		const matched = sensitiveWords.some(filter => {
 | 
			
		||||
		const matched = keyWords.some(filter => {
 | 
			
		||||
			// represents RegExp
 | 
			
		||||
			const regexp = filter.match(regexpregexp);
 | 
			
		||||
			// This should never happen due to input sanitisation.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -76,6 +76,11 @@ export class MiMeta {
 | 
			
		|||
	})
 | 
			
		||||
	public sensitiveWords: string[];
 | 
			
		||||
 | 
			
		||||
	@Column('varchar', {
 | 
			
		||||
		length: 1024, array: true, default: '{}',
 | 
			
		||||
	})
 | 
			
		||||
	public prohibitedWords: string[];
 | 
			
		||||
 | 
			
		||||
	@Column('varchar', {
 | 
			
		||||
		length: 1024, array: true, default: '{}',
 | 
			
		||||
	})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -156,6 +156,13 @@ export const meta = {
 | 
			
		|||
					type: 'string',
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			prohibitedWords: {
 | 
			
		||||
				type: 'array',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
				items: {
 | 
			
		||||
					type: 'string',
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			bannedEmailDomains: {
 | 
			
		||||
				type: 'array',
 | 
			
		||||
				optional: true, nullable: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -515,6 +522,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				blockedHosts: instance.blockedHosts,
 | 
			
		||||
				silencedHosts: instance.silencedHosts,
 | 
			
		||||
				sensitiveWords: instance.sensitiveWords,
 | 
			
		||||
				prohibitedWords: instance.prohibitedWords,
 | 
			
		||||
				preservedUsernames: instance.preservedUsernames,
 | 
			
		||||
				hcaptchaSecretKey: instance.hcaptchaSecretKey,
 | 
			
		||||
				mcaptchaSecretKey: instance.mcaptchaSecretKey,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,11 @@ export const paramDef = {
 | 
			
		|||
				type: 'string',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		prohibitedWords: {
 | 
			
		||||
			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 },
 | 
			
		||||
| 
						 | 
				
			
			@ -177,6 +182,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
			if (Array.isArray(ps.sensitiveWords)) {
 | 
			
		||||
				set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
 | 
			
		||||
			}
 | 
			
		||||
			if (Array.isArray(ps.prohibitedWords)) {
 | 
			
		||||
				set.prohibitedWords = ps.prohibitedWords.filter(Boolean);
 | 
			
		||||
			}
 | 
			
		||||
			if (Array.isArray(ps.silencedHosts)) {
 | 
			
		||||
				let lastValue = '';
 | 
			
		||||
				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 { DI } from '@/di-symbols.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';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
| 
						 | 
				
			
			@ -111,6 +113,12 @@ export const meta = {
 | 
			
		|||
			code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
 | 
			
		||||
			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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -340,31 +348,40 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
			}
 | 
			
		||||
 | 
			
		||||
			// 投稿を作成
 | 
			
		||||
			const note = await this.noteCreateService.create(me, {
 | 
			
		||||
				createdAt: new Date(),
 | 
			
		||||
				files: files,
 | 
			
		||||
				poll: ps.poll ? {
 | 
			
		||||
					choices: ps.poll.choices,
 | 
			
		||||
					multiple: ps.poll.multiple ?? false,
 | 
			
		||||
					expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
 | 
			
		||||
				} : undefined,
 | 
			
		||||
				text: ps.text ?? undefined,
 | 
			
		||||
				reply,
 | 
			
		||||
				renote,
 | 
			
		||||
				cw: ps.cw,
 | 
			
		||||
				localOnly: ps.localOnly,
 | 
			
		||||
				reactionAcceptance: ps.reactionAcceptance,
 | 
			
		||||
				visibility: ps.visibility,
 | 
			
		||||
				visibleUsers,
 | 
			
		||||
				channel,
 | 
			
		||||
				apMentions: ps.noExtractMentions ? [] : undefined,
 | 
			
		||||
				apHashtags: ps.noExtractHashtags ? [] : undefined,
 | 
			
		||||
				apEmojis: ps.noExtractEmojis ? [] : undefined,
 | 
			
		||||
			});
 | 
			
		||||
			try {
 | 
			
		||||
				const note = await this.noteCreateService.create(me, {
 | 
			
		||||
					createdAt: new Date(),
 | 
			
		||||
					files: files,
 | 
			
		||||
					poll: ps.poll ? {
 | 
			
		||||
						choices: ps.poll.choices,
 | 
			
		||||
						multiple: ps.poll.multiple ?? false,
 | 
			
		||||
						expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
 | 
			
		||||
					} : undefined,
 | 
			
		||||
					text: ps.text ?? undefined,
 | 
			
		||||
					reply,
 | 
			
		||||
					renote,
 | 
			
		||||
					cw: ps.cw,
 | 
			
		||||
					localOnly: ps.localOnly,
 | 
			
		||||
					reactionAcceptance: ps.reactionAcceptance,
 | 
			
		||||
					visibility: ps.visibility,
 | 
			
		||||
					visibleUsers,
 | 
			
		||||
					channel,
 | 
			
		||||
					apMentions: ps.noExtractMentions ? [] : undefined,
 | 
			
		||||
					apHashtags: ps.noExtractHashtags ? [] : undefined,
 | 
			
		||||
					apEmojis: ps.noExtractEmojis ? [] : undefined,
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
			return {
 | 
			
		||||
				createdNote: await this.noteEntityService.pack(note, me),
 | 
			
		||||
			};
 | 
			
		||||
				return {
 | 
			
		||||
					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 bob: misskey.entities.SignupResponse;
 | 
			
		||||
	let tom: misskey.entities.SignupResponse;
 | 
			
		||||
 | 
			
		||||
	beforeAll(async () => {
 | 
			
		||||
		const connection = await initTestDb(true);
 | 
			
		||||
		Notes = connection.getRepository(MiNote);
 | 
			
		||||
		alice = await signup({ username: 'alice' });
 | 
			
		||||
		bob = await signup({ username: 'bob' });
 | 
			
		||||
		tom = await signup({ username: 'tom', host: 'example.com' });
 | 
			
		||||
	}, 1000 * 60 * 2);
 | 
			
		||||
 | 
			
		||||
	test('投稿できる', async () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -607,6 +609,77 @@ describe('Note', () => {
 | 
			
		|||
			assert.strictEqual(note2.status, 200);
 | 
			
		||||
			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', () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
						<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
 | 
			
		||||
					</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">
 | 
			
		||||
						<template #label>{{ i18n.ts.hiddenTags }}</template>
 | 
			
		||||
						<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +81,7 @@ import FormLink from '@/components/form/link.vue';
 | 
			
		|||
const enableRegistration = ref<boolean>(false);
 | 
			
		||||
const emailRequiredForSignup = ref<boolean>(false);
 | 
			
		||||
const sensitiveWords = ref<string>('');
 | 
			
		||||
const prohibitedWords = ref<string>('');
 | 
			
		||||
const hiddenTags = ref<string>('');
 | 
			
		||||
const preservedUsernames = ref<string>('');
 | 
			
		||||
const tosUrl = ref<string | null>(null);
 | 
			
		||||
| 
						 | 
				
			
			@ -86,6 +92,7 @@ async function init() {
 | 
			
		|||
	enableRegistration.value = !meta.disableRegistration;
 | 
			
		||||
	emailRequiredForSignup.value = meta.emailRequiredForSignup;
 | 
			
		||||
	sensitiveWords.value = meta.sensitiveWords.join('\n');
 | 
			
		||||
	prohibitedWords.value = meta.prohibitedWords.join('\n');
 | 
			
		||||
	hiddenTags.value = meta.hiddenTags.join('\n');
 | 
			
		||||
	preservedUsernames.value = meta.preservedUsernames.join('\n');
 | 
			
		||||
	tosUrl.value = meta.tosUrl;
 | 
			
		||||
| 
						 | 
				
			
			@ -99,6 +106,7 @@ function save() {
 | 
			
		|||
		tosUrl: tosUrl.value,
 | 
			
		||||
		privacyPolicyUrl: privacyPolicyUrl.value,
 | 
			
		||||
		sensitiveWords: sensitiveWords.value.split('\n'),
 | 
			
		||||
		prohibitedWords: prohibitedWords.value.split('\n'),
 | 
			
		||||
		hiddenTags: hiddenTags.value.split('\n'),
 | 
			
		||||
		preservedUsernames: preservedUsernames.value.split('\n'),
 | 
			
		||||
	}).then(() => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4659,6 +4659,7 @@ export type operations = {
 | 
			
		|||
            hiddenTags: string[];
 | 
			
		||||
            blockedHosts: string[];
 | 
			
		||||
            sensitiveWords: string[];
 | 
			
		||||
            prohibitedWords: string[];
 | 
			
		||||
            bannedEmailDomains?: string[];
 | 
			
		||||
            preservedUsernames: string[];
 | 
			
		||||
            hcaptchaSecretKey: string | null;
 | 
			
		||||
| 
						 | 
				
			
			@ -8413,6 +8414,7 @@ export type operations = {
 | 
			
		|||
          hiddenTags?: string[] | null;
 | 
			
		||||
          blockedHosts?: string[] | null;
 | 
			
		||||
          sensitiveWords?: string[] | null;
 | 
			
		||||
          prohibitedWords?: string[] | null;
 | 
			
		||||
          themeColor?: string | null;
 | 
			
		||||
          mascotImageUrl?: string | null;
 | 
			
		||||
          bannerUrl?: string | null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue