Skip to content

Commit

Permalink
refactor: 絵文字URLを引き回すのをやめる (misskey-dev#9423)
Browse files Browse the repository at this point in the history
  • Loading branch information
syuilo committed Dec 29, 2022
1 parent 510e6ec commit 912791b
Show file tree
Hide file tree
Showing 28 changed files with 79 additions and 58 deletions.
8 changes: 5 additions & 3 deletions packages/backend/src/core/entities/EmojiEntityService.ts
Expand Up @@ -6,8 +6,8 @@ import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { Emoji } from '@/models/entities/Emoji.js';
import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';

@Injectable()
export class EmojiEntityService {
Expand All @@ -22,6 +22,7 @@ export class EmojiEntityService {
@bindThis
public async pack(
src: Emoji['id'] | Emoji,
opts: { omitUrl?: boolean } = {},
): Promise<Packed<'Emoji'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });

Expand All @@ -32,15 +33,16 @@ export class EmojiEntityService {
category: emoji.category,
host: emoji.host,
// ?? emoji.originalUrl してるのは後方互換性のため
url: emoji.publicUrl ?? emoji.originalUrl,
url: opts.omitUrl ? undefined : (emoji.publicUrl ?? emoji.originalUrl),
};
}

@bindThis
public packMany(
emojis: any[],
opts: { omitUrl?: boolean } = {},
) {
return Promise.all(emojis.map(x => this.pack(x)));
return Promise.all(emojis.map(x => this.pack(x, opts)));
}
}

2 changes: 1 addition & 1 deletion packages/backend/src/models/schema/emoji.ts
Expand Up @@ -31,7 +31,7 @@ export const packedEmojiSchema = {
},
url: {
type: 'string',
optional: false, nullable: false,
optional: true, nullable: false,
},
},
} as const;
3 changes: 2 additions & 1 deletion packages/backend/src/server/api/endpoints/meta.ts
Expand Up @@ -309,6 +309,7 @@ export const paramDef = {
type: 'object',
properties: {
detail: { type: 'boolean', default: true },
omitEmojiUrl: { type: 'boolean', default: false },
},
required: [],
} as const;
Expand Down Expand Up @@ -390,7 +391,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
emojis: await this.emojiEntityService.packMany(emojis),
emojis: await this.emojiEntityService.packMany(emojis, { omitUrl: ps.omitEmojiUrl }),
defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme,
ads: ads.map(ad => ({
Expand Down
32 changes: 31 additions & 1 deletion packages/backend/src/server/web/ClientServerService.ts
Expand Up @@ -26,7 +26,7 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { ChannelsRepository, ClipsRepository, EmojisRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import { deepClone } from '@/misc/clone.js';
import { bindThis } from '@/decorators.js';
import manifest from './manifest.json' assert { type: 'json' };
Expand Down Expand Up @@ -70,6 +70,9 @@ export class ClientServerService {
@Inject(DI.pagesRepository)
private pagesRepository: PagesRepository,

@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,

private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private pageEntityService: PageEntityService,
Expand Down Expand Up @@ -217,6 +220,33 @@ export class ClientServerService {
return reply.sendFile('/apple-touch-icon.png', staticAssets);
});

fastify.get<{ Params: { path: string } }>('/emoji/:path(.*)', async (request, reply) => {
const path = request.params.path;

if (!path.match(/^[a-zA-Z0-9\-_@\.]+?\.webp$/)) {
reply.code(404);
return;
}

const name = path.split('@')[0].replace('.webp', '');
const host = path.split('@')[1]?.replace('.webp', '');

const emoji = await this.emojisRepository.findOneBy({
host: host == null ? IsNull() : host,
name: name,
});

if (emoji == null) {
reply.code(404);
return;
}

reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');

// ?? emoji.originalUrl してるのは後方互換性のため
return await reply.redirect(301, emoji.publicUrl ?? emoji.originalUrl);
});

fastify.get<{ Params: { path: string } }>('/fluent-emoji/:path(.*)', async (request, reply) => {
const path = request.params.path;

Expand Down
6 changes: 3 additions & 3 deletions packages/frontend/src/components/MkNote.vue
Expand Up @@ -37,20 +37,20 @@
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
<div class="body">
<p v-if="appearNote.cw != null" class="cw">
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
<XCwButton v-model="showContent" :note="appearNote"/>
</p>
<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
<div class="text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i"/>
<a v-if="appearNote.renote != null" class="rp">RN:</a>
<div v-if="translating || translation" class="translation">
<MkLoading v-if="translating" mini/>
<div v-else class="translated">
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<Mfm :text="translation.text" :author="appearNote.user" :i="$i"/>
</div>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions packages/frontend/src/components/MkNoteDetailed.vue
Expand Up @@ -48,20 +48,20 @@
<div class="main">
<div class="body">
<p v-if="appearNote.cw != null" class="cw">
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
<XCwButton v-model="showContent" :note="appearNote"/>
</p>
<div v-show="appearNote.cw == null || showContent" class="content">
<div class="text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i"/>
<a v-if="appearNote.renote != null" class="rp">RN:</a>
<div v-if="translating || translation" class="translation">
<MkLoading v-if="translating" mini/>
<div v-else class="translated">
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<Mfm :text="translation.text" :author="appearNote.user" :i="$i"/>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkNoteSimple.vue
Expand Up @@ -5,7 +5,7 @@
<XNoteHeader class="header" :note="note" :mini="true"/>
<div class="body">
<p v-if="note.cw != null" class="cw">
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/>
<XCwButton v-model="showContent" :note="note"/>
</p>
<div v-show="note.cw == null || showContent" class="content">
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkNoteSub.vue
Expand Up @@ -6,7 +6,7 @@
<XNoteHeader class="header" :note="note" :mini="true"/>
<div class="body">
<p v-if="note.cw != null" class="cw">
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/>
<XCwButton v-model="showContent" :note="note"/>
</p>
<div v-show="note.cw == null || showContent" class="content">
Expand Down
14 changes: 7 additions & 7 deletions packages/frontend/src/components/MkNotification.vue
Expand Up @@ -34,31 +34,31 @@
</header>
<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ti ti-quote"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
<i class="ti ti-quote"></i>
</MkA>
<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
<i class="ti ti-quote"></i>
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.renote.emojis"/>
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full"/>
<i class="ti ti-quote"></i>
</MkA>
<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
</MkA>
<MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
</MkA>
<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
</MkA>
<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ti ti-quote"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
<i class="ti ti-quote"></i>
</MkA>
<MkA v-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ti ti-quote"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
<i class="ti ti-quote"></i>
</MkA>
<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkPoll.vue
Expand Up @@ -5,7 +5,7 @@
<div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
<span>
<template v-if="choice.isVoted"><i class="ti ti-check"></i></template>
<Mfm :text="choice.text" :plain="true" :custom-emojis="note.emojis"/>
<Mfm :text="choice.text" :plain="true"/>
<span v-if="showResult" class="votes">({{ $t('_poll.votesCount', { n: choice.votes }) }})</span>
</span>
</li>
Expand Down
3 changes: 1 addition & 2 deletions packages/frontend/src/components/MkReactionIcon.vue
@@ -1,13 +1,12 @@
<template>
<MkEmoji :emoji="reaction" :custom-emojis="customEmojis || []" :is-reaction="true" :normal="true" :no-style="noStyle"/>
<MkEmoji :emoji="reaction" :is-reaction="true" :normal="true" :no-style="noStyle"/>
</template>

<script lang="ts" setup>
import { } from 'vue';
const props = defineProps<{
reaction: string;
customEmojis?: any[]; // TODO
noStyle?: boolean;
}>();
</script>
3 changes: 1 addition & 2 deletions packages/frontend/src/components/MkReactionTooltip.vue
@@ -1,7 +1,7 @@
<template>
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
<div class="beeadbfb">
<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/>
<XReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
<div class="name">{{ reaction.replace('@.', '') }}</div>
</div>
</MkTooltip>
Expand All @@ -15,7 +15,6 @@ import XReactionIcon from '@/components/MkReactionIcon.vue';
defineProps<{
showing: boolean;
reaction: string;
emojis: any[]; // TODO
targetElement: HTMLElement;
}>();
Expand Down
Expand Up @@ -2,7 +2,7 @@
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
<div class="bqxuuuey">
<div class="reaction">
<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/>
<XReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
<div class="name">{{ getReactionName(reaction) }}</div>
</div>
<div class="users">
Expand All @@ -27,7 +27,6 @@ defineProps<{
reaction: string;
users: any[]; // TODO
count: number;
emojis: any[]; // TODO
targetElement: HTMLElement;
}>();
Expand Down
Expand Up @@ -7,7 +7,7 @@
:class="{ reacted: note.myReaction == reaction, canToggle }"
@click="toggleReaction()"
>
<XReactionIcon class="icon" :reaction="reaction" :custom-emojis="note.emojis"/>
<XReactionIcon class="icon" :reaction="reaction"/>
<span class="count">{{ count }}</span>
</button>
</template>
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkSubNoteContent.vue
Expand Up @@ -4,7 +4,7 @@
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/>
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div>
<details v-if="note.files.length > 0">
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkUserInfo.vue
Expand Up @@ -9,7 +9,7 @@
<span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span>
<div class="description">
<div v-if="user.description" class="mfm">
<Mfm :text="user.description" :author="user" :i="$i" :custom-emojis="user.emojis"/>
<Mfm :text="user.description" :author="user" :i="$i"/>
</div>
<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkUserPreview.vue
Expand Up @@ -11,7 +11,7 @@
<p class="username"><MkAcct :user="user"/></p>
</div>
<div class="description">
<Mfm v-if="user.description" :text="user.description" :author="user" :i="$i" :custom-emojis="user.emojis"/>
<Mfm v-if="user.description" :text="user.description" :author="user" :i="$i"/>
</div>
<div class="status">
<div>
Expand Down
16 changes: 6 additions & 10 deletions packages/frontend/src/components/global/MkEmoji.vue
@@ -1,50 +1,46 @@
<template>
<img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/>
<img v-if="isCustom" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/>
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/>
<span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span>
<span v-else>{{ emoji }}</span>
</template>

<script lang="ts" setup>
import { computed } from 'vue';
import { CustomEmoji } from 'misskey-js/built/entities';
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base';
import { defaultStore } from '@/store';
import { instance } from '@/instance';
import { getEmojiName } from '@/scripts/emojilist';
const props = defineProps<{
emoji: string;
normal?: boolean;
noStyle?: boolean;
customEmojis?: CustomEmoji[];
isReaction?: boolean;
}>();
const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
const isCustom = computed(() => props.emoji.startsWith(':'));
const customEmojiName = props.emoji.substr(1, props.emoji.length - 2);
const char = computed(() => isCustom.value ? undefined : props.emoji);
const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native' && !props.isReaction);
const ce = computed(() => props.customEmojis ?? instance.emojis ?? []);
const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : undefined);
const url = computed(() => {
if (char.value) {
return char2path(char.value);
} else {
const rawUrl = (customEmoji.value as CustomEmoji).url;
const rawUrl = '/emoji/' + customEmojiName + '.webp';
return defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(rawUrl)
: rawUrl;
}
});
const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value);
const alt = computed(() => isCustom.value ? `:${customEmojiName}:` : char.value);
// Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter
function computeTitle(event: PointerEvent): void {
const title = customEmoji.value
? `:${customEmoji.value.name}:`
const title = isCustom.value
? `:${customEmojiName}:`
: (getEmojiName(char.value as string) ?? char.value as string);
(event.target as HTMLElement).title = title;
}
Expand Down
@@ -1,5 +1,5 @@
<template>
<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :customEmojis="customEmojis" :isNote="isNote" class="havbbuyv" :class="{ nowrap }"/>
<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :is-note="isNote" class="havbbuyv" :class="{ nowrap }"/>
</template>

<script lang="ts" setup>
Expand All @@ -11,7 +11,6 @@ const props = withDefaults(defineProps<{
plain?: boolean;
nowrap?: boolean;
author?: any;
customEmojis?: any;
isNote?: boolean;
}>(), {
plain: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/global/MkUserName.vue
@@ -1,5 +1,5 @@
<template>
<Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/>
<Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap"/>
</template>

<script lang="ts" setup>
Expand Down

0 comments on commit 912791b

Please sign in to comment.