mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Fetch favicon from NIP-05 domain
This commit is contained in:
parent
79ac4ada81
commit
e539a29775
4 changed files with 101 additions and 1 deletions
|
|
@ -24,6 +24,7 @@
|
|||
"exclude": ["./public"],
|
||||
"imports": {
|
||||
"@/": "./src/",
|
||||
"@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.47",
|
||||
"@bradenmacdonald/s3-lite-client": "jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
||||
"@db/sqlite": "jsr:@db/sqlite@^0.11.1",
|
||||
"@hono/hono": "jsr:@hono/hono@^4.4.6",
|
||||
|
|
|
|||
46
deno.lock
generated
46
deno.lock
generated
|
|
@ -2,9 +2,11 @@
|
|||
"version": "3",
|
||||
"packages": {
|
||||
"specifiers": {
|
||||
"jsr:@b-fuze/deno-dom@^0.1.47": "jsr:@b-fuze/deno-dom@0.1.47",
|
||||
"jsr:@bradenmacdonald/s3-lite-client@^0.7.4": "jsr:@bradenmacdonald/s3-lite-client@0.7.6",
|
||||
"jsr:@db/sqlite@^0.11.1": "jsr:@db/sqlite@0.11.1",
|
||||
"jsr:@denosaurs/plug@1": "jsr:@denosaurs/plug@1.0.6",
|
||||
"jsr:@denosaurs/plug@1.0.3": "jsr:@denosaurs/plug@1.0.3",
|
||||
"jsr:@gleasonator/policy": "jsr:@gleasonator/policy@0.2.0",
|
||||
"jsr:@gleasonator/policy@0.2.0": "jsr:@gleasonator/policy@0.2.0",
|
||||
"jsr:@gleasonator/policy@0.4.0": "jsr:@gleasonator/policy@0.4.0",
|
||||
|
|
@ -20,6 +22,7 @@
|
|||
"jsr:@nostrify/types@^0.30.0": "jsr:@nostrify/types@0.30.0",
|
||||
"jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0",
|
||||
"jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0",
|
||||
"jsr:@std/assert@^0.213.1": "jsr:@std/assert@0.213.1",
|
||||
"jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0",
|
||||
"jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0",
|
||||
"jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0",
|
||||
|
|
@ -30,17 +33,22 @@
|
|||
"jsr:@std/bytes@^1.0.2-rc.3": "jsr:@std/bytes@1.0.2",
|
||||
"jsr:@std/crypto@^0.224.0": "jsr:@std/crypto@0.224.0",
|
||||
"jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.2",
|
||||
"jsr:@std/encoding@0.213.1": "jsr:@std/encoding@0.213.1",
|
||||
"jsr:@std/encoding@^0.221.0": "jsr:@std/encoding@0.221.0",
|
||||
"jsr:@std/encoding@^0.224.0": "jsr:@std/encoding@0.224.3",
|
||||
"jsr:@std/encoding@^0.224.1": "jsr:@std/encoding@0.224.3",
|
||||
"jsr:@std/fmt@0.213.1": "jsr:@std/fmt@0.213.1",
|
||||
"jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0",
|
||||
"jsr:@std/fs@0.213.1": "jsr:@std/fs@0.213.1",
|
||||
"jsr:@std/fs@^0.221.0": "jsr:@std/fs@0.221.0",
|
||||
"jsr:@std/fs@^0.229.3": "jsr:@std/fs@0.229.3",
|
||||
"jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.1",
|
||||
"jsr:@std/io@^0.224": "jsr:@std/io@0.224.4",
|
||||
"jsr:@std/json@^0.223.0": "jsr:@std/json@0.223.0",
|
||||
"jsr:@std/media-types@^0.224.1": "jsr:@std/media-types@0.224.1",
|
||||
"jsr:@std/path@0.213.1": "jsr:@std/path@0.213.1",
|
||||
"jsr:@std/path@0.217": "jsr:@std/path@0.217.0",
|
||||
"jsr:@std/path@^0.213.1": "jsr:@std/path@0.213.1",
|
||||
"jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0",
|
||||
"jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0",
|
||||
"npm:@isaacs/ttlcache@^1.4.1": "npm:@isaacs/ttlcache@1.4.1",
|
||||
|
|
@ -84,6 +92,12 @@
|
|||
"npm:zod@^3.23.8": "npm:zod@3.23.8"
|
||||
},
|
||||
"jsr": {
|
||||
"@b-fuze/deno-dom@0.1.47": {
|
||||
"integrity": "270a888de91329f8ce3849211ece0ad97ce1e8b9a8a774f2bed2f43c8b0ffe8e",
|
||||
"dependencies": [
|
||||
"jsr:@denosaurs/plug@1.0.3"
|
||||
]
|
||||
},
|
||||
"@bradenmacdonald/s3-lite-client@0.7.6": {
|
||||
"integrity": "2b5976dca95d207dc88e23f9807e3eecbc441b0cf547dcda5784afe6668404d1",
|
||||
"dependencies": [
|
||||
|
|
@ -97,6 +111,15 @@
|
|||
"jsr:@std/path@0.217"
|
||||
]
|
||||
},
|
||||
"@denosaurs/plug@1.0.3": {
|
||||
"integrity": "b010544e386bea0ff3a1d05e0c88f704ea28cbd4d753439c2f1ee021a85d4640",
|
||||
"dependencies": [
|
||||
"jsr:@std/encoding@0.213.1",
|
||||
"jsr:@std/fmt@0.213.1",
|
||||
"jsr:@std/fs@0.213.1",
|
||||
"jsr:@std/path@0.213.1"
|
||||
]
|
||||
},
|
||||
"@denosaurs/plug@1.0.6": {
|
||||
"integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7",
|
||||
"dependencies": [
|
||||
|
|
@ -216,6 +239,9 @@
|
|||
"@soapbox/stickynotes@0.4.0": {
|
||||
"integrity": "60bfe61ab3d7e04bf708273b1e2d391a59534bdf29e54160e98d7afd328ca1ec"
|
||||
},
|
||||
"@std/assert@0.213.1": {
|
||||
"integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe"
|
||||
},
|
||||
"@std/assert@0.217.0": {
|
||||
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
|
||||
},
|
||||
|
|
@ -253,15 +279,28 @@
|
|||
"@std/dotenv@0.224.2": {
|
||||
"integrity": "29081695357e4534696c9e986b2560be29c141ccf52daa32b6c20ff5b5c64ab9"
|
||||
},
|
||||
"@std/encoding@0.213.1": {
|
||||
"integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62"
|
||||
},
|
||||
"@std/encoding@0.221.0": {
|
||||
"integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45"
|
||||
},
|
||||
"@std/encoding@0.224.3": {
|
||||
"integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf"
|
||||
},
|
||||
"@std/fmt@0.213.1": {
|
||||
"integrity": "a06d31777566d874b9c856c10244ac3e6b660bdec4c82506cd46be052a1082c3"
|
||||
},
|
||||
"@std/fmt@0.221.0": {
|
||||
"integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a"
|
||||
},
|
||||
"@std/fs@0.213.1": {
|
||||
"integrity": "fbcaf099f8a85c27ab0712b666262cda8fe6d02e9937bf9313ecaea39a22c501",
|
||||
"dependencies": [
|
||||
"jsr:@std/assert@^0.213.1",
|
||||
"jsr:@std/path@^0.213.1"
|
||||
]
|
||||
},
|
||||
"@std/fs@0.221.0": {
|
||||
"integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286",
|
||||
"dependencies": [
|
||||
|
|
@ -308,6 +347,12 @@
|
|||
"@std/media-types@0.224.1": {
|
||||
"integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1"
|
||||
},
|
||||
"@std/path@0.213.1": {
|
||||
"integrity": "f187bf278a172752e02fcbacf6bd78a335ed320d080a7ed3a5a59c3e88abc673",
|
||||
"dependencies": [
|
||||
"jsr:@std/assert@^0.213.1"
|
||||
]
|
||||
},
|
||||
"@std/path@0.217.0": {
|
||||
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
|
||||
"dependencies": [
|
||||
|
|
@ -1836,6 +1881,7 @@
|
|||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@b-fuze/deno-dom@^0.1.47",
|
||||
"jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
||||
"jsr:@db/sqlite@^0.11.1",
|
||||
"jsr:@hono/hono@^4.4.6",
|
||||
|
|
|
|||
43
src/utils/favicon.ts
Normal file
43
src/utils/favicon.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { DOMParser } from '@b-fuze/deno-dom/native';
|
||||
import Debug from '@soapbox/stickynotes/debug';
|
||||
import tldts from 'tldts';
|
||||
|
||||
import { SimpleLRU } from '@/utils/SimpleLRU.ts';
|
||||
import { Time } from '@/utils/time.ts';
|
||||
import { fetchWorker } from '@/workers/fetch.ts';
|
||||
|
||||
const debug = Debug('ditto:favicon');
|
||||
|
||||
const faviconCache = new SimpleLRU<string, URL>(
|
||||
async (key, { signal }) => {
|
||||
debug(`Fetching favicon ${key}`);
|
||||
const tld = tldts.parse(key);
|
||||
|
||||
if (!tld.isIcann || tld.isIp || tld.isPrivate) {
|
||||
throw new Error(`Invalid favicon domain: ${key}`);
|
||||
}
|
||||
|
||||
const rootUrl = new URL('/', `https://${key}/`);
|
||||
const response = await fetchWorker(rootUrl, { signal });
|
||||
const html = await response.text();
|
||||
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
const link = doc.querySelector('link[rel="icon"], link[rel="shortcut icon"]');
|
||||
|
||||
if (link) {
|
||||
const href = link.getAttribute('href');
|
||||
if (href) {
|
||||
try {
|
||||
return new URL(href);
|
||||
} catch {
|
||||
return new URL(href, rootUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Favicon not found: ${key}`);
|
||||
},
|
||||
{ max: 500, ttl: Time.hours(1) },
|
||||
);
|
||||
|
||||
export { faviconCache };
|
||||
|
|
@ -8,6 +8,7 @@ import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
|||
import { getLnurl } from '@/utils/lnurl.ts';
|
||||
import { parseAndVerifyNip05 } from '@/utils/nip05.ts';
|
||||
import { getTagSet } from '@/utils/tags.ts';
|
||||
import { faviconCache } from '@/utils/favicon.ts';
|
||||
import { nostrDate, nostrNow } from '@/utils.ts';
|
||||
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
||||
|
||||
|
|
@ -44,6 +45,15 @@ async function renderAccount(
|
|||
const parsed05 = await parseAndVerifyNip05(nip05, pubkey);
|
||||
const acct = parsed05?.handle || npub;
|
||||
|
||||
let favicon: URL | undefined;
|
||||
if (parsed05?.domain) {
|
||||
try {
|
||||
favicon = await faviconCache.fetch(parsed05.domain);
|
||||
} catch {
|
||||
favicon = new URL('/favicon.ico', `https://${parsed05.domain}/`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: pubkey,
|
||||
acct,
|
||||
|
|
@ -95,7 +105,7 @@ async function renderAccount(
|
|||
is_local: parsed05?.domain === Conf.url.host,
|
||||
settings_store: undefined as unknown,
|
||||
tags: [...getTagSet(event.user?.tags ?? [], 't')],
|
||||
favicon: parsed05?.domain ? new URL('/favicon.ico', `https://${parsed05.domain}`).toString() : undefined,
|
||||
favicon: favicon?.toString(),
|
||||
},
|
||||
nostr: {
|
||||
pubkey,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue