forked from ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI
113 lines
3.5 KiB
JavaScript
113 lines
3.5 KiB
JavaScript
|
|
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`);
|