feat: センシティブなカスタム絵文字のリアクションを受け入れない設定を追加

This commit is contained in:
syuilo 2023-05-19 09:43:38 +09:00
parent 527a13b77d
commit 3804c6e7ad
8 changed files with 27 additions and 14 deletions

View file

@ -17,6 +17,8 @@
### General ### General
- カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように - カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように
- カスタム絵文字ごとに連合するかどうか設定できるように - カスタム絵文字ごとに連合するかどうか設定できるように
- カスタム絵文字ごとにセンシティブフラグを設定できるように
- センシティブなカスタム絵文字のリアクションを受け入れない設定が可能に
- タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように - タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように
- 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります - 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります

View file

@ -990,7 +990,9 @@ postToTheChannel: "チャンネルに投稿"
cannotBeChangedLater: "後から変更できません。" cannotBeChangedLater: "後から変更できません。"
reactionAcceptance: "リアクションの受け入れ" reactionAcceptance: "リアクションの受け入れ"
likeOnly: "いいねのみ" likeOnly: "いいねのみ"
likeOnlyForRemote: "リモートからはいいねのみ" likeOnlyForRemote: "全て (リモートはいいねのみ)"
nonSensitiveOnly: "非センシティブのみ"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "非センシティブのみ (リモートはいいねのみ)"
rolesAssignedToMe: "自分に割り当てられたロール" rolesAssignedToMe: "自分に割り当てられたロール"
resetPasswordConfirm: "パスワードリセットしますか?" resetPasswordConfirm: "パスワードリセットしますか?"
sensitiveWords: "センシティブワード" sensitiveWords: "センシティブワード"

View file

@ -106,7 +106,7 @@ export class ReactionService {
let reaction = _reaction ?? FALLBACK; let reaction = _reaction ?? FALLBACK;
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) { if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
reaction = '❤️'; reaction = '❤️';
} else if (_reaction) { } else if (_reaction) {
const custom = reaction.match(isCustomEmojiRegexp); const custom = reaction.match(isCustomEmojiRegexp);
@ -124,6 +124,11 @@ export class ReactionService {
if (emoji) { if (emoji) {
if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) { if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) {
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`; reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
// センシティブ
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
reaction = FALLBACK;
}
} else { } else {
// リアクションとして使う権限がない // リアクションとして使う権限がない
reaction = FALLBACK; reaction = FALLBACK;

View file

@ -90,7 +90,7 @@ export class Note {
@Column('varchar', { @Column('varchar', {
length: 64, nullable: true, length: 64, nullable: true,
}) })
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null; public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
@Column('smallint', { @Column('smallint', {
default: 0, default: 0,

View file

@ -99,7 +99,7 @@ export const paramDef = {
} }, } },
cw: { type: 'string', nullable: true, maxLength: 100 }, cw: { type: 'string', nullable: true, maxLength: 100 },
localOnly: { type: 'boolean', default: false }, localOnly: { type: 'boolean', default: false },
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote'], default: null }, reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
noExtractMentions: { type: 'boolean', default: false }, noExtractMentions: { type: 'boolean', default: false },
noExtractHashtags: { type: 'boolean', default: false }, noExtractHashtags: { type: 'boolean', default: false },
noExtractEmojis: { type: 'boolean', default: false }, noExtractEmojis: { type: 'boolean', default: false },

View file

@ -31,7 +31,7 @@
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span> <span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
<span v-else><i class="ti ti-rocket-off"></i></span> <span v-else><i class="ti ti-rocket-off"></i></span>
</button> </button>
<button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" :class="['_button', $style.headerRightItem, $style.reactionAcceptance, { [$style.danger]: reactionAcceptance }]" @click="toggleReactionAcceptance"> <button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" :class="['_button', $style.headerRightItem, $style.reactionAcceptance, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance">
<span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span> <span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span>
<span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span> <span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span>
<span v-else><i class="ti ti-icons"></i></span> <span v-else><i class="ti ti-icons"></i></span>
@ -484,8 +484,10 @@ async function toggleReactionAcceptance() {
title: i18n.ts.reactionAcceptance, title: i18n.ts.reactionAcceptance,
items: [ items: [
{ value: null, text: i18n.ts.all }, { value: null, text: i18n.ts.all },
{ value: 'likeOnly' as const, text: i18n.ts.likeOnly },
{ value: 'likeOnlyForRemote' as const, text: i18n.ts.likeOnlyForRemote }, { value: 'likeOnlyForRemote' as const, text: i18n.ts.likeOnlyForRemote },
{ value: 'nonSensitiveOnly' as const, text: i18n.ts.nonSensitiveOnly },
{ value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, text: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote },
{ value: 'likeOnly' as const, text: i18n.ts.likeOnly },
], ],
default: reactionAcceptance, default: reactionAcceptance,
}); });

View file

@ -8,21 +8,21 @@
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton> <MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
</div> </div>
<MkInput v-model="profile.name" :max="30" manual-save> <MkInput v-model="profile.name" :max="30" manualSave>
<template #label>{{ i18n.ts._profile.name }}</template> <template #label>{{ i18n.ts._profile.name }}</template>
</MkInput> </MkInput>
<MkTextarea v-model="profile.description" :max="500" tall manual-save> <MkTextarea v-model="profile.description" :max="500" tall manualSave>
<template #label>{{ i18n.ts._profile.description }}</template> <template #label>{{ i18n.ts._profile.description }}</template>
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template> <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
</MkTextarea> </MkTextarea>
<MkInput v-model="profile.location" manual-save> <MkInput v-model="profile.location" manualSave>
<template #label>{{ i18n.ts.location }}</template> <template #label>{{ i18n.ts.location }}</template>
<template #prefix><i class="ti ti-map-pin"></i></template> <template #prefix><i class="ti ti-map-pin"></i></template>
</MkInput> </MkInput>
<MkInput v-model="profile.birthday" type="date" manual-save> <MkInput v-model="profile.birthday" type="date" manualSave>
<template #label>{{ i18n.ts.birthday }}</template> <template #label>{{ i18n.ts.birthday }}</template>
<template #prefix><i class="ti ti-cake"></i></template> <template #prefix><i class="ti ti-cake"></i></template>
</MkInput> </MkInput>
@ -48,7 +48,7 @@
<Sortable <Sortable
v-model="fields" v-model="fields"
class="_gaps_s" class="_gaps_s"
item-key="id" itemKey="id"
:animation="150" :animation="150"
:handle="'.' + $style.dragItemHandle" :handle="'.' + $style.dragItemHandle"
@start="e => e.item.classList.add('active')" @start="e => e.item.classList.add('active')"
@ -59,7 +59,7 @@
<button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button> <button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
<button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button> <button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button>
<div :class="$style.dragItemForm"> <div :class="$style.dragItemForm">
<FormSplit :min-width="200"> <FormSplit :minWidth="200">
<MkInput v-model="element.name" small> <MkInput v-model="element.name" small>
<template #label>{{ i18n.ts._profile.metadataLabel }}</template> <template #label>{{ i18n.ts._profile.metadataLabel }}</template>
</MkInput> </MkInput>
@ -88,8 +88,10 @@
<MkSelect v-model="reactionAcceptance"> <MkSelect v-model="reactionAcceptance">
<template #label>{{ i18n.ts.reactionAcceptance }}</template> <template #label>{{ i18n.ts.reactionAcceptance }}</template>
<option :value="null">{{ i18n.ts.all }}</option> <option :value="null">{{ i18n.ts.all }}</option>
<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option> <option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
<option value="nonSensitiveOnly">{{ i18n.ts.nonSensitiveOnly }}</option>
<option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
</MkSelect> </MkSelect>
</div> </div>
</template> </template>

View file

@ -92,7 +92,7 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
reactionAcceptance: { reactionAcceptance: {
where: 'account', where: 'account',
default: null as 'likeOnly' | 'likeOnlyForRemote' | null, default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
}, },
mutedWords: { mutedWords: {
where: 'account', where: 'account',