diff options
| author | 1Step621 <86859447+1STEP621@users.noreply.github.com> | 2024-02-06 16:45:21 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-06 16:45:21 +0900 |
| commit | 74245df3829622a3cc0c880ea710b5c1c4f5c584 (patch) | |
| tree | 3f6f4f1c598f4bd75be6bbfcd1d5deee8daa5adc | |
| parent | enhance(frontend): KeepAliveのページキャッシュを削除できるよ... (diff) | |
| download | misskey-74245df3829622a3cc0c880ea710b5c1c4f5c584.tar.gz misskey-74245df3829622a3cc0c880ea710b5c1c4f5c584.tar.bz2 misskey-74245df3829622a3cc0c880ea710b5c1c4f5c584.zip | |
Enhance(frontend): フロント側でもリアクション権限のチェックをするように (#13134)
* フロント側でもリアクション権限のチェックをするように
* update CHANGELOG.md
* lint fixes
* remove unrelated diffs
* deny -> reject
denyは「(信用しないことを理由に)拒否する」という意味らしい
* allow -> accept
* EmojiSimpleにlocalOnlyを含めるように
* リアクション権限のない絵文字は打てないように(ダイアログを出すのではなく)
* regenerate type definitions
* lint fix
* remove unused locales
* remove unnecessary async
17 files changed, 53 insertions, 16 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fc99cf5b8..17c06a90e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,10 @@ - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように - Enhance: リモートのユーザーはメニューから直接リモートで表示できるように - Enhance: コードのシンタックスハイライトにテーマを適用できるように +- Enhance: リアクション権限がない場合、ハートにフォールバックするのではなく、権限がないことをダイアログで表示するように + - リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合 + - センシティブなリアクションを認めていないユーザーにセンシティブなカスタム絵文字をリアクションしようとした場合 + - ロールが必要な絵文字をリアクションしようとした場合 - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 5b97cfad5e..655c4c5ada 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -31,6 +31,7 @@ export class EmojiEntityService { category: emoji.category, // || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ) url: emoji.publicUrl || emoji.originalUrl, + localOnly: emoji.localOnly ? true : undefined, isSensitive: emoji.isSensitive ? true : undefined, roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined, }; diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts index 99a58f8773..954eb98d57 100644 --- a/packages/backend/src/models/json-schema/emoji.ts +++ b/packages/backend/src/models/json-schema/emoji.ts @@ -27,6 +27,10 @@ export const packedEmojiSimpleSchema = { type: 'string', optional: false, nullable: false, }, + localOnly: { + type: 'boolean', + optional: true, nullable: false, + }, isSensitive: { type: 'boolean', optional: true, nullable: false, diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 58160cdf5b..f5ab7a2e29 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -118,6 +118,7 @@ import { i18n } from '@/i18n.js'; import { defaultStore } from '@/store.js'; import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js'; import { $i } from '@/account.js'; +import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js'; const props = withDefaults(defineProps<{ showPinned?: boolean; @@ -126,6 +127,7 @@ const props = withDefaults(defineProps<{ asDrawer?: boolean; asWindow?: boolean; asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう + targetNote?: Misskey.entities.Note; }>(), { showPinned: true, }); @@ -340,7 +342,7 @@ watch(q, () => { }); function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean { - return ((emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction?.includes(r.id)))) ?? false; + return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji); } function focus() { diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index 6660dcf1ed..444e8a4cec 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only :showPinned="showPinned" :pinnedEmojis="pinnedEmojis" :asReactionPicker="asReactionPicker" + :targetNote="targetNote" :asDrawer="type === 'drawer'" :max-height="maxHeight" @chosen="chosen" @@ -32,6 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> +import * as Misskey from 'misskey-js'; import { shallowRef } from 'vue'; import MkModal from '@/components/MkModal.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; @@ -43,6 +45,7 @@ const props = withDefaults(defineProps<{ showPinned?: boolean; pinnedEmojis?: string[], asReactionPicker?: boolean; + targetNote?: Misskey.entities.Note; choseAndClose?: boolean; }>(), { manualShowing: null, diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue index 1a2c55e785..2a6828f242 100644 --- a/packages/frontend/src/components/MkEmojiPickerWindow.vue +++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue @@ -13,12 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only :front="true" @closed="emit('closed')" > - <MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" asWindow :class="$style.picker" @chosen="chosen"/> + <MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" :targetNote="targetNote" asWindow :class="$style.picker" @chosen="chosen"/> </MkWindow> </template> <script lang="ts" setup> import { } from 'vue'; +import * as Misskey from 'misskey-js'; import MkWindow from '@/components/MkWindow.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; @@ -26,6 +27,7 @@ withDefaults(defineProps<{ src?: HTMLElement; showPinned?: boolean; asReactionPicker?: boolean; + targetNote?: Misskey.entities.Note }>(), { showPinned: true, }); diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 735ec86564..27e8bc45b7 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -385,7 +385,7 @@ function react(viaKeyboard = false): void { } } else { blur(); - reactionPicker.show(reactButton.value ?? null, reaction => { + reactionPicker.show(reactButton.value ?? null, note.value, reaction => { sound.playMisskeySfx('reaction'); if (props.mock) { diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index dd956b21ad..a53dd90d27 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -385,7 +385,7 @@ function react(viaKeyboard = false): void { } } else { blur(); - reactionPicker.show(reactButton.value ?? null, reaction => { + reactionPicker.show(reactButton.value ?? null, note.value, reaction => { sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index ffbf62a45c..67b448ccb7 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -32,6 +32,8 @@ import { claimAchievement } from '@/scripts/achievements.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as sound from '@/scripts/sound.js'; +import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js'; +import { customEmojis } from '@/custom-emojis.js'; const props = defineProps<{ reaction: string; @@ -48,13 +50,19 @@ const emit = defineEmits<{ const buttonEl = shallowRef<HTMLElement>(); -const canToggle = computed(() => !props.reaction.match(/@\w/) && $i); +const isCustomEmoji = computed(() => props.reaction.includes(':')); +const emoji = computed(() => isCustomEmoji.value ? customEmojis.value.find(emoji => emoji.name === props.reaction.replace(/:/g, '').replace(/@\./, '')) : null); + +const canToggle = computed(() => { + return !props.reaction.match(/@\w/) && $i + && (emoji.value && checkReactionPermissions($i, props.note, emoji.value)) + || !isCustomEmoji.value; +}); +const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':')); async function toggleReaction() { if (!canToggle.value) return; - // TODO: その絵文字を使う権限があるかどうか確認 - const oldReaction = props.note.myReaction; if (oldReaction) { const confirm = await os.confirm({ @@ -101,8 +109,8 @@ async function toggleReaction() { } async function menu(ev) { - if (!canToggle.value) return; - if (!props.reaction.includes(':')) return; + if (!canGetInfo.value) return; + os.popupMenu([{ text: i18n.ts.info, icon: 'ti ti-info-circle', diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue index 61f3332122..1215af35cf 100644 --- a/packages/frontend/src/pages/settings/emoji-picker.vue +++ b/packages/frontend/src/pages/settings/emoji-picker.vue @@ -157,7 +157,7 @@ const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev); const setDefaultEmoji = () => setDefault(pinnedEmojis); function previewReaction(ev: MouseEvent) { - reactionPicker.show(getHTMLElement(ev)); + reactionPicker.show(getHTMLElement(ev), null); } function previewEmoji(ev: MouseEvent) { diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts new file mode 100644 index 0000000000..c9d2a5bfc6 --- /dev/null +++ b/packages/frontend/src/scripts/check-reaction-permissions.ts @@ -0,0 +1,8 @@ +import * as Misskey from 'misskey-js'; + +export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple): boolean { + const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? []; + return !(emoji.localOnly && note.user.host !== me.host) + && !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote')) + && (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id))); +} diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/scripts/reaction-picker.ts index a13351b536..193ac838a2 100644 --- a/packages/frontend/src/scripts/reaction-picker.ts +++ b/packages/frontend/src/scripts/reaction-picker.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import * as Misskey from 'misskey-js'; import { defineAsyncComponent, Ref, ref } from 'vue'; import { popup } from '@/os.js'; import { defaultStore } from '@/store.js'; @@ -10,6 +11,7 @@ import { defaultStore } from '@/store.js'; class ReactionPicker { private src: Ref<HTMLElement | null> = ref(null); private manualShowing = ref(false); + private targetNote: Ref<Misskey.entities.Note | null> = ref(null); private onChosen?: (reaction: string) => void; private onClosed?: () => void; @@ -23,6 +25,7 @@ class ReactionPicker { src: this.src, pinnedEmojis: reactionsRef, asReactionPicker: true, + targetNote: this.targetNote, manualShowing: this.manualShowing, }, { done: reaction => { @@ -38,8 +41,9 @@ class ReactionPicker { }); } - public show(src: HTMLElement | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { + public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { this.src.value = src; + this.targetNote.value = targetNote; this.manualShowing.value = true; this.onChosen = onChosen; this.onClosed = onClosed; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 7c727d2878..cb761493a2 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1,6 +1,6 @@ /* * version: 2024.2.0-beta.8 - * generatedAt: 2024-02-04T11:51:13.598Z + * generatedAt: 2024-02-04T16:51:09.469Z */ import type { SwitchCaseResponseType } from '../api.js'; diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index cf9f96b526..10ec645a53 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -1,6 +1,6 @@ /* * version: 2024.2.0-beta.8 - * generatedAt: 2024-02-04T11:51:13.595Z + * generatedAt: 2024-02-04T16:51:09.467Z */ import type { diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 2930f2cb7f..04b52e9fb6 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -1,6 +1,6 @@ /* * version: 2024.2.0-beta.8 - * generatedAt: 2024-02-04T11:51:13.593Z + * generatedAt: 2024-02-04T16:51:09.466Z */ import { operations } from './types.js'; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index f01beaa706..ee14b22f74 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -1,6 +1,6 @@ /* * version: 2024.2.0-beta.8 - * generatedAt: 2024-02-04T11:51:13.592Z + * generatedAt: 2024-02-04T16:51:09.465Z */ import { components } from './types.js'; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 77a493fe2e..cce2dadcdb 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -3,7 +3,7 @@ /* * version: 2024.2.0-beta.8 - * generatedAt: 2024-02-04T11:51:13.473Z + * generatedAt: 2024-02-04T16:51:09.378Z */ /** @@ -4423,6 +4423,7 @@ export type components = { name: string; category: string | null; url: string; + localOnly?: boolean; isSensitive?: boolean; roleIdsThatCanBeUsedThisEmojiAsReaction?: string[]; }; |