main
|
Last change
on this file since d8c5042 was c628db10, checked in by Robin Genis <roboburr@…>, 2 months ago |
|
fix(fediverse): expose cover via Note.image for player-card posts (Cirkel covers)
Hosted-audio posts suppress the image attachment so Mastodon shows the player
card; that left the Cirkel/timeline cards coverless. buildNote now also sets the
Note's AS2 image (Mastodon ignores it), and handleInbox uses it as a cover
fallback. scripts/backfill-cirkel-covers.mjs re-fetches existing coverless
timeline notes to fill them in.
Co-Authored-By: Claude <noreply@…>
|
-
Property mode
set to
100644
|
|
File size:
1.8 KB
|
| Rev | Line | |
|---|
| [c628db10] | 1 | // Backfill missing Cirkel/timeline covers: re-fetch each coverless ap_timeline
|
|---|
| 2 | // note (as ActivityPub) and pull its cover from the now-exposed `image` (or an
|
|---|
| 3 | // image attachment). Run on a CONSUMER instance AFTER the publisher deployed the
|
|---|
| 4 | // buildNote `image` fix: cd ~/apps/<instance> && node scripts/backfill-cirkel-covers.mjs
|
|---|
| 5 |
|
|---|
| 6 | import 'dotenv/config';
|
|---|
| 7 | import db from '../src/config/database.js';
|
|---|
| 8 |
|
|---|
| 9 | function hasImg(mj) {
|
|---|
| 10 | try { return JSON.parse(mj || '[]').some((m) => m.url && (!m.type || /image/i.test(m.type))); } catch { return false; }
|
|---|
| 11 | }
|
|---|
| 12 | async function fetchNote(url) {
|
|---|
| 13 | try { const r = await fetch(url, { headers: { Accept: 'application/activity+json' } }); if (r.ok) return await r.json(); } catch { /* skip */ }
|
|---|
| 14 | return null;
|
|---|
| 15 | }
|
|---|
| 16 | function coverFrom(note) {
|
|---|
| 17 | if (!note) return null;
|
|---|
| 18 | const atts = (Array.isArray(note.attachment) ? note.attachment : []).filter((a) => a && a.url && (/image/i.test(a.mediaType || '') || !a.mediaType));
|
|---|
| 19 | if (atts.length) return { url: atts[0].url, type: atts[0].mediaType || 'image/jpeg' };
|
|---|
| 20 | const im = Array.isArray(note.image) ? note.image[0] : note.image;
|
|---|
| 21 | const iu = im && (typeof im === 'string' ? im : im.url);
|
|---|
| 22 | if (iu) return { url: iu, type: (im && im.mediaType) || 'image/jpeg' };
|
|---|
| 23 | return null;
|
|---|
| 24 | }
|
|---|
| 25 |
|
|---|
| 26 | let rows = [];
|
|---|
| 27 | try { rows = db.prepare('SELECT id, media_json FROM ap_timeline').all().filter((r) => !hasImg(r.media_json)); } catch { console.log('no ap_timeline'); process.exit(0); }
|
|---|
| 28 | let fixed = 0;
|
|---|
| 29 | for (const r of rows) {
|
|---|
| 30 | const cov = coverFrom(await fetchNote(r.id));
|
|---|
| 31 | if (!cov) continue;
|
|---|
| 32 | let arr = []; try { arr = JSON.parse(r.media_json || '[]'); } catch { /* reset */ }
|
|---|
| 33 | arr.push(cov);
|
|---|
| 34 | try { db.prepare('UPDATE ap_timeline SET media_json = ? WHERE id = ?').run(JSON.stringify(arr), r.id); fixed++; } catch { /* skip */ }
|
|---|
| 35 | }
|
|---|
| 36 | console.log(`backfilled ${fixed}/${rows.length} coverless timeline posts`);
|
|---|
| 37 | process.exit(0);
|
|---|
Note:
See
TracBrowser
for help on using the repository browser.