mirror of
https://github.com/MisskeyIO/misskey
synced 2024-11-27 14:28:49 +09:00
feat: 指定のロールはリアクションに使えないカスタム絵文字 (MisskeyIO#136)
This commit is contained in:
parent
42a90f56e1
commit
3b73874196
@ -1065,6 +1065,7 @@ update: "Update"
|
||||
rolesThatCanBeUsedThisEmojiAsReaction: "Roles that can use this emoji as reaction"
|
||||
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "If no roles are specified, anyone can use this emoji as reaction."
|
||||
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "These roles must be public."
|
||||
rolesThatCanNotBeUsedThisEmojiAsReaction: "Roles that can not use this emoji as reaction"
|
||||
cancelReactionConfirm: "Really delete your reaction?"
|
||||
changeReactionConfirm: "Really change your reaction?"
|
||||
later: "Later"
|
||||
|
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
@ -1068,6 +1068,7 @@ export interface Locale {
|
||||
"rolesThatCanBeUsedThisEmojiAsReaction": string;
|
||||
"rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription": string;
|
||||
"rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn": string;
|
||||
"rolesThatCanNotBeUsedThisEmojiAsReaction": string;
|
||||
"cancelReactionConfirm": string;
|
||||
"changeReactionConfirm": string;
|
||||
"later": string;
|
||||
|
@ -1065,6 +1065,7 @@ update: "更新"
|
||||
rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール"
|
||||
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。"
|
||||
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロールである必要があります。"
|
||||
rolesThatCanNotBeUsedThisEmojiAsReaction: "リアクションとして使えないロール"
|
||||
cancelReactionConfirm: "リアクションを取り消しますか?"
|
||||
changeReactionConfirm: "リアクションを変更しますか?"
|
||||
later: "あとで"
|
||||
|
@ -0,0 +1,11 @@
|
||||
export class CustomemojiRestrictedRoles1691317808362 {
|
||||
name = 'CustomemojiRestrictedRoles1691317808362'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" ADD "roleIdsThatCanNotBeUsedThisEmojiAsReaction" character varying(128) array NOT NULL DEFAULT '{}'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "roleIdsThatCanNotBeUsedThisEmojiAsReaction"`);
|
||||
}
|
||||
}
|
@ -68,6 +68,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
isSensitive: boolean;
|
||||
localOnly: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: Role['id'][];
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: Role['id'][];
|
||||
}): Promise<Emoji> {
|
||||
const emoji = await this.emojisRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
@ -83,6 +84,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: data.roleIdsThatCanNotBeUsedThisEmojiAsReaction,
|
||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
if (data.host == null) {
|
||||
@ -106,6 +108,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
isSensitive?: boolean;
|
||||
localOnly?: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: Role['id'][];
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction?: Role['id'][];
|
||||
}): Promise<void> {
|
||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
|
||||
@ -123,6 +126,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
||||
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: data.roleIdsThatCanNotBeUsedThisEmojiAsReaction ?? undefined,
|
||||
});
|
||||
|
||||
this.localEmojisCache.refresh();
|
||||
|
@ -122,7 +122,10 @@ export class ReactionService {
|
||||
});
|
||||
|
||||
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))) &&
|
||||
(emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction.length === 0 || !(await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction.includes(r.id)))
|
||||
) {
|
||||
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
|
||||
|
||||
// センシティブ
|
||||
|
@ -28,6 +28,7 @@ export class EmojiEntityService {
|
||||
url: emoji.publicUrl || emoji.originalUrl,
|
||||
isSensitive: emoji.isSensitive ? true : undefined,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -56,6 +57,7 @@ export class EmojiEntityService {
|
||||
isSensitive: emoji.isSensitive,
|
||||
localOnly: emoji.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -76,4 +76,9 @@ export class Emoji {
|
||||
array: true, length: 128, default: '{}',
|
||||
})
|
||||
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
array: true, length: 128, default: '{}',
|
||||
})
|
||||
public roleIdsThatCanNotBeUsedThisEmojiAsReaction: string[];
|
||||
}
|
||||
|
@ -35,6 +35,15 @@ export const packedEmojiSimpleSchema = {
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@ -86,7 +95,16 @@ export const packedEmojiDetailedSchema = {
|
||||
},
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
@ -109,6 +109,7 @@ export class ImportCustomEmojisProcessorService {
|
||||
isSensitive: emojiInfo.isSensitive,
|
||||
localOnly: emojiInfo.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: [],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,11 @@ export const paramDef = {
|
||||
localOnly: { type: 'boolean' },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
} },
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
} },
|
||||
},
|
||||
required: ['name', 'fileId'],
|
||||
@ -73,6 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: ps.roleIdsThatCanNotBeUsedThisEmojiAsReaction ?? [],
|
||||
});
|
||||
|
||||
this.moderationLogService.insertModerationLog(me, 'addEmoji', {
|
||||
|
@ -49,6 +49,11 @@ export const paramDef = {
|
||||
localOnly: { type: 'boolean' },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
} },
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
} },
|
||||
},
|
||||
required: ['id', 'name', 'aliases'],
|
||||
@ -80,6 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
isSensitive: ps.isSensitive,
|
||||
localOnly: ps.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: ps.roleIdsThatCanNotBeUsedThisEmojiAsReaction,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -284,7 +284,8 @@ watch(q, () => {
|
||||
});
|
||||
|
||||
function filterAvailable(emoji: Misskey.entities.CustomEmoji): boolean {
|
||||
return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id)));
|
||||
return ((emoji.roleIdsThatCanBeUsedThisEmojiAsReaction === undefined || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id)))) &&
|
||||
((emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction === undefined || emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction.length === 0) || ($i && !$i.roles.some(r => emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction.includes(r.id))));
|
||||
}
|
||||
|
||||
function focus() {
|
||||
|
@ -44,11 +44,28 @@
|
||||
<template #suffix>{{ rolesThatCanBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisEmojiAsReaction.length }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
<MkButton rounded @click="addRole(true)"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
|
||||
<div v-for="role in rolesThatCanBeUsedThisEmojiAsReaction" :key="role.id" :class="$style.roleItem">
|
||||
<MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
|
||||
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button>
|
||||
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(true, role, $event)"><i class="ti ti-x"></i></button>
|
||||
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||
</div>
|
||||
|
||||
<MkInfo>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription }}</MkInfo>
|
||||
<MkInfo warn>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn }}</MkInfo>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.rolesThatCanNotBeUsedThisEmojiAsReaction }}</template>
|
||||
<template #suffix>{{ rolesThatCanNotBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.none : rolesThatCanNotBeUsedThisEmojiAsReaction.length }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkButton rounded @click="addRole(false)"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
|
||||
<div v-for="role in rolesThatCanNotBeUsedThisEmojiAsReaction" :key="role.id" :class="$style.roleItem">
|
||||
<MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
|
||||
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(false, role, $event)"><i class="ti ti-x"></i></button>
|
||||
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||
</div>
|
||||
|
||||
@ -97,12 +114,18 @@ let isSensitive = $ref(props.emoji ? props.emoji.isSensitive : false);
|
||||
let localOnly = $ref(props.emoji ? props.emoji.localOnly : false);
|
||||
let roleIdsThatCanBeUsedThisEmojiAsReaction = $ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
|
||||
let rolesThatCanBeUsedThisEmojiAsReaction = $ref([]);
|
||||
let roleIdsThatCanNotBeUsedThisEmojiAsReaction = $ref(props.emoji ? props.emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction : []);
|
||||
let rolesThatCanNotBeUsedThisEmojiAsReaction = $ref([]);
|
||||
let file = $ref<misskey.entities.DriveFile>();
|
||||
|
||||
watch($$(roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {
|
||||
rolesThatCanBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
||||
}, { immediate: true });
|
||||
|
||||
watch($$(roleIdsThatCanNotBeUsedThisEmojiAsReaction), async () => {
|
||||
rolesThatCanNotBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanNotBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
||||
}, { immediate: true });
|
||||
|
||||
const imgUrl = computed(() => file ? file.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -118,20 +141,22 @@ async function changeImage(ev) {
|
||||
}
|
||||
}
|
||||
|
||||
async function addRole() {
|
||||
async function addRole(type: boolean) {
|
||||
const roles = await os.api('admin/roles/list');
|
||||
const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id);
|
||||
const currentRoleIds = type ? rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id) : rolesThatCanNotBeUsedThisEmojiAsReaction.map(x => x.id);
|
||||
|
||||
const { canceled, result: role } = await os.select({
|
||||
items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
rolesThatCanBeUsedThisEmojiAsReaction.push(role);
|
||||
if (type) rolesThatCanBeUsedThisEmojiAsReaction.push(role);
|
||||
else rolesThatCanNotBeUsedThisEmojiAsReaction.push(role);
|
||||
}
|
||||
|
||||
async function removeRole(role, ev) {
|
||||
rolesThatCanBeUsedThisEmojiAsReaction = rolesThatCanBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id);
|
||||
async function removeRole(type: boolean, role, ev) {
|
||||
if (type) rolesThatCanBeUsedThisEmojiAsReaction = rolesThatCanBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id);
|
||||
else rolesThatCanNotBeUsedThisEmojiAsReaction = rolesThatCanNotBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id);
|
||||
}
|
||||
|
||||
async function done() {
|
||||
@ -143,6 +168,7 @@ async function done() {
|
||||
isSensitive,
|
||||
localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id),
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: rolesThatCanNotBeUsedThisEmojiAsReaction.map(x => x.id),
|
||||
};
|
||||
|
||||
if (file) {
|
||||
|
@ -263,6 +263,9 @@ type CustomEmoji = {
|
||||
url: string;
|
||||
category: string;
|
||||
aliases: string[];
|
||||
isSensitive?: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: string[];
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction?: string[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -281,6 +281,9 @@ export type CustomEmoji = {
|
||||
url: string;
|
||||
category: string;
|
||||
aliases: string[];
|
||||
isSensitive?: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: string[];
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction?: string[];
|
||||
};
|
||||
|
||||
export type LiteInstanceMetadata = {
|
||||
|
Loading…
Reference in New Issue
Block a user