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"],
|
"exclude": ["./public"],
|
||||||
"imports": {
|
"imports": {
|
||||||
"@/": "./src/",
|
"@/": "./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",
|
"@bradenmacdonald/s3-lite-client": "jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
||||||
"@db/sqlite": "jsr:@db/sqlite@^0.11.1",
|
"@db/sqlite": "jsr:@db/sqlite@^0.11.1",
|
||||||
"@hono/hono": "jsr:@hono/hono@^4.4.6",
|
"@hono/hono": "jsr:@hono/hono@^4.4.6",
|
||||||
|
|
|
||||||
46
deno.lock
generated
46
deno.lock
generated
|
|
@ -2,9 +2,11 @@
|
||||||
"version": "3",
|
"version": "3",
|
||||||
"packages": {
|
"packages": {
|
||||||
"specifiers": {
|
"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:@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:@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": "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": "jsr:@gleasonator/policy@0.2.0",
|
||||||
"jsr:@gleasonator/policy@0.2.0": "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",
|
"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:@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/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:@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.217.0": "jsr:@std/assert@0.217.0",
|
||||||
"jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.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",
|
"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/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/crypto@^0.224.0": "jsr:@std/crypto@0.224.0",
|
||||||
"jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.2",
|
"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.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.0": "jsr:@std/encoding@0.224.3",
|
||||||
"jsr:@std/encoding@^0.224.1": "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/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.221.0": "jsr:@std/fs@0.221.0",
|
||||||
"jsr:@std/fs@^0.229.3": "jsr:@std/fs@0.229.3",
|
"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/internal@^1.0.0": "jsr:@std/internal@1.0.1",
|
||||||
"jsr:@std/io@^0.224": "jsr:@std/io@0.224.4",
|
"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/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/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.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/path@^0.221.0": "jsr:@std/path@0.221.0",
|
||||||
"jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.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",
|
"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"
|
"npm:zod@^3.23.8": "npm:zod@3.23.8"
|
||||||
},
|
},
|
||||||
"jsr": {
|
"jsr": {
|
||||||
|
"@b-fuze/deno-dom@0.1.47": {
|
||||||
|
"integrity": "270a888de91329f8ce3849211ece0ad97ce1e8b9a8a774f2bed2f43c8b0ffe8e",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@denosaurs/plug@1.0.3"
|
||||||
|
]
|
||||||
|
},
|
||||||
"@bradenmacdonald/s3-lite-client@0.7.6": {
|
"@bradenmacdonald/s3-lite-client@0.7.6": {
|
||||||
"integrity": "2b5976dca95d207dc88e23f9807e3eecbc441b0cf547dcda5784afe6668404d1",
|
"integrity": "2b5976dca95d207dc88e23f9807e3eecbc441b0cf547dcda5784afe6668404d1",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -97,6 +111,15 @@
|
||||||
"jsr:@std/path@0.217"
|
"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": {
|
"@denosaurs/plug@1.0.6": {
|
||||||
"integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7",
|
"integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -216,6 +239,9 @@
|
||||||
"@soapbox/stickynotes@0.4.0": {
|
"@soapbox/stickynotes@0.4.0": {
|
||||||
"integrity": "60bfe61ab3d7e04bf708273b1e2d391a59534bdf29e54160e98d7afd328ca1ec"
|
"integrity": "60bfe61ab3d7e04bf708273b1e2d391a59534bdf29e54160e98d7afd328ca1ec"
|
||||||
},
|
},
|
||||||
|
"@std/assert@0.213.1": {
|
||||||
|
"integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe"
|
||||||
|
},
|
||||||
"@std/assert@0.217.0": {
|
"@std/assert@0.217.0": {
|
||||||
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
|
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
|
||||||
},
|
},
|
||||||
|
|
@ -253,15 +279,28 @@
|
||||||
"@std/dotenv@0.224.2": {
|
"@std/dotenv@0.224.2": {
|
||||||
"integrity": "29081695357e4534696c9e986b2560be29c141ccf52daa32b6c20ff5b5c64ab9"
|
"integrity": "29081695357e4534696c9e986b2560be29c141ccf52daa32b6c20ff5b5c64ab9"
|
||||||
},
|
},
|
||||||
|
"@std/encoding@0.213.1": {
|
||||||
|
"integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62"
|
||||||
|
},
|
||||||
"@std/encoding@0.221.0": {
|
"@std/encoding@0.221.0": {
|
||||||
"integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45"
|
"integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45"
|
||||||
},
|
},
|
||||||
"@std/encoding@0.224.3": {
|
"@std/encoding@0.224.3": {
|
||||||
"integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf"
|
"integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf"
|
||||||
},
|
},
|
||||||
|
"@std/fmt@0.213.1": {
|
||||||
|
"integrity": "a06d31777566d874b9c856c10244ac3e6b660bdec4c82506cd46be052a1082c3"
|
||||||
|
},
|
||||||
"@std/fmt@0.221.0": {
|
"@std/fmt@0.221.0": {
|
||||||
"integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a"
|
"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": {
|
"@std/fs@0.221.0": {
|
||||||
"integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286",
|
"integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -308,6 +347,12 @@
|
||||||
"@std/media-types@0.224.1": {
|
"@std/media-types@0.224.1": {
|
||||||
"integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1"
|
"integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1"
|
||||||
},
|
},
|
||||||
|
"@std/path@0.213.1": {
|
||||||
|
"integrity": "f187bf278a172752e02fcbacf6bd78a335ed320d080a7ed3a5a59c3e88abc673",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/assert@^0.213.1"
|
||||||
|
]
|
||||||
|
},
|
||||||
"@std/path@0.217.0": {
|
"@std/path@0.217.0": {
|
||||||
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
|
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -1836,6 +1881,7 @@
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
"jsr:@b-fuze/deno-dom@^0.1.47",
|
||||||
"jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
"jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
||||||
"jsr:@db/sqlite@^0.11.1",
|
"jsr:@db/sqlite@^0.11.1",
|
||||||
"jsr:@hono/hono@^4.4.6",
|
"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 { getLnurl } from '@/utils/lnurl.ts';
|
||||||
import { parseAndVerifyNip05 } from '@/utils/nip05.ts';
|
import { parseAndVerifyNip05 } from '@/utils/nip05.ts';
|
||||||
import { getTagSet } from '@/utils/tags.ts';
|
import { getTagSet } from '@/utils/tags.ts';
|
||||||
|
import { faviconCache } from '@/utils/favicon.ts';
|
||||||
import { nostrDate, nostrNow } from '@/utils.ts';
|
import { nostrDate, nostrNow } from '@/utils.ts';
|
||||||
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
||||||
|
|
||||||
|
|
@ -44,6 +45,15 @@ async function renderAccount(
|
||||||
const parsed05 = await parseAndVerifyNip05(nip05, pubkey);
|
const parsed05 = await parseAndVerifyNip05(nip05, pubkey);
|
||||||
const acct = parsed05?.handle || npub;
|
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 {
|
return {
|
||||||
id: pubkey,
|
id: pubkey,
|
||||||
acct,
|
acct,
|
||||||
|
|
@ -95,7 +105,7 @@ async function renderAccount(
|
||||||
is_local: parsed05?.domain === Conf.url.host,
|
is_local: parsed05?.domain === Conf.url.host,
|
||||||
settings_store: undefined as unknown,
|
settings_store: undefined as unknown,
|
||||||
tags: [...getTagSet(event.user?.tags ?? [], 't')],
|
tags: [...getTagSet(event.user?.tags ?? [], 't')],
|
||||||
favicon: parsed05?.domain ? new URL('/favicon.ico', `https://${parsed05.domain}`).toString() : undefined,
|
favicon: favicon?.toString(),
|
||||||
},
|
},
|
||||||
nostr: {
|
nostr: {
|
||||||
pubkey,
|
pubkey,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue