wip
This commit is contained in:
		
							parent
							
								
									824c51a19f
								
							
						
					
					
						commit
						88cf83b9cf
					
				|  | @ -5170,6 +5170,22 @@ export interface Locale extends ILocale { | |||
|      * CAPTCHAのテストを目的とした機能です。<strong>本番環境で使用しないでください。</strong> | ||||
|      */ | ||||
|     "testCaptchaWarning": string; | ||||
|     /** | ||||
|      * 禁止するユーザー名に含まれる文字列 | ||||
|      */ | ||||
|     "prohibitedPartialScreenNames": string; | ||||
|     /** | ||||
|      * ユーザー名に含まれる文字列がこのリストに含まれる場合、そのユーザー名は使用できません。 | ||||
|      */ | ||||
|     "prohibitedPartialScreenNamesDescription": string; | ||||
|     /** | ||||
|      * 変更しようとした名前に禁止された文字列が含まれています | ||||
|      */ | ||||
|     "screenNameContainsProhibitedWords": string; | ||||
|     /** | ||||
|      * 名前に禁止されている文字列が含まれています。本名である等の理由でこの名前を使用したい場合は、サーバー管理者にお問い合わせください。 | ||||
|      */ | ||||
|     "screenNameContainsProhibitedWordsDescription": string; | ||||
|     "_abuseUserReport": { | ||||
|         /** | ||||
|          * 転送 | ||||
|  |  | |||
|  | @ -1288,6 +1288,10 @@ passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証 | |||
| messageToFollower: "フォロワーへのメッセージ" | ||||
| target: "対象" | ||||
| testCaptchaWarning: "CAPTCHAのテストを目的とした機能です。<strong>本番環境で使用しないでください。</strong>" | ||||
| prohibitedPartialScreenNames: "禁止するユーザー名に含まれる文字列" | ||||
| prohibitedPartialScreenNamesDescription: "ユーザー名に含まれる文字列がこのリストに含まれる場合、そのユーザー名は使用できません。" | ||||
| screenNameContainsProhibitedWords: "変更しようとした名前に禁止された文字列が含まれています" | ||||
| screenNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。本名である等の理由でこの名前を使用したい場合は、サーバー管理者にお問い合わせください。" | ||||
| 
 | ||||
| _abuseUserReport: | ||||
|   forward: "転送" | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| export class ProhibitedPartialScreenNames1728634286056 { | ||||
| 		async up(queryRunner) { | ||||
| 			await queryRunner.query(`ALTER TABLE "meta" ADD "prohibitedPartialScreenNames" character varying(1024) array NOT NULL DEFAULT '{}'`); | ||||
| 		} | ||||
| 
 | ||||
| 		async down(queryRunner) { | ||||
| 			await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "prohibitedPartialScreenNames"`); | ||||
| 		} | ||||
| } | ||||
|  | @ -81,6 +81,11 @@ export class MiMeta { | |||
| 	}) | ||||
| 	public prohibitedWords: string[]; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 1024, array: true, default: '{}', | ||||
| 	}) | ||||
| 	public prohibitedPartialScreenNames: string[]; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 1024, array: true, default: '{}', | ||||
| 	}) | ||||
|  |  | |||
|  | @ -177,6 +177,13 @@ export const meta = { | |||
| 					type: 'string', | ||||
| 				}, | ||||
| 			}, | ||||
| 			prohibitedPartialScreenNames: { | ||||
| 				type: 'array', | ||||
| 				optional: false, nullable: false, | ||||
| 				items: { | ||||
| 					type: 'string', | ||||
| 				}, | ||||
| 			}, | ||||
| 			bannedEmailDomains: { | ||||
| 				type: 'array', | ||||
| 				optional: true, nullable: false, | ||||
|  | @ -586,6 +593,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				mediaSilencedHosts: instance.mediaSilencedHosts, | ||||
| 				sensitiveWords: instance.sensitiveWords, | ||||
| 				prohibitedWords: instance.prohibitedWords, | ||||
| 				prohibitedPartialScreenNames: instance.prohibitedPartialScreenNames, | ||||
| 				preservedUsernames: instance.preservedUsernames, | ||||
| 				hcaptchaSecretKey: instance.hcaptchaSecretKey, | ||||
| 				mcaptchaSecretKey: instance.mcaptchaSecretKey, | ||||
|  |  | |||
|  | @ -46,6 +46,11 @@ export const paramDef = { | |||
| 				type: 'string', | ||||
| 			}, | ||||
| 		}, | ||||
| 		prohibitedPartialScreenNames: { | ||||
| 			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 }, | ||||
|  | @ -214,6 +219,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			if (Array.isArray(ps.prohibitedWords)) { | ||||
| 				set.prohibitedWords = ps.prohibitedWords.filter(Boolean); | ||||
| 			} | ||||
| 			if (Array.isArray(ps.prohibitedPartialScreenNames)) { | ||||
| 				set.prohibitedPartialScreenNames = ps.prohibitedPartialScreenNames.filter(Boolean); | ||||
| 			} | ||||
| 			if (Array.isArray(ps.silencedHosts)) { | ||||
| 				let lastValue = ''; | ||||
| 				set.silencedHosts = ps.silencedHosts.sort().filter((h) => { | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ import { JSDOM } from 'jsdom'; | |||
| import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; | ||||
| import { extractHashtags } from '@/misc/extract-hashtags.js'; | ||||
| import * as Acct from '@/misc/acct.js'; | ||||
| import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js'; | ||||
| import type { UsersRepository, DriveFilesRepository, MiMeta, UserProfilesRepository, PagesRepository } from '@/models/_.js'; | ||||
| import type { MiLocalUser, MiUser } from '@/models/User.js'; | ||||
| import { birthdaySchema, descriptionSchema, followedMessageSchema, locationSchema, nameSchema } from '@/models/User.js'; | ||||
| import type { MiUserProfile } from '@/models/UserProfile.js'; | ||||
|  | @ -22,6 +22,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; | |||
| import { GlobalEventService } from '@/core/GlobalEventService.js'; | ||||
| import { UserFollowingService } from '@/core/UserFollowingService.js'; | ||||
| import { AccountUpdateService } from '@/core/AccountUpdateService.js'; | ||||
| import { UtilityService } from '@/core/UtilityService.js'; | ||||
| import { HashtagService } from '@/core/HashtagService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { RolePolicies, RoleService } from '@/core/RoleService.js'; | ||||
|  | @ -114,6 +115,13 @@ export const meta = { | |||
| 			code: 'RESTRICTED_BY_ROLE', | ||||
| 			id: '8feff0ba-5ab5-585b-31f4-4df816663fad', | ||||
| 		}, | ||||
| 
 | ||||
| 		screenNameContainsProhibitedWords: { | ||||
| 			message: 'Screen name contains prohibited words.', | ||||
| 			code: 'SCREEN_NAME_CONTAINS_PROHIBITED_WORDS', | ||||
| 			id: '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191', | ||||
| 			httpStatusCode: 422, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	res: { | ||||
|  | @ -223,6 +231,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 		@Inject(DI.config) | ||||
| 		private config: Config, | ||||
| 
 | ||||
| 		@Inject(DI.meta) | ||||
| 		private instanceMeta: MiMeta, | ||||
| 
 | ||||
| 		@Inject(DI.usersRepository) | ||||
| 		private usersRepository: UsersRepository, | ||||
| 
 | ||||
|  | @ -247,6 +258,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 		private cacheService: CacheService, | ||||
| 		private httpRequestService: HttpRequestService, | ||||
| 		private avatarDecorationService: AvatarDecorationService, | ||||
| 		private utilityService: UtilityService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, _user, token) => { | ||||
| 			const user = await this.usersRepository.findOneByOrFail({ id: _user.id }) as MiLocalUser; | ||||
|  | @ -445,6 +457,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			let tags = [] as string[]; | ||||
| 
 | ||||
| 			const newName = updates.name === undefined ? user.name : updates.name; | ||||
| 			if (newName != null) { | ||||
| 				const hasProhibitedWords = this.checkScreennameProhibitedWordsContain(newName, this.instanceMeta.prohibitedPartialScreenNames); | ||||
| 				if (hasProhibitedWords) { | ||||
| 					throw new ApiError(meta.errors.screenNameContainsProhibitedWords); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; | ||||
| 			const newFields = profileUpdates.fields === undefined ? profile.fields : profileUpdates.fields; | ||||
| 
 | ||||
|  | @ -545,4 +564,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			// なにもしない
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private checkScreennameProhibitedWordsContain(name: string, prohibitedPartialScreenNames: string[]) { | ||||
| 		if (this.utilityService.isKeyWordIncluded(name, prohibitedPartialScreenNames)) { | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import { EventEmitter } from 'eventemitter3'; | |||
| import * as Misskey from 'misskey-js'; | ||||
| import type { ComponentProps as CP } from 'vue-component-type-helpers'; | ||||
| import type { Form, GetFormResultType } from '@/scripts/form.js'; | ||||
| import type { MenuItem } from '@/types/menu.js'; | ||||
| import { misskeyApi } from '@/scripts/misskey-api.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | @ -22,7 +23,6 @@ import MkPasswordDialog from '@/components/MkPasswordDialog.vue'; | |||
| import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue'; | ||||
| import MkPopupMenu from '@/components/MkPopupMenu.vue'; | ||||
| import MkContextMenu from '@/components/MkContextMenu.vue'; | ||||
| import type { MenuItem } from '@/types/menu.js'; | ||||
| import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; | ||||
| import { pleaseLogin } from '@/scripts/please-login.js'; | ||||
| import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; | ||||
|  | @ -77,6 +77,9 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey | |||
| 		} else if (err.message.startsWith('Unexpected token')) { | ||||
| 			title = i18n.ts.gotInvalidResponseError; | ||||
| 			text = i18n.ts.gotInvalidResponseErrorDescription; | ||||
| 		} else if (err.code === 'SCREEN_NAME_CONTAINS_PROHIBITED_WORDS') { | ||||
| 			title = i18n.ts.screenNameContainsProhibitedWords; | ||||
| 			text = i18n.ts.screenNameContainsProhibitedWordsDescription; | ||||
| 		} | ||||
| 		alert({ | ||||
| 			type: 'error', | ||||
|  |  | |||
|  | @ -57,6 +57,18 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 						</div> | ||||
| 					</MkFolder> | ||||
| 
 | ||||
| 					<MkFolder> | ||||
| 						<template #icon><i class="ti ti-message-x"></i></template> | ||||
| 						<template #label>{{ i18n.ts.prohibitedPartialScreenNames }}</template> | ||||
| 
 | ||||
| 						<div class="_gaps"> | ||||
| 							<MkTextarea v-model="prohibitedPartialScreenNames"> | ||||
| 								<template #caption>{{ i18n.ts.prohibitedPartialScreenNamesDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template> | ||||
| 							</MkTextarea> | ||||
| 							<MkButton primary @click="save_prohibitedPartialScreenNames">{{ i18n.ts.save }}</MkButton> | ||||
| 						</div> | ||||
| 					</MkFolder> | ||||
| 
 | ||||
| 					<MkFolder> | ||||
| 						<template #icon><i class="ti ti-eye-off"></i></template> | ||||
| 						<template #label>{{ i18n.ts.hiddenTags }}</template> | ||||
|  | @ -131,6 +143,7 @@ const enableRegistration = ref<boolean>(false); | |||
| const emailRequiredForSignup = ref<boolean>(false); | ||||
| const sensitiveWords = ref<string>(''); | ||||
| const prohibitedWords = ref<string>(''); | ||||
| const prohibitedPartialScreenNames = ref<string>(''); | ||||
| const hiddenTags = ref<string>(''); | ||||
| const preservedUsernames = ref<string>(''); | ||||
| const blockedHosts = ref<string>(''); | ||||
|  | @ -143,6 +156,7 @@ async function init() { | |||
| 	emailRequiredForSignup.value = meta.emailRequiredForSignup; | ||||
| 	sensitiveWords.value = meta.sensitiveWords.join('\n'); | ||||
| 	prohibitedWords.value = meta.prohibitedWords.join('\n'); | ||||
| 	prohibitedPartialScreenNames.value = meta.prohibitedPartialScreenNames.join('\n'); | ||||
| 	hiddenTags.value = meta.hiddenTags.join('\n'); | ||||
| 	preservedUsernames.value = meta.preservedUsernames.join('\n'); | ||||
| 	blockedHosts.value = meta.blockedHosts.join('\n'); | ||||
|  | @ -190,6 +204,14 @@ function save_prohibitedWords() { | |||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function save_prohibitedPartialScreenNames() { | ||||
| 	os.apiWithDialog('admin/update-meta', { | ||||
| 		prohibitedPartialScreenNames: prohibitedPartialScreenNames.value.split('\n'), | ||||
| 	}).then(() => { | ||||
| 		fetchInstance(true); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function save_hiddenTags() { | ||||
| 	os.apiWithDialog('admin/update-meta', { | ||||
| 		hiddenTags: hiddenTags.value.split('\n'), | ||||
|  |  | |||
|  | @ -5124,6 +5124,7 @@ export type operations = { | |||
|             blockedHosts: string[]; | ||||
|             sensitiveWords: string[]; | ||||
|             prohibitedWords: string[]; | ||||
|             prohibitedPartialScreenNames: string[]; | ||||
|             bannedEmailDomains?: string[]; | ||||
|             preservedUsernames: string[]; | ||||
|             hcaptchaSecretKey: string | null; | ||||
|  | @ -9461,6 +9462,7 @@ export type operations = { | |||
|           blockedHosts?: string[] | null; | ||||
|           sensitiveWords?: string[] | null; | ||||
|           prohibitedWords?: string[] | null; | ||||
|           prohibitedPartialScreenNames?: string[] | null; | ||||
|           themeColor?: string | null; | ||||
|           mascotImageUrl?: string | null; | ||||
|           bannerUrl?: string | null; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue