From 81d2c5a4a7355af8a385893136e12e618a8b92d2 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Wed, 12 Apr 2023 10:58:56 +0900
Subject: [PATCH] =?UTF-8?q?enhance:=20=E3=82=AB=E3=82=B9=E3=82=BF=E3=83=A0?=
 =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E9=96=A2=E9=80=A3=E3=81=AE=E5=A4=89?=
 =?UTF-8?q?=E6=9B=B4=20(#9794)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* PackedNoteなどのemojisはプロキシしていないURLを返すように

* MFMでx3/x4もしくはscale.x/yが2.5以上に指定されていた場合にはオリジナル品質の絵文字を使用する

* update CHANGELOG.md

* fix changelog

* ??

* wip

* fix

* merge

* Update packages/frontend/src/scripts/media-proxy.ts

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* merge

* calc scale

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 CHANGELOG.md                                  |  4 +-
 .../backend/src/core/CustomEmojiService.ts    | 11 +---
 .../src/components/global/MkCustomEmoji.vue   | 27 +++++++---
 packages/frontend/src/components/mfm.ts       | 52 ++++++++++++-------
 packages/frontend/src/scripts/media-proxy.ts  |  4 +-
 5 files changed, 58 insertions(+), 40 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2741b8f568..b1cd253a1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,9 @@
 ## 13.x.x (unreleased)
 
 ### General
--
+- カスタム絵文字関連の変更
+  * ノートなどに含まれるemojis(populateEmojiの結果)は(プロキシされたURLではなく)オリジナルのURLを指すように
+  * MFMでx3/x4もしくはscale.x/yが2.5以上に指定されていた場合にはオリジナル品質の絵文字を使用するように
 
 ### Client
 -
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 3de936dd65..416c3de5a8 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -267,16 +267,7 @@ export class CustomEmojiService {
 		const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull);
 
 		if (emoji == null) return null;
-
-		const isLocal = emoji.host == null;
-		const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
-		const url = isLocal
-			? emojiUrl
-			: this.config.proxyRemoteFiles
-				? `${this.config.mediaProxy}/emoji.webp?${query({ url: emojiUrl })}`
-				: emojiUrl;
-
-		return url;
+		return emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 	}
 
 	/**
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index 84aae1cff8..0cb31ffcba 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -5,7 +5,7 @@
 
 <script lang="ts" setup>
 import { computed } from 'vue';
-import { getStaticImageUrl } from '@/scripts/media-proxy';
+import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy';
 import { defaultStore } from '@/store';
 import { customEmojis } from '@/custom-emojis';
 
@@ -15,25 +15,38 @@ const props = defineProps<{
 	noStyle?: boolean;
 	host?: string | null;
 	url?: string;
+	useOriginalSize?: boolean;
 }>();
 
 const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substr(1, props.name.length - 2) : props.name).replace('@.', ''));
+const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@')));
 
 const rawUrl = computed(() => {
 	if (props.url) {
 		return props.url;
 	}
-	if (props.host == null && !customEmojiName.value.includes('@')) {
+	if (isLocal.value) {
 		return customEmojis.value.find(x => x.name === customEmojiName.value)?.url ?? null;
 	}
 	return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`;
 });
 
-const url = computed(() =>
-	defaultStore.reactiveState.disableShowingAnimatedImages.value && rawUrl.value
-		? getStaticImageUrl(rawUrl.value)
-		: rawUrl.value,
-);
+const url = computed(() => {
+	if (rawUrl.value == null) return null;
+
+	const proxied =
+		(rawUrl.value.startsWith('/emoji/') || (props.useOriginalSize && isLocal.value))
+			? rawUrl.value
+			: getProxiedImageUrl(
+				rawUrl.value,
+				props.useOriginalSize ? undefined : 'emoji',
+				false,
+				true,
+			);
+	return defaultStore.reactiveState.disableShowingAnimatedImages.value
+		? getStaticImageUrl(proxied)
+		: proxied;
+});
 
 const alt = computed(() => `:${customEmojiName.value}:`);
 let errored = $ref(url.value == null);
diff --git a/packages/frontend/src/components/mfm.ts b/packages/frontend/src/components/mfm.ts
index e84eabcbcc..c3c07b5834 100644
--- a/packages/frontend/src/components/mfm.ts
+++ b/packages/frontend/src/components/mfm.ts
@@ -51,6 +51,10 @@ export default defineComponent({
 			type: Object,
 			default: null,
 		},
+		rootScale: {
+			type: Number,
+			default: 1,
+		}
 	},
 
 	render() {
@@ -65,7 +69,12 @@ export default defineComponent({
 
 		const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm;
 
-		const genEl = (ast: mfm.MfmNode[]) => ast.map((token): VNode | string | (VNode | string)[] => {
+		/**
+		 * Gen Vue Elements from MFM AST
+		 * @param ast MFM AST
+		 * @param scale How times large the text is
+		 */
+		const genEl = (ast: mfm.MfmNode[], scale: number) => ast.map((token): VNode | string | (VNode | string)[] => {
 			switch (token.type) {
 				case 'text': {
 					const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
@@ -84,17 +93,17 @@ export default defineComponent({
 				}
 
 				case 'bold': {
-					return [h('b', genEl(token.children))];
+					return [h('b', genEl(token.children, scale))];
 				}
 
 				case 'strike': {
-					return [h('del', genEl(token.children))];
+					return [h('del', genEl(token.children, scale))];
 				}
 
 				case 'italic': {
 					return h('i', {
 						style: 'font-style: oblique;',
-					}, genEl(token.children));
+					}, genEl(token.children, scale));
 				}
 
 				case 'fn': {
@@ -155,17 +164,17 @@ export default defineComponent({
 						case 'x2': {
 							return h('span', {
 								class: defaultStore.state.advancedMfm ? 'mfm-x2' : '',
-							}, genEl(token.children));
+							}, genEl(token.children, scale * 2));
 						}
 						case 'x3': {
 							return h('span', {
 								class: defaultStore.state.advancedMfm ? 'mfm-x3' : '',
-							}, genEl(token.children));
+							}, genEl(token.children, scale * 3));
 						}
 						case 'x4': {
 							return h('span', {
 								class: defaultStore.state.advancedMfm ? 'mfm-x4' : '',
-							}, genEl(token.children));
+							}, genEl(token.children, scale * 4));
 						}
 						case 'font': {
 							const family =
@@ -182,7 +191,7 @@ export default defineComponent({
 						case 'blur': {
 							return h('span', {
 								class: '_mfm_blur_',
-							}, genEl(token.children));
+							}, genEl(token.children, scale));
 						}
 						case 'rainbow': {
 							const speed = validTime(token.props.args.speed) ?? '1s';
@@ -191,9 +200,9 @@ export default defineComponent({
 						}
 						case 'sparkle': {
 							if (!useAnim) {
-								return genEl(token.children);
+								return genEl(token.children, scale);
 							}
-							return h(MkSparkle, {}, genEl(token.children));
+							return h(MkSparkle, {}, genEl(token.children, scale));
 						}
 						case 'rotate': {
 							const degrees = parseFloat(token.props.args.deg ?? '90');
@@ -214,7 +223,8 @@ export default defineComponent({
 							}
 							const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
 							const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
-							style = `transform: scale(${x}, ${y});`;
+							style = `transform: scale(${x}, ${y});`; 
+							scale = scale * Math.max(x, y);
 							break;
 						}
 						case 'fg': {
@@ -231,24 +241,24 @@ export default defineComponent({
 						}
 					}
 					if (style == null) {
-						return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']);
+						return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);
 					} else {
 						return h('span', {
 							style: 'display: inline-block; ' + style,
-						}, genEl(token.children));
+						}, genEl(token.children, scale));
 					}
 				}
 
 				case 'small': {
 					return [h('small', {
 						style: 'opacity: 0.7;',
-					}, genEl(token.children))];
+					}, genEl(token.children, scale))];
 				}
 
 				case 'center': {
 					return [h('div', {
 						style: 'text-align:center;',
-					}, genEl(token.children))];
+					}, genEl(token.children, scale))];
 				}
 
 				case 'url': {
@@ -264,7 +274,7 @@ export default defineComponent({
 						key: Math.random(),
 						url: token.props.url,
 						rel: 'nofollow noopener',
-					}, genEl(token.children))];
+					}, genEl(token.children, scale))];
 				}
 
 				case 'mention': {
@@ -303,11 +313,11 @@ export default defineComponent({
 					if (!this.nowrap) {
 						return [h('div', {
 							style: QUOTE_STYLE,
-						}, genEl(token.children))];
+						}, genEl(token.children, scale))];
 					} else {
 						return [h('span', {
 							style: QUOTE_STYLE,
-						}, genEl(token.children))];
+						}, genEl(token.children, scale))];
 					}
 				}
 
@@ -319,6 +329,7 @@ export default defineComponent({
 							name: token.props.name,
 							normal: this.plain,
 							host: null,
+							useOriginalSize: scale >= 2.5,
 						})];
 					} else {
 						// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -332,6 +343,7 @@ export default defineComponent({
 								url: this.emojiUrls ? this.emojiUrls[token.props.name] : null,
 								normal: this.plain,
 								host: this.author.host,
+								useOriginalSize: scale >= 2.5,
 							})];
 						}
 					}
@@ -360,7 +372,7 @@ export default defineComponent({
 				}
 
 				case 'plain': {
-					return [h('span', genEl(token.children))];
+					return [h('span', genEl(token.children, scale))];
 				}
 
 				default: {
@@ -373,6 +385,6 @@ export default defineComponent({
 		}).flat(Infinity) as (VNode | string)[];
 
 		// Parse ast to DOM
-		return h('span', genEl(ast));
+		return h('span', genEl(ast, this.rootScale));
 	},
 });
diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts
index 91ac14c06d..3a3dbffb47 100644
--- a/packages/frontend/src/scripts/media-proxy.ts
+++ b/packages/frontend/src/scripts/media-proxy.ts
@@ -2,7 +2,7 @@ import { query } from '@/scripts/url';
 import { url } from '@/config';
 import { instance } from '@/instance';
 
-export function getProxiedImageUrl(imageUrl: string, type?: 'preview', mustOrigin: boolean = false): string {
+export function getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji' | 'avatar', mustOrigin: boolean = false, noFallback: boolean = false): string {
 	const localProxy = `${url}/proxy`;
 
 	if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
@@ -15,7 +15,7 @@ export function getProxiedImageUrl(imageUrl: string, type?: 'preview', mustOrigi
 		: 'image.webp'
 	}?${query({
 		url: imageUrl,
-		fallback: '1',
+		...(!noFallback ? { 'fallback': '1' } : {}),
 		...(type ? { [type]: '1' } : {}),
 		...(mustOrigin ? { origin: '1' } : {}),
 	})}`;