diff --git a/docs/DEPLOIEMENT-SVELTEKIT.md b/docs/DEPLOIEMENT-SVELTEKIT.md index 614eca5..5efdf7a 100644 --- a/docs/DEPLOIEMENT-SVELTEKIT.md +++ b/docs/DEPLOIEMENT-SVELTEKIT.md @@ -6,7 +6,9 @@ Branche `svelte` : le hub est un site **100 % statique** (SvelteKit + adapter-st ```bash npm ci -npm run build # génère build/ + injecte la CSP par page (scripts/postbuild-csp.mjs) +npm run build # prebuild : optimise les pochettes Castopod (scripts/fetch-podcast-cover.mjs, + # nécessite ImageMagick) et les vignettes/médias distants (scripts/optimize-remote-images.mjs) + # puis génère build/ + injecte la CSP par page (scripts/postbuild-csp.mjs) npm run preview # vérification locale ``` diff --git a/package-lock.json b/package-lock.json index 75fba85..7a4e415 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.63.0", "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@types/node": "^26.1.1", "svelte": "^5.56.1", "svelte-check": "^4.6.0", "typescript": "^6.0.3", @@ -2614,6 +2615,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -5986,6 +5997,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", diff --git a/package.json b/package.json index ee52076..82bf368 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,14 @@ "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "postbuild": "node scripts/postbuild-csp.mjs" + "postbuild": "node scripts/postbuild-csp.mjs", + "prebuild": "node scripts/fetch-podcast-cover.mjs && node scripts/optimize-remote-images.mjs" }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.63.0", "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@types/node": "^26.1.1", "svelte": "^5.56.1", "svelte-check": "^4.6.0", "typescript": "^6.0.3", diff --git a/scripts/fetch-podcast-cover.mjs b/scripts/fetch-podcast-cover.mjs new file mode 100644 index 0000000..ef9bacc --- /dev/null +++ b/scripts/fetch-podcast-cover.mjs @@ -0,0 +1,51 @@ +import { writeFileSync, statSync, mkdirSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { join, dirname, basename } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const FEED_URL = 'https://kute.o-k-i.net/@annu_kute_cedric/feed'; +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const EPISODES_DIR = join(ROOT, 'static', 'images', 'episodes'); +const CHANNEL_COVER = join(ROOT, 'static', 'images', 'podcast-cover.webp'); + +mkdirSync(EPISODES_DIR, { recursive: true }); + +const xml = await (await fetch(FEED_URL)).text(); + +const urls = new Set(); +const channel = xml.split(']*href="([^"]+)"/i) ?? channel.match(/([^<]+)<\/url>/i); +if (channelMatch) urls.add(channelMatch[1]); + +for (const item of xml.split(/]/i).slice(1)) { + const match = item.match(/]*href="([^"]+)"/i); + if (match) urls.add(match[1]); +} + +let total = 0; + +for (const url of urls) { + const isChannel = url === channelMatch?.[1]; + const out = isChannel + ? CHANNEL_COVER + : join(EPISODES_DIR, basename(url).replace(/\.(png|jpe?g|gif|webp)$/i, '.webp')); + try { + const res = await fetch(url); + if (!res.ok) { + console.warn(`[covers] HTTP ${res.status} pour ${url}`); + continue; + } + const source = Buffer.from(await res.arrayBuffer()); + const tmp = `/tmp/opencode/cover-${Date.now()}`; + writeFileSync(tmp, source); + execFileSync('convert', [tmp, '-resize', '512x512>', '-quality', '82', '-define', 'webp:method=6', out]); + const size = statSync(out).size; + total += size; + console.log(`[covers] ${basename(out)} : ${Math.round(source.length / 1024)} Ko → ${Math.round(size / 1024)} Ko`); + } catch (err) { + console.warn(`[covers] échec pour ${url}:`, err.message); + } +} + +console.log(`[covers] total optimisé : ${Math.round(total / 1024)} Ko`); diff --git a/scripts/optimize-remote-images.mjs b/scripts/optimize-remote-images.mjs new file mode 100644 index 0000000..289484c --- /dev/null +++ b/scripts/optimize-remote-images.mjs @@ -0,0 +1,112 @@ +import { createHash } from 'node:crypto'; +import { mkdirSync, writeFileSync, statSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const OUT_DIR = join(ROOT, 'static', 'images', 'remote'); +const MAP_FILE = join(ROOT, 'src', 'lib', 'server', 'image-map.json'); + +const PEERTUBE = 'https://gade.o-k-i.net'; +const MASTODON = 'https://bokante.o-k-i.net'; +const MASTODON_ACCT = 'cedric'; +const PRIORITY_CATEGORIES = [11, 15, 4, 9, 10]; + +mkdirSync(OUT_DIR, { recursive: true }); + +const urls = new Map(); + +function localPathFor(remoteUrl, width) { + const hash = createHash('sha1').update(remoteUrl).digest('hex').slice(0, 16); + return { local: `/images/remote/${hash}.webp`, width }; +} + +async function collectVideos(params, width) { + const query = new URLSearchParams({ isLocal: 'true', ...params }); + try { + const res = await fetch(`${PEERTUBE}/api/v1/videos?${query}`); + if (!res.ok) return; + const data = await res.json(); + for (const video of data?.data ?? []) { + if (video.previewPath) { + const url = `${PEERTUBE}${video.previewPath}`; + if (!urls.has(url)) urls.set(url, localPathFor(url, width)); + } + const avatar = video.channel?.avatars?.[0]?.path; + if (avatar) { + const url = `${PEERTUBE}${avatar}`; + if (!urls.has(url)) urls.set(url, localPathFor(url, 96)); + } + } + } catch (err) { + console.warn('[images] collectVideos échoué:', err.message); + } +} + +// Pool récent (couvre récentes, shorts et l'essentiel des catégories) + tendances + chaque catégorie prioritaire +await collectVideos({ sort: '-publishedAt', count: '100' }, 640); +await collectVideos({ sort: '-trending', count: '6' }, 640); +for (const id of PRIORITY_CATEGORIES) { + await collectVideos({ categoryOneOf: String(id), sort: '-publishedAt', count: '6' }, 640); +} + +// Médias de la timeline Mastodon +try { + const account = await ( + await fetch(`${MASTODON}/api/v1/accounts/lookup?acct=${encodeURIComponent(MASTODON_ACCT)}`) + ).json(); + if (account?.id) { + const statuses = await ( + await fetch(`${MASTODON}/api/v1/accounts/${account.id}/statuses?limit=10&exclude_replies=true`) + ).json(); + for (const status of statuses ?? []) { + const source = status.reblog ?? status; + for (const media of source.media_attachments ?? []) { + const url = media.preview_url ?? media.url; + if (url && !urls.has(url)) urls.set(url, localPathFor(url, 800)); + } + } + } +} catch (err) { + console.warn('[images] médias Mastodon échoués:', err.message); +} + +console.log(`[images] ${urls.size} image(s) distante(s) à optimiser`); + +const map = {}; +let total = 0; +let done = 0; + +for (const [remote, { local, width }] of urls) { + const out = join(OUT_DIR, local.split('/').pop()); + try { + const res = await fetch(remote); + if (!res.ok) { + console.warn(`[images] HTTP ${res.status} pour ${remote}`); + continue; + } + const source = Buffer.from(await res.arrayBuffer()); + const tmp = `/tmp/opencode/img-${done}`; + writeFileSync(tmp, source); + execFileSync('convert', [ + tmp, + '-auto-orient', + '-resize', + `${width}x${width}>`, + '-quality', + '80', + '-define', + 'webp:method=6', + out + ]); + map[remote] = local; + total += statSync(out).size; + done++; + } catch (err) { + console.warn(`[images] échec pour ${remote}:`, err.message); + } +} + +writeFileSync(MAP_FILE, JSON.stringify(map, null, '\t') + '\n'); +console.log(`[images] ${done} optimisée(s), ${Math.round(total / 1024)} Ko au total`); diff --git a/src/lib/components/Footer.svelte b/src/lib/components/Footer.svelte index 48fcd2c..edda692 100644 --- a/src/lib/components/Footer.svelte +++ b/src/lib/components/Footer.svelte @@ -231,5 +231,7 @@ .footer-federated a, .footer-copyright a { color: var(--accent); + text-decoration: underline; + text-underline-offset: 0.15em; } diff --git a/src/lib/components/MastodonTimeline.svelte b/src/lib/components/MastodonTimeline.svelte index f9a9bca..6bef71b 100644 --- a/src/lib/components/MastodonTimeline.svelte +++ b/src/lib/components/MastodonTimeline.svelte @@ -79,6 +79,7 @@ .post-content { font-size: 0.95rem; + line-height: 1.9; overflow-wrap: anywhere; } @@ -86,6 +87,8 @@ color: var(--accent); text-decoration: underline; text-underline-offset: 0.15em; + display: inline-block; + padding: 0.35rem 0.15rem; } .post-media { diff --git a/src/lib/server/castopod.ts b/src/lib/server/castopod.ts index f648bae..765307e 100644 --- a/src/lib/server/castopod.ts +++ b/src/lib/server/castopod.ts @@ -1,7 +1,20 @@ +import { existsSync } from 'node:fs'; import { castopod } from '$lib/config'; import { fetchText } from './http'; import type { Episode } from '$lib/types'; +// Pochette du flux optimisée au prebuild (scripts/fetch-podcast-cover.mjs) +const LOCAL_COVER = '/images/podcast-cover.webp'; +const hasLocalCover = existsSync(`static${LOCAL_COVER}`); + +function localEpisodeCover(remoteUrl: string | null): string | null { + if (!remoteUrl) return null; + const file = remoteUrl.split('/').pop()?.replace(/\.(png|jpe?g|gif|webp)$/i, '.webp'); + if (!file) return remoteUrl; + const local = `/images/episodes/${file}`; + return existsSync(`static${local}`) ? local : remoteUrl; +} + function tag(xml: string, name: string): string { const cdata = new RegExp(`<${name}[^>]*>`, 'i'); const plain = new RegExp(`<${name}[^>]*>([\\s\\S]*?)`, 'i'); @@ -53,6 +66,10 @@ async function getEpisodesForSlug(slug: string): Promise { const link = tag(body, 'link'); const guid = tag(body, 'guid') || link || `${slug}-${index}`; const description = stripTags(tag(body, 'description') || tag(body, 'itunes:summary')); + const remoteImage = attr(body, 'itunes:image', 'href') || channelImage || null; + // Les pochettes du flux (souvent lourdes) sont remplacées par leurs versions optimisées au build + const image = + hasLocalCover && remoteImage === channelImage ? LOCAL_COVER : localEpisodeCover(remoteImage); return { id: guid, title, @@ -61,7 +78,7 @@ async function getEpisodesForSlug(slug: string): Promise { audioType: attr(body, 'enclosure', 'type') || 'audio/mpeg', duration: parseDuration(tag(body, 'itunes:duration')), date: tag(body, 'pubDate'), - image: attr(body, 'itunes:image', 'href') || channelImage || null, + image, link, podcast: slug }; diff --git a/src/lib/server/image-map.json b/src/lib/server/image-map.json new file mode 100644 index 0000000..92e663d --- /dev/null +++ b/src/lib/server/image-map.json @@ -0,0 +1,61 @@ +{ + "https://gade.o-k-i.net/lazy-static/thumbnails/ee8fe93e-62b0-45a3-b200-3b9a7d34ad37.jpg": "/images/remote/8d484efeec42b22e.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/713279b3-0a30-4e88-a4cd-2bd9015706ff.jpg": "/images/remote/2b57d5382f67e8a6.webp", + "https://gade.o-k-i.net/lazy-static/avatars/b483874d-df89-4403-aabb-684f4eeffce8.png": "/images/remote/b82953cc708a503e.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/65458991-cc2d-4f88-b04b-1ecb4c831196.jpg": "/images/remote/1e1a6bad9723ecba.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/19ed28dd-429b-4b86-9f5b-032b0fd8a555.jpg": "/images/remote/a105bc4c5812eb6d.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/a02a736a-1ca2-48cb-bd04-6adb8ab8bd3f.jpg": "/images/remote/7e49bf87876caf2b.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/df2d06a4-c5f1-40b2-85c6-bb2cc6f75f0f.jpg": "/images/remote/23a4aee1ddac377d.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/ac2d53df-9432-419a-bafa-4e92a23a065a.png": "/images/remote/ca91ce3a8c6c048c.webp", + "https://gade.o-k-i.net/lazy-static/avatars/1af26d32-dd03-4ef9-9cb7-e4680630b93a.png": "/images/remote/d3efbaf0d582a4de.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/7a40594d-6ee8-4a93-a891-acf6a06706e5.jpg": "/images/remote/d668c254603561d5.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/c433fb22-085b-4b95-840d-a244a16b9d86.jpg": "/images/remote/7f3ee600df8bf0a9.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/eeee79a2-13a7-41f3-8cf7-c0356e8b4af1.jpg": "/images/remote/8b91a7098be22de6.webp", + "https://gade.o-k-i.net/lazy-static/avatars/68710d9c-f623-4db3-950c-5dcb9519ea89.png": "/images/remote/72f6f94d5459b36c.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/aa49c8f7-e746-4087-ac8a-b18911ae34bb.jpg": "/images/remote/953bc828f4a909ed.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/04471380-b2fc-410d-a255-d1ed2c566582.jpg": "/images/remote/dd3dfb419e66a44c.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/f9dcc845-f6dc-4383-abac-192a74d3ddbb.jpg": "/images/remote/2cd6b3491e675fe9.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/ee9d0e2b-a266-4e5e-b708-f8a68dd38cd8.jpg": "/images/remote/7794b4aa91e0886e.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/63aab582-1e95-403f-8fd4-2cd1355e4c48.jpg": "/images/remote/06c7423ee81dbae1.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/f06d5990-2113-428d-8364-cedc2dd5163f.jpg": "/images/remote/38183e7822943589.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/e034ba48-4696-4747-8090-36a1ffb7ead8.jpg": "/images/remote/83b50cf812d906f0.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/8a1726da-b05c-4f4c-9d02-a84aa80089cb.jpg": "/images/remote/ce0279994a8d0c22.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/eae89876-94f3-4c32-b5bb-d1cd44551ca5.jpg": "/images/remote/626f2aa1c4d7030a.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/82061a8c-4435-4759-b905-5c8a10eb8a8f.jpg": "/images/remote/efa5aca9038f196e.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/b1472219-de2b-4a85-8f95-bd544eaf5797.jpg": "/images/remote/940f43beb51345fa.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/28acfa31-e05d-49c6-89b0-2da42502416e.jpg": "/images/remote/64ba2429385bdd5f.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/9b9fde1a-be2f-4c7c-8372-f3d65aad73ac.jpg": "/images/remote/674d7ec610419571.webp", + "https://gade.o-k-i.net/lazy-static/avatars/35d753a1-cd9f-4f64-999f-483ef4c8d0f6.jpg": "/images/remote/ff2cc37374f4a362.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/730fcddd-645e-4377-9b93-f38835818b7d.jpg": "/images/remote/a7ced37c3c927888.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/66cf4194-3e6c-4c09-adb1-b3ae6591c6a0.jpg": "/images/remote/4ebf139c7ad742c0.webp", + "https://gade.o-k-i.net/lazy-static/avatars/bcdc941d-1be8-4c67-b0c7-d40313ef9aaf.png": "/images/remote/5a4d501bd6d6088c.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/14fa6efc-9a9a-4c36-a044-02300a92af47.jpg": "/images/remote/461e77261af8212f.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/7b7768e4-eeac-4024-a59e-052bdfbefe25.jpg": "/images/remote/a652b99264ef113c.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/f6730ffc-8236-4df3-83f9-bce65b1c5973.jpg": "/images/remote/cc2a19d9f6ac44b1.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/9fe95de1-ea17-41f9-ae02-d4b93f33f3e9.jpg": "/images/remote/e0d61d23865f0262.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/a1799f29-ffd4-4c9f-a4a6-7cdfe56cd48c.png": "/images/remote/880d1ba7cf4187a5.webp", + "https://gade.o-k-i.net/lazy-static/avatars/df91692b-df37-4350-a5e4-3b9a1f56af8e.png": "/images/remote/035f2b8e8f5ee0dd.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/ad988f0b-1fd7-46bc-93b2-0d8b9587b880.jpg": "/images/remote/2713aa6bd6511e7e.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/f60a9ca6-7a62-44c2-a242-016fdc530577.jpg": "/images/remote/67b3bca007b144c5.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/3f130e71-7cfd-473b-8ea2-80df832c1241.jpg": "/images/remote/b9e62fd5fa9ce102.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/61f25602-6dd7-4fe1-9269-af0fe3f73527.jpg": "/images/remote/e7c1aec80a4fc8b6.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/23fdd0ad-8256-45b2-b8fa-15496fef9d3f.jpg": "/images/remote/d9d7e08baf18edfe.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/ce2c5680-a787-4028-8768-ca64e8c18fba.jpg": "/images/remote/11f1ace7f75ed731.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/45e971a5-3131-46bc-8a39-db10d68dbd29.jpg": "/images/remote/09eb4898f46c76db.webp", + "https://gade.o-k-i.net/lazy-static/avatars/60cbad62-3881-4e34-aa3a-726b80b5d334.png": "/images/remote/62c8fe68d5ea8108.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/bb2aafc3-46bd-4059-a3a7-cd08d4097113.jpg": "/images/remote/4fc5ba257091f2f6.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/160eda0d-326c-480b-81e4-aabe88b46729.jpg": "/images/remote/8ab957ee76be64c8.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/d00d12e6-c5c8-4df8-b0fd-5a49f9675bd4.jpg": "/images/remote/61d4b3032d0c87dd.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/3521d0ed-ae0d-4610-88ff-96c4703d8849.jpg": "/images/remote/d47f4bcf0566064e.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/05e25c9a-ef93-4ec4-b7a9-5453745d7af5.jpg": "/images/remote/4ceaa3d9b7bf41aa.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/32220182-8186-4e66-a2d6-03c722fe13af.jpg": "/images/remote/254e260419aa661e.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/5e7b2bd0-edfe-49ca-abee-fbe21321c3d0.jpg": "/images/remote/57e3480631c3524f.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/cbe90101-8276-4a39-bce0-4e3b5ade98a7.jpg": "/images/remote/1f6ca697aa450bf5.webp", + "https://gade.o-k-i.net/lazy-static/thumbnails/6ee72db1-d86c-4e6c-8deb-de1672ef40f5.png": "/images/remote/16b11ca906d64dcf.webp", + "https://bokante.o-k-i.net/system/media_attachments/files/116/961/470/179/848/592/small/a1f65d6923b825c6.png": "/images/remote/f0e99c1fd601e8f4.webp", + "https://bokante.o-k-i.net/system/media_attachments/files/116/961/462/013/833/918/small/ab87d35ebbc5b3ff.jpg": "/images/remote/b0f9e20257ed9d70.webp", + "https://bokante.o-k-i.net/system/media_attachments/files/116/961/174/942/983/872/small/9a39261de8c0a287.png": "/images/remote/ac55ca23fae564d8.webp", + "https://bokante.o-k-i.net/system/media_attachments/files/116/944/486/745/892/539/small/fce48c574fa53917.png": "/images/remote/69a1a813f6b9324e.webp", + "https://bokante.o-k-i.net/system/media_attachments/files/116/943/727/051/792/872/small/d7c8bcea86822972.png": "/images/remote/656fc21dfbab689a.webp", + "https://bokante.o-k-i.net/system/media_attachments/files/116/936/109/649/552/039/small/c80ad03182b67097.png": "/images/remote/e925811cec427259.webp" +} diff --git a/src/lib/server/image-map.ts b/src/lib/server/image-map.ts new file mode 100644 index 0000000..f6d0eba --- /dev/null +++ b/src/lib/server/image-map.ts @@ -0,0 +1,10 @@ +import { existsSync } from 'node:fs'; +import map from './image-map.json' with { type: 'json' }; + +const imageMap = map as Record; + +export function mapImage(remoteUrl: string): string { + const local = imageMap[remoteUrl]; + if (local && existsSync(`static${local}`)) return local; + return remoteUrl; +} diff --git a/src/lib/server/mastodon.ts b/src/lib/server/mastodon.ts index 1a25070..3ea874f 100644 --- a/src/lib/server/mastodon.ts +++ b/src/lib/server/mastodon.ts @@ -1,5 +1,6 @@ import { mastodon } from '$lib/config'; import { fetchJson, sanitizeHtml } from './http'; +import { mapImage } from './image-map'; import type { Post } from '$lib/types'; interface MastodonMedia { @@ -46,7 +47,7 @@ export async function getMastodonPosts(count = mastodon.maxPosts): Promise media.url) .map((media) => ({ url: media.url as string, - previewUrl: media.preview_url ?? null, + previewUrl: media.preview_url ? mapImage(media.preview_url) : mapImage(media.url as string), description: media.description ?? '' })), repliesCount: status.replies_count ?? 0, diff --git a/src/lib/server/peertube.ts b/src/lib/server/peertube.ts index 7eb243d..b2abe66 100644 --- a/src/lib/server/peertube.ts +++ b/src/lib/server/peertube.ts @@ -1,5 +1,6 @@ import { peertube } from '$lib/config'; import { fetchJson, markdownToHtml, sanitizeHtml } from './http'; +import { mapImage } from './image-map'; import type { DownloadOption, SearchEntry, @@ -44,11 +45,11 @@ function formatVideos(videos: PtVideo[] = []): VideoSummary[] { return videos.map((video) => ({ id: video.uuid, title: video.name, - thumbnail: video.previewPath ? `${BASE}${video.previewPath}` : '/images/logo.png', + thumbnail: video.previewPath ? mapImage(`${BASE}${video.previewPath}`) : '/images/logo.png', duration: video.duration, channel: video.channel?.displayName ?? '', channelAvatar: video.channel?.avatars?.[0]?.path - ? `${BASE}${video.channel.avatars[0].path}` + ? mapImage(`${BASE}${video.channel.avatars[0].path}`) : '/images/logo.png', views: video.views, date: video.publishedAt, diff --git a/static/images/episodes/annu-kute-ced-s01-e01_feed.webp b/static/images/episodes/annu-kute-ced-s01-e01_feed.webp new file mode 100644 index 0000000..bdef938 Binary files /dev/null and b/static/images/episodes/annu-kute-ced-s01-e01_feed.webp differ diff --git a/static/images/podcast-cover.webp b/static/images/podcast-cover.webp new file mode 100644 index 0000000..51e5e0e Binary files /dev/null and b/static/images/podcast-cover.webp differ diff --git a/static/images/remote/035f2b8e8f5ee0dd.webp b/static/images/remote/035f2b8e8f5ee0dd.webp new file mode 100644 index 0000000..5d2ea20 Binary files /dev/null and b/static/images/remote/035f2b8e8f5ee0dd.webp differ diff --git a/static/images/remote/06c7423ee81dbae1.webp b/static/images/remote/06c7423ee81dbae1.webp new file mode 100644 index 0000000..70b38c3 Binary files /dev/null and b/static/images/remote/06c7423ee81dbae1.webp differ diff --git a/static/images/remote/09eb4898f46c76db.webp b/static/images/remote/09eb4898f46c76db.webp new file mode 100644 index 0000000..f2bfb22 Binary files /dev/null and b/static/images/remote/09eb4898f46c76db.webp differ diff --git a/static/images/remote/11f1ace7f75ed731.webp b/static/images/remote/11f1ace7f75ed731.webp new file mode 100644 index 0000000..8493ab9 Binary files /dev/null and b/static/images/remote/11f1ace7f75ed731.webp differ diff --git a/static/images/remote/16b11ca906d64dcf.webp b/static/images/remote/16b11ca906d64dcf.webp new file mode 100644 index 0000000..b3f1b43 Binary files /dev/null and b/static/images/remote/16b11ca906d64dcf.webp differ diff --git a/static/images/remote/1e1a6bad9723ecba.webp b/static/images/remote/1e1a6bad9723ecba.webp new file mode 100644 index 0000000..06da1ee Binary files /dev/null and b/static/images/remote/1e1a6bad9723ecba.webp differ diff --git a/static/images/remote/1f6ca697aa450bf5.webp b/static/images/remote/1f6ca697aa450bf5.webp new file mode 100644 index 0000000..bcebf81 Binary files /dev/null and b/static/images/remote/1f6ca697aa450bf5.webp differ diff --git a/static/images/remote/23a4aee1ddac377d.webp b/static/images/remote/23a4aee1ddac377d.webp new file mode 100644 index 0000000..51584ff Binary files /dev/null and b/static/images/remote/23a4aee1ddac377d.webp differ diff --git a/static/images/remote/254e260419aa661e.webp b/static/images/remote/254e260419aa661e.webp new file mode 100644 index 0000000..2a2a269 Binary files /dev/null and b/static/images/remote/254e260419aa661e.webp differ diff --git a/static/images/remote/2713aa6bd6511e7e.webp b/static/images/remote/2713aa6bd6511e7e.webp new file mode 100644 index 0000000..ab1c3c2 Binary files /dev/null and b/static/images/remote/2713aa6bd6511e7e.webp differ diff --git a/static/images/remote/2b57d5382f67e8a6.webp b/static/images/remote/2b57d5382f67e8a6.webp new file mode 100644 index 0000000..749cc86 Binary files /dev/null and b/static/images/remote/2b57d5382f67e8a6.webp differ diff --git a/static/images/remote/2cd6b3491e675fe9.webp b/static/images/remote/2cd6b3491e675fe9.webp new file mode 100644 index 0000000..06da1ee Binary files /dev/null and b/static/images/remote/2cd6b3491e675fe9.webp differ diff --git a/static/images/remote/38183e7822943589.webp b/static/images/remote/38183e7822943589.webp new file mode 100644 index 0000000..2cab7b5 Binary files /dev/null and b/static/images/remote/38183e7822943589.webp differ diff --git a/static/images/remote/461e77261af8212f.webp b/static/images/remote/461e77261af8212f.webp new file mode 100644 index 0000000..e72b14b Binary files /dev/null and b/static/images/remote/461e77261af8212f.webp differ diff --git a/static/images/remote/4ceaa3d9b7bf41aa.webp b/static/images/remote/4ceaa3d9b7bf41aa.webp new file mode 100644 index 0000000..d750f19 Binary files /dev/null and b/static/images/remote/4ceaa3d9b7bf41aa.webp differ diff --git a/static/images/remote/4ebf139c7ad742c0.webp b/static/images/remote/4ebf139c7ad742c0.webp new file mode 100644 index 0000000..a26339b Binary files /dev/null and b/static/images/remote/4ebf139c7ad742c0.webp differ diff --git a/static/images/remote/4fc5ba257091f2f6.webp b/static/images/remote/4fc5ba257091f2f6.webp new file mode 100644 index 0000000..a511c29 Binary files /dev/null and b/static/images/remote/4fc5ba257091f2f6.webp differ diff --git a/static/images/remote/57e3480631c3524f.webp b/static/images/remote/57e3480631c3524f.webp new file mode 100644 index 0000000..9a006fc Binary files /dev/null and b/static/images/remote/57e3480631c3524f.webp differ diff --git a/static/images/remote/5a4d501bd6d6088c.webp b/static/images/remote/5a4d501bd6d6088c.webp new file mode 100644 index 0000000..32840d6 Binary files /dev/null and b/static/images/remote/5a4d501bd6d6088c.webp differ diff --git a/static/images/remote/61d4b3032d0c87dd.webp b/static/images/remote/61d4b3032d0c87dd.webp new file mode 100644 index 0000000..8d1fdb9 Binary files /dev/null and b/static/images/remote/61d4b3032d0c87dd.webp differ diff --git a/static/images/remote/626f2aa1c4d7030a.webp b/static/images/remote/626f2aa1c4d7030a.webp new file mode 100644 index 0000000..aff0552 Binary files /dev/null and b/static/images/remote/626f2aa1c4d7030a.webp differ diff --git a/static/images/remote/62c8fe68d5ea8108.webp b/static/images/remote/62c8fe68d5ea8108.webp new file mode 100644 index 0000000..b965e27 Binary files /dev/null and b/static/images/remote/62c8fe68d5ea8108.webp differ diff --git a/static/images/remote/64ba2429385bdd5f.webp b/static/images/remote/64ba2429385bdd5f.webp new file mode 100644 index 0000000..66cb812 Binary files /dev/null and b/static/images/remote/64ba2429385bdd5f.webp differ diff --git a/static/images/remote/656fc21dfbab689a.webp b/static/images/remote/656fc21dfbab689a.webp new file mode 100644 index 0000000..25ea8a6 Binary files /dev/null and b/static/images/remote/656fc21dfbab689a.webp differ diff --git a/static/images/remote/674d7ec610419571.webp b/static/images/remote/674d7ec610419571.webp new file mode 100644 index 0000000..6d15076 Binary files /dev/null and b/static/images/remote/674d7ec610419571.webp differ diff --git a/static/images/remote/67b3bca007b144c5.webp b/static/images/remote/67b3bca007b144c5.webp new file mode 100644 index 0000000..e0a2f8b Binary files /dev/null and b/static/images/remote/67b3bca007b144c5.webp differ diff --git a/static/images/remote/69a1a813f6b9324e.webp b/static/images/remote/69a1a813f6b9324e.webp new file mode 100644 index 0000000..6c50ea0 Binary files /dev/null and b/static/images/remote/69a1a813f6b9324e.webp differ diff --git a/static/images/remote/72f6f94d5459b36c.webp b/static/images/remote/72f6f94d5459b36c.webp new file mode 100644 index 0000000..12cadf0 Binary files /dev/null and b/static/images/remote/72f6f94d5459b36c.webp differ diff --git a/static/images/remote/7794b4aa91e0886e.webp b/static/images/remote/7794b4aa91e0886e.webp new file mode 100644 index 0000000..c89caf6 Binary files /dev/null and b/static/images/remote/7794b4aa91e0886e.webp differ diff --git a/static/images/remote/7e49bf87876caf2b.webp b/static/images/remote/7e49bf87876caf2b.webp new file mode 100644 index 0000000..df412ed Binary files /dev/null and b/static/images/remote/7e49bf87876caf2b.webp differ diff --git a/static/images/remote/7f3ee600df8bf0a9.webp b/static/images/remote/7f3ee600df8bf0a9.webp new file mode 100644 index 0000000..535b512 Binary files /dev/null and b/static/images/remote/7f3ee600df8bf0a9.webp differ diff --git a/static/images/remote/83b50cf812d906f0.webp b/static/images/remote/83b50cf812d906f0.webp new file mode 100644 index 0000000..deffb23 Binary files /dev/null and b/static/images/remote/83b50cf812d906f0.webp differ diff --git a/static/images/remote/880d1ba7cf4187a5.webp b/static/images/remote/880d1ba7cf4187a5.webp new file mode 100644 index 0000000..0383595 Binary files /dev/null and b/static/images/remote/880d1ba7cf4187a5.webp differ diff --git a/static/images/remote/8ab957ee76be64c8.webp b/static/images/remote/8ab957ee76be64c8.webp new file mode 100644 index 0000000..0375d14 Binary files /dev/null and b/static/images/remote/8ab957ee76be64c8.webp differ diff --git a/static/images/remote/8b91a7098be22de6.webp b/static/images/remote/8b91a7098be22de6.webp new file mode 100644 index 0000000..7c82dfd Binary files /dev/null and b/static/images/remote/8b91a7098be22de6.webp differ diff --git a/static/images/remote/8d484efeec42b22e.webp b/static/images/remote/8d484efeec42b22e.webp new file mode 100644 index 0000000..1dbdeb5 Binary files /dev/null and b/static/images/remote/8d484efeec42b22e.webp differ diff --git a/static/images/remote/940f43beb51345fa.webp b/static/images/remote/940f43beb51345fa.webp new file mode 100644 index 0000000..9680473 Binary files /dev/null and b/static/images/remote/940f43beb51345fa.webp differ diff --git a/static/images/remote/953bc828f4a909ed.webp b/static/images/remote/953bc828f4a909ed.webp new file mode 100644 index 0000000..0a37c55 Binary files /dev/null and b/static/images/remote/953bc828f4a909ed.webp differ diff --git a/static/images/remote/a105bc4c5812eb6d.webp b/static/images/remote/a105bc4c5812eb6d.webp new file mode 100644 index 0000000..a7ef9fc Binary files /dev/null and b/static/images/remote/a105bc4c5812eb6d.webp differ diff --git a/static/images/remote/a652b99264ef113c.webp b/static/images/remote/a652b99264ef113c.webp new file mode 100644 index 0000000..89ccac3 Binary files /dev/null and b/static/images/remote/a652b99264ef113c.webp differ diff --git a/static/images/remote/a7ced37c3c927888.webp b/static/images/remote/a7ced37c3c927888.webp new file mode 100644 index 0000000..0335fef Binary files /dev/null and b/static/images/remote/a7ced37c3c927888.webp differ diff --git a/static/images/remote/ac55ca23fae564d8.webp b/static/images/remote/ac55ca23fae564d8.webp new file mode 100644 index 0000000..fc6cb31 Binary files /dev/null and b/static/images/remote/ac55ca23fae564d8.webp differ diff --git a/static/images/remote/b0f9e20257ed9d70.webp b/static/images/remote/b0f9e20257ed9d70.webp new file mode 100644 index 0000000..7f9597a Binary files /dev/null and b/static/images/remote/b0f9e20257ed9d70.webp differ diff --git a/static/images/remote/b82953cc708a503e.webp b/static/images/remote/b82953cc708a503e.webp new file mode 100644 index 0000000..d9d26e7 Binary files /dev/null and b/static/images/remote/b82953cc708a503e.webp differ diff --git a/static/images/remote/b9e62fd5fa9ce102.webp b/static/images/remote/b9e62fd5fa9ce102.webp new file mode 100644 index 0000000..e536b4f Binary files /dev/null and b/static/images/remote/b9e62fd5fa9ce102.webp differ diff --git a/static/images/remote/ca91ce3a8c6c048c.webp b/static/images/remote/ca91ce3a8c6c048c.webp new file mode 100644 index 0000000..53ba74a Binary files /dev/null and b/static/images/remote/ca91ce3a8c6c048c.webp differ diff --git a/static/images/remote/cc2a19d9f6ac44b1.webp b/static/images/remote/cc2a19d9f6ac44b1.webp new file mode 100644 index 0000000..7bdeb0d Binary files /dev/null and b/static/images/remote/cc2a19d9f6ac44b1.webp differ diff --git a/static/images/remote/ce0279994a8d0c22.webp b/static/images/remote/ce0279994a8d0c22.webp new file mode 100644 index 0000000..9b33289 Binary files /dev/null and b/static/images/remote/ce0279994a8d0c22.webp differ diff --git a/static/images/remote/d3efbaf0d582a4de.webp b/static/images/remote/d3efbaf0d582a4de.webp new file mode 100644 index 0000000..84de588 Binary files /dev/null and b/static/images/remote/d3efbaf0d582a4de.webp differ diff --git a/static/images/remote/d47f4bcf0566064e.webp b/static/images/remote/d47f4bcf0566064e.webp new file mode 100644 index 0000000..2aba192 Binary files /dev/null and b/static/images/remote/d47f4bcf0566064e.webp differ diff --git a/static/images/remote/d668c254603561d5.webp b/static/images/remote/d668c254603561d5.webp new file mode 100644 index 0000000..5abb7d8 Binary files /dev/null and b/static/images/remote/d668c254603561d5.webp differ diff --git a/static/images/remote/d9d7e08baf18edfe.webp b/static/images/remote/d9d7e08baf18edfe.webp new file mode 100644 index 0000000..4af20c6 Binary files /dev/null and b/static/images/remote/d9d7e08baf18edfe.webp differ diff --git a/static/images/remote/dd3dfb419e66a44c.webp b/static/images/remote/dd3dfb419e66a44c.webp new file mode 100644 index 0000000..a96b9a5 Binary files /dev/null and b/static/images/remote/dd3dfb419e66a44c.webp differ diff --git a/static/images/remote/e0d61d23865f0262.webp b/static/images/remote/e0d61d23865f0262.webp new file mode 100644 index 0000000..9cecb22 Binary files /dev/null and b/static/images/remote/e0d61d23865f0262.webp differ diff --git a/static/images/remote/e7c1aec80a4fc8b6.webp b/static/images/remote/e7c1aec80a4fc8b6.webp new file mode 100644 index 0000000..5919ee1 Binary files /dev/null and b/static/images/remote/e7c1aec80a4fc8b6.webp differ diff --git a/static/images/remote/e925811cec427259.webp b/static/images/remote/e925811cec427259.webp new file mode 100644 index 0000000..f7b3242 Binary files /dev/null and b/static/images/remote/e925811cec427259.webp differ diff --git a/static/images/remote/efa5aca9038f196e.webp b/static/images/remote/efa5aca9038f196e.webp new file mode 100644 index 0000000..c7f4cbf Binary files /dev/null and b/static/images/remote/efa5aca9038f196e.webp differ diff --git a/static/images/remote/f0e99c1fd601e8f4.webp b/static/images/remote/f0e99c1fd601e8f4.webp new file mode 100644 index 0000000..cfba95a Binary files /dev/null and b/static/images/remote/f0e99c1fd601e8f4.webp differ diff --git a/static/images/remote/ff2cc37374f4a362.webp b/static/images/remote/ff2cc37374f4a362.webp new file mode 100644 index 0000000..8ac3fa6 Binary files /dev/null and b/static/images/remote/ff2cc37374f4a362.webp differ