From 38d4a7fd56fe8fb9b027e16f27e907c865a9c754 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 20 May 2025 21:21:42 -0400
Subject: don't recursively render note previews

---
 packages/frontend/src/components/MkNoteDetailed.vue | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

(limited to 'packages/frontend/src/components/MkNoteDetailed.vue')

diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index bde2086508..c05b8afcfb 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -112,7 +112,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 				<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :class="$style.poll" :author="appearNote.user" :emojiUrls="appearNote.emojis"/>
 				<div v-if="isEnabledUrlPreview">
-					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" :showAsQuote="!appearNote.user.rejectQuotes" :skipNoteIds="[appearNote.renote?.id]" style="margin-top: 6px;"/>
+					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" :showAsQuote="!appearNote.user.rejectQuotes" :skipNoteIds="selfNoteIds" style="margin-top: 6px;"/>
 				</div>
 				<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote" :expandAllCws="props.expandAllCws"/></div>
 			</div>
@@ -285,6 +285,7 @@ import { getPluginHandlers } from '@/plugin.js';
 import { DI } from '@/di.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
 import SkNoteTranslation from '@/components/SkNoteTranslation.vue';
+import { getSelfNoteIds } from '@/utility/get-self-note-ids.js';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -339,6 +340,7 @@ const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(
 const translating = ref(false);
 const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
 const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
+const selfNoteIds = computed(() => getSelfNoteIds(props.note));
 const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
 const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm);
 const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance);
-- 
cgit v1.2.3-freya


From dc1adcc4918cb8019c3263c99503460431d3516b Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 20 May 2025 21:37:25 -0400
Subject: skip resolving preview when a link is known to be recursive

---
 packages/frontend/src/components/MkNote.vue        |  3 +-
 .../frontend/src/components/MkNoteDetailed.vue     |  7 +++--
 packages/frontend/src/components/SkNote.vue        |  3 +-
 .../frontend/src/components/SkNoteDetailed.vue     |  7 +++--
 .../frontend/src/components/SkOldNoteWindow.vue    |  9 +++---
 .../frontend/src/utility/extract-preview-urls.ts   | 32 ++++++++++++++++++++++
 .../frontend/src/utility/extract-url-from-mfm.ts   |  1 +
 7 files changed, 49 insertions(+), 13 deletions(-)
 create mode 100644 packages/frontend/src/utility/extract-preview-urls.ts

(limited to 'packages/frontend/src/components/MkNoteDetailed.vue')

diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index dcf477f74d..2ffa2778fc 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -236,6 +236,7 @@ import { useRouter } from '@/router.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
 import SkNoteTranslation from '@/components/SkNoteTranslation.vue';
 import { getSelfNoteIds } from '@/utility/get-self-note-ids.js';
+import { extractPreviewUrls } from '@/utility/extract-preview-urls.js';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -303,7 +304,7 @@ const galleryEl = useTemplateRef('galleryEl');
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(prefer.s.uncollapseCW);
 const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
-const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
+const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : null);
 const selfNoteIds = computed(() => getSelfNoteIds(props.note));
 const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
 const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index c05b8afcfb..f5f4bb64ec 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -286,6 +286,7 @@ import { DI } from '@/di.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
 import SkNoteTranslation from '@/components/SkNoteTranslation.vue';
 import { getSelfNoteIds } from '@/utility/get-self-note-ids.js';
+import { extractPreviewUrls } from '@/utility/extract-preview-urls.js';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -338,10 +339,10 @@ const isDeleted = ref(false);
 const renoted = ref(false);
 const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
 const translating = ref(false);
-const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
-const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
+const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
+const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : null);
 const selfNoteIds = computed(() => getSelfNoteIds(props.note));
-const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
+const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
 const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm);
 const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance);
 const conversation = ref<Misskey.entities.Note[]>([]);
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 49ed815af8..621f732caa 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -236,6 +236,7 @@ import { useRouter } from '@/router.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
 import SkNoteTranslation from '@/components/SkNoteTranslation.vue';
 import { getSelfNoteIds } from '@/utility/get-self-note-ids.js';
+import { extractPreviewUrls } from '@/utility/extract-preview-urls.js';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -303,7 +304,7 @@ const galleryEl = useTemplateRef('galleryEl');
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(prefer.s.uncollapseCW);
 const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
-const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
+const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : null);
 const selfNoteIds = computed(() => getSelfNoteIds(props.note));
 const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
 const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index 7dab05d157..e96c80e3d4 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -291,6 +291,7 @@ import { DI } from '@/di.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
 import SkNoteTranslation from '@/components/SkNoteTranslation.vue';
 import { getSelfNoteIds } from '@/utility/get-self-note-ids.js';
+import { extractPreviewUrls } from '@/utility/extract-preview-urls';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -344,10 +345,10 @@ const isDeleted = ref(false);
 const renoted = ref(false);
 const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
 const translating = ref(false);
-const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
-const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
+const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
+const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : null);
 const selfNoteIds = computed(() => getSelfNoteIds(props.note));
-const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
+const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
 const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm ? true : false);
 const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance);
 const conversation = ref<Misskey.entities.Note[]>([]);
diff --git a/packages/frontend/src/components/SkOldNoteWindow.vue b/packages/frontend/src/components/SkOldNoteWindow.vue
index 50c500dbea..b6dbec81c5 100644
--- a/packages/frontend/src/components/SkOldNoteWindow.vue
+++ b/packages/frontend/src/components/SkOldNoteWindow.vue
@@ -86,7 +86,6 @@ import MkPoll from '@/components/MkPoll.vue';
 import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
 import { userPage } from '@/filters/user.js';
-import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
 import { i18n } from '@/i18n.js';
 import { deepClone } from '@/utility/clone.js';
 import { dateTimeFormat } from '@/utility/intl-const.js';
@@ -94,6 +93,7 @@ import { prefer } from '@/preferences';
 import { getPluginHandlers } from '@/plugin.js';
 import SkNoteTranslation from '@/components/SkNoteTranslation.vue';
 import { getSelfNoteIds } from '@/utility/get-self-note-ids';
+import { extractPreviewUrls } from '@/utility/extract-preview-urls.js';
 
 const props = defineProps<{
 	note: Misskey.entities.Note;
@@ -142,14 +142,13 @@ const isRenote = (
 );
 
 const el = shallowRef<HTMLElement>();
-let appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
+const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
+const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
 
 const showContent = ref(false);
 const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
 const translating = ref(false);
-const urls = appearNote.value.text ? extractUrlFromMfm(mfm.parse(appearNote.value.text)).filter(u => u !== renoteUrl && u !== renoteUri) : null;
+const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : null);
 const selfNoteIds = computed(() => getSelfNoteIds(props.note));
 const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance);
 
diff --git a/packages/frontend/src/utility/extract-preview-urls.ts b/packages/frontend/src/utility/extract-preview-urls.ts
new file mode 100644
index 0000000000..e14ed68f27
--- /dev/null
+++ b/packages/frontend/src/utility/extract-preview-urls.ts
@@ -0,0 +1,32 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { host } from '@@/js/config.js';
+import type * as Misskey from 'misskey-js';
+import type * as mfm from '@transfem-org/sfm-js';
+import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
+
+/**
+ * Extracts all previewable URLs from a note.
+ */
+export function extractPreviewUrls(note: Misskey.entities.Note, contents: mfm.MfmNode[]): string[] {
+	const links = extractUrlFromMfm(contents);
+	return links.filter(url =>
+		// Remote note
+		url !== note.url &&
+		url !== note.uri &&
+		// Local note
+		url !== `https://${host}/notes/${note.id}` &&
+		// Remote renote or quote
+		url !== note.renote?.url &&
+		url !== note.renote?.uri &&
+		// Local renote or quote
+		url !== `https://${host}/notes/${note.renote?.id}` &&
+		// Remote renote *of* a quote
+		url !== note.renote?.renote?.url &&
+		url !== note.renote?.renote?.uri &&
+		// Local renote *of* a quote
+		url !== `https://${host}/notes/${note.renote?.renote?.id}`);
+}
diff --git a/packages/frontend/src/utility/extract-url-from-mfm.ts b/packages/frontend/src/utility/extract-url-from-mfm.ts
index baebbff8ae..e1b9df138e 100644
--- a/packages/frontend/src/utility/extract-url-from-mfm.ts
+++ b/packages/frontend/src/utility/extract-url-from-mfm.ts
@@ -10,6 +10,7 @@ import { unique } from '@/utility/array.js';
 // [ http://a/#1, http://a/#2, http://b/#3 ] => [ http://a/#1, http://b/#3 ]
 const removeHash = (x: string) => x.replace(/#[^#]*$/, '');
 
+// TODO this is O(n^2) which could introduce a frontend DoS with a large enough character limit
 export function extractUrlFromMfm(nodes: mfm.MfmNode[], respectSilentFlag = true): string[] {
 	const urlNodes = mfm.extract(nodes, (node) => {
 		return (node.type === 'url') || (node.type === 'link' && (!respectSilentFlag || !node.props.silent));
-- 
cgit v1.2.3-freya


From 28551c810309c69c8cc58fbc254507e705e8fa05 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Wed, 21 May 2025 08:27:23 -0400
Subject: use config.url instead of "https://${config.host}"

---
 packages/backend/src/server/web/ClientServerService.ts | 1 +
 packages/backend/src/server/web/views/info-card.pug    | 2 +-
 packages/frontend/src/components/MkNote.vue            | 4 ++--
 packages/frontend/src/components/MkNoteDetailed.vue    | 4 ++--
 packages/frontend/src/components/MkNoteSub.vue         | 4 ++--
 packages/frontend/src/components/MkPoll.vue            | 4 ++--
 packages/frontend/src/components/SkNote.vue            | 4 ++--
 packages/frontend/src/components/SkNoteDetailed.vue    | 4 ++--
 packages/frontend/src/components/SkNoteSub.vue         | 4 ++--
 packages/frontend/src/pages/note.vue                   | 4 ++--
 packages/frontend/src/utility/extract-preview-urls.ts  | 8 ++++----
 11 files changed, 22 insertions(+), 21 deletions(-)

(limited to 'packages/frontend/src/components/MkNoteDetailed.vue')

diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 1321cf6338..c40d042fa4 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -890,6 +890,7 @@ export class ClientServerService {
 			return await reply.view('info-card', {
 				version: this.config.version,
 				host: this.config.host,
+				url: this.config.url,
 				meta: this.meta,
 				originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }),
 				originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
diff --git a/packages/backend/src/server/web/views/info-card.pug b/packages/backend/src/server/web/views/info-card.pug
index 4a9d00a596..0a95ea7b17 100644
--- a/packages/backend/src/server/web/views/info-card.pug
+++ b/packages/backend/src/server/web/views/info-card.pug
@@ -43,7 +43,7 @@ html
 			}
 
 	body
-		a#a(href=`https://${host}` target="_blank")
+		a#a(href=url target="_blank")
 			header#banner(style=`background-image: url(${meta.bannerUrl})`)
 				div#title= meta.name || host
 		div#content
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 2ffa2778fc..6a356ffd37 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -184,7 +184,7 @@ import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import { isLink } from '@@/js/is-link.js';
 import { shouldCollapsed } from '@@/js/collapsed.js';
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import { computeMergedCw } from '@@/js/compute-merged-cw.js';
 import type { Ref } from 'vue';
 import type { MenuItem } from '@/types/menu.js';
@@ -328,7 +328,7 @@ const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm);
 
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
-	url: appearNote.value.url ?? appearNote.value.uri ?? `https://${host}/notes/${appearNote.value.id}`,
+	url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
 }));
 
 const mergedCW = computed(() => computeMergedCw(appearNote.value));
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index f5f4bb64ec..92a3c7b5cc 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -236,7 +236,7 @@ import { computed, inject, onMounted, provide, ref, useTemplateRef, watch } from
 import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import { isLink } from '@@/js/is-link.js';
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import { computeMergedCw } from '@@/js/compute-merged-cw.js';
 import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
 import type { Paging } from '@/components/MkPagination.vue';
@@ -375,7 +375,7 @@ let renoting = false;
 
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
-	url: appearNote.value.url ?? appearNote.value.uri ?? `https://${host}/notes/${appearNote.value.id}`,
+	url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
 }));
 
 const keymap = {
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index eb72939bf1..b6a18ccab6 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -81,7 +81,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, ref, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { computeMergedCw } from '@@/js/compute-merged-cw.js';
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import type { Visibility } from '@/utility/boost-quote.js';
 import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
 import MkNoteHeader from '@/components/MkNoteHeader.vue';
@@ -150,7 +150,7 @@ const isRenote = (
 
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
-	url: appearNote.value.url ?? appearNote.value.uri ?? `https://${host}/notes/${appearNote.value.id}`,
+	url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
 }));
 
 async function addReplyTo(replyNote: Misskey.entities.Note) {
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index 07febacc36..72f3ced088 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import { useInterval } from '@@/js/use-interval.js';
 import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
 import { sum } from '@/utility/array.js';
@@ -72,7 +72,7 @@ const showResult = ref(props.readOnly || isVoted.value);
 
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
-	url: `https://${host}/notes/${props.noteId}`,
+	url: `${config.url}/notes/${props.noteId}`,
 }));
 
 // 期限付きアンケート
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 621f732caa..99cd85d123 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -185,7 +185,7 @@ import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import { isLink } from '@@/js/is-link.js';
 import { shouldCollapsed } from '@@/js/collapsed.js';
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import { computeMergedCw } from '@@/js/compute-merged-cw.js';
 import type { Ref } from 'vue';
 import type { MenuItem } from '@/types/menu.js';
@@ -328,7 +328,7 @@ const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm);
 
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
-	url: appearNote.value.url ?? appearNote.value.uri ?? `https://${host}/notes/${appearNote.value.id}`,
+	url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
 }));
 
 const mergedCW = computed(() => computeMergedCw(appearNote.value));
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index e96c80e3d4..9ca36c2fc9 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -241,7 +241,7 @@ import { computed, inject, onMounted, onUnmounted, onUpdated, provide, ref, useT
 import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import { isLink } from '@@/js/is-link.js';
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import { computeMergedCw } from '@@/js/compute-merged-cw.js';
 import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
 import type { Paging } from '@/components/MkPagination.vue';
@@ -381,7 +381,7 @@ let renoting = false;
 
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
-	url: appearNote.value.url ?? appearNote.value.uri ?? `https://${host}/notes/${appearNote.value.id}`,
+	url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
 }));
 
 const keymap = {
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index 434af87ed9..12e12ad028 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, inject, ref, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { computeMergedCw } from '@@/js/compute-merged-cw.js';
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import type { Visibility } from '@/utility/boost-quote.js';
 import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
 import SkNoteHeader from '@/components/SkNoteHeader.vue';
@@ -164,7 +164,7 @@ const isRenote = (
 
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
-	url: appearNote.value.url ?? appearNote.value.uri ?? `https://${host}/notes/${appearNote.value.id}`,
+	url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
 }));
 
 async function addReplyTo(replyNote: Misskey.entities.Note) {
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index d801db017e..85befbb18b 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import type { Paging } from '@/components/MkPagination.vue';
 import DynamicNoteDetailed from '@/components/DynamicNoteDetailed.vue';
 import MkNotes from '@/components/MkNotes.vue';
@@ -151,7 +151,7 @@ function fetchNote() {
 				message: i18n.ts.thisContentsAreMarkedAsSigninRequiredByAuthor,
 				openOnRemote: {
 					type: 'lookup',
-					url: `https://${host}/notes/${props.noteId}`,
+					url: `${config.url}/notes/${props.noteId}`,
 				},
 			});
 		}
diff --git a/packages/frontend/src/utility/extract-preview-urls.ts b/packages/frontend/src/utility/extract-preview-urls.ts
index e14ed68f27..dd066df888 100644
--- a/packages/frontend/src/utility/extract-preview-urls.ts
+++ b/packages/frontend/src/utility/extract-preview-urls.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { host } from '@@/js/config.js';
+import * as config from '@@/js/config.js';
 import type * as Misskey from 'misskey-js';
 import type * as mfm from '@transfem-org/sfm-js';
 import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
@@ -18,15 +18,15 @@ export function extractPreviewUrls(note: Misskey.entities.Note, contents: mfm.Mf
 		url !== note.url &&
 		url !== note.uri &&
 		// Local note
-		url !== `https://${host}/notes/${note.id}` &&
+		url !== `${config.url}/notes/${note.id}` &&
 		// Remote renote or quote
 		url !== note.renote?.url &&
 		url !== note.renote?.uri &&
 		// Local renote or quote
-		url !== `https://${host}/notes/${note.renote?.id}` &&
+		url !== `${config.url}/notes/${note.renote?.id}` &&
 		// Remote renote *of* a quote
 		url !== note.renote?.renote?.url &&
 		url !== note.renote?.renote?.uri &&
 		// Local renote *of* a quote
-		url !== `https://${host}/notes/${note.renote?.renote?.id}`);
+		url !== `${config.url}/notes/${note.renote?.renote?.id}`);
 }
-- 
cgit v1.2.3-freya

