@@ -70,6 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
70
70
:enableEmojiMenu="true"
71
71
:enableEmojiMenuReaction="true"
72
72
/>
73
+ <MkPhishingCaution v-if="isSuspectPhishingLink" :cautionMessage="i18n.ts.shortPhishingCaution" />
73
74
<div v-if="translating || translation" :class="$style.translation">
74
75
<MkLoading v-if="translating" mini/>
75
76
<div v-else-if="translation">
@@ -82,7 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only
82
83
<MkMediaList :mediaList="appearNote.files"/>
83
84
</div>
84
85
<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
85
- <MkUrlPreview v-for="url in urls " :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
86
+ <MkUrlPreview v-for="url in previewUrls " :key="url" :url="url.href " :compact="true" :detail="false" :class="$style.urlPreview"/>
86
87
<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
87
88
<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
88
89
<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
@@ -168,6 +169,7 @@ import MkPoll from '@/components/MkPoll.vue';
168
169
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
169
170
import MkUrlPreview from '@/components/MkUrlPreview.vue';
170
171
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
172
+ import MkPhishingCaution from '@/components/MkPhishingCaution.vue';
171
173
import { pleaseLogin } from '@/scripts/please-login.js';
172
174
import { focusPrev, focusNext } from '@/scripts/focus.js';
173
175
import { checkWordMute } from '@/scripts/check-word-mute.js';
@@ -250,7 +252,8 @@ const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entiti
250
252
const isMyRenote = $i && ($i.id === note.value.userId);
251
253
const showContent = ref(false);
252
254
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
253
- const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
255
+ const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url.href && appearNote.value.renote?.uri !== url.href) : null);
256
+ const previewUrls = computed(() => urls.value.filter(url => url.preview))
254
257
const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
255
258
const collapsed = ref(appearNote.value.cw == null && isLong);
256
259
const isDeleted = ref(false);
@@ -266,6 +269,40 @@ const renoteCollapsed = ref(
266
269
(appearNote.value.myReaction != null)
267
270
)
268
271
);
272
+ function isURL(str) {
273
+ try {
274
+ new URL(str);
275
+ return true;
276
+ } catch (e) {
277
+ return false;
278
+ }
279
+ }
280
+ function getDomain(url) {
281
+ try {
282
+ const domain = new URL(url).hostname;
283
+ return domain;
284
+ } catch (e) {
285
+ return null;
286
+ }
287
+ }
288
+ const isSuspectPhishingLink = computed(() => {
289
+ return urls.value.some(url => {
290
+ // url.textが配列でない場合、配列に変換
291
+ const text = Array.isArray(url.text) ? url.text.join('') : url.text;
292
+
293
+ // textがURLでない場合、すぐに次のurlへ
294
+ console.log(text)
295
+ if (!isURL((text.startsWith('https://') || text.startsWith('http://')) ? text : `https://${text}`)) return false;
296
+
297
+ // hrefとtextのドメインを比較
298
+ const hrefDomain = getDomain(url.href);
299
+ const textDomain = getDomain(text);
300
+ console.log(hrefDomain, textDomain)
301
+
302
+ // ドメインが一致しない場合、フィッシングの疑いあり
303
+ return hrefDomain !== textDomain && textDomain;
304
+ });
305
+ });
269
306
270
307
/* Overload FunctionにLintが対応していないのでコメントアウト
271
308
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
0 commit comments