source: Klonkt/scripts/recover-from-cache.mjs@ 7aa3140

main
Last change on this file since 7aa3140 was 7aa3140, checked in by roboburr <roboburr@…>, 5 weeks ago

Herstel uit andermans tijdlijn-cache (shaer-l1v)

De aanleiding: boiert.eu verloor zijn database. De posts staan nog in de
ap_timeline van instances die boiert volgen, en die tabel sleutelt op de
OORSPRONKELIJKE AP-object-URI. De identiteiten overleven dus, en dat is het
verschil tussen herstellen en opnieuw posten -- boosts, likes en antwoorden
elders wijzen naar die ids.

Er is bewust GEEN apart herstelpad. recover-from-cache.mjs maakt een gewoon
archief in het formaat uit docs/EXPORT-FORMAT.md, dat door dezelfde importer gaat
als een export: droogloop, versiecontrole, en dezelfde regel rond het behouden
van AP-ids. Een tweede implementatie zou uiteenlopen met de eerste.

Meerdere bronnen mogen samen: verschillende volgers dekken verschillende
periodes, en per AP-id wint de rijkste versie.

Titels moeten terug uit de TEKST, want buildNote bakt ze daar in (Mastodon
negeert name). Er is geen sluitend signaal: op echte data heet een post "Back
to 1987!" met slug "waiting-on-you", dus de slug is niet van de titel afgeleid.
We gaan op de vorm af -- een openende alinea met alleen vetgedrukte platte tekst
-- en het script drukt AF wat het lostrok, zodat een mens het naloopt. --houd-titel
laat alles staan.

Gevonden door tegen echte cachedata te draaien (24 posts van sound-fabrics uit de
beta-cache): een deel van de bijlagen loopt niet via /media/ maar via
/audio/stream/<bestandsnaam>, en dat is AUDIO_DIR, een andere map. Zonder dat pad
blijft op een muzieksite de halve catalogus liggen. De importer had hetzelfde
gat aan zijn kant: zo'n bestand werd wel weggeschreven maar verdween uit de
kolommen, omdat er geen bestemming onder /media voor was. Nu krijgt het er een.

Het manifest draagt shaer:recovered met de bronnen, het tijdvenster en wat er
principieel ontbreekt -- eigen antwoorden, alles van voor het volgmoment, en
concepten. Anders ziet iemand dit over een jaar aan voor een gewone export en
denkt hij dat de site compleet is.

14 tests, waaronder de hele ketting: cache -> archief -> importer -> posts met
behouden ids, titel, cover en gesloten poll. Suite 528/528.

  • Property mode set to 100644
File size: 4.2 KB
Line 
1#!/usr/bin/env node
2//
3// Herstel de posts van een verloren Klonkt uit de tijdlijn-cache van instances
4// die haar volgden (shaer-l1v).
5//
6// node scripts/recover-from-cache.mjs \
7// --actor https://boiert.eu/ap/users/boiert \
8// --from /pad/naar/sound-fabrics/database.sqlite \
9// --from /pad/naar/nog-een/database.sqlite \
10// --media /geredde/storage/media \
11// --audio /geredde/storage/audio \
12// --out boiert-herstel.zip
13//
14// Het resultaat is een gewoon archief in het formaat uit docs/EXPORT-FORMAT.md.
15// Terugzetten gaat dus met scripts/import-archive.mjs, met dezelfde droogloop en
16// dezelfde regel rond het behouden van AP-ids. Er is bewust geen apart
17// herstelpad: dat zou een tweede implementatie zijn van iets dat al bestaat.
18//
19// WAT HIER PRINCIPIEEL NIET IN ZIT:
20// - antwoorden van de verloren site zelf (die staan niet in een tijdlijn-cache)
21// - alles van voor het moment dat de bron ging volgen
22// - concepten (nooit gefedereerd, dus nergens gecachet)
23// - de plek van afbeeldingen IN de tekst (bij het federeren eruit gehaald)
24
25import fs from 'fs';
26import path from 'path';
27import { recoverFromCache } from '../src/services/ArchiveRecoveryService.js';
28import { zipArchive } from '../src/services/ArchiveExportService.js';
29
30const args = process.argv.slice(2);
31const alle = (naam) => args.reduce((uit, a, i) => (a === naam && args[i + 1] ? [...uit, args[i + 1]] : uit), []);
32const een = (naam) => alle(naam)[0] || null;
33
34const actor = een('--actor');
35const sources = alle('--from');
36if (!actor || !sources.length) {
37 console.error('gebruik: node scripts/recover-from-cache.mjs --actor <actor-uri> --from <db> [--from <db>] [--media <map>] [--audio <map>] [--out <zip> | --dir <map>] [--houd-titel]');
38 process.exit(1);
39}
40
41let uit;
42try {
43 uit = recoverFromCache({
44 sources, actorUri: actor, mediaRoot: een('--media'), audioRoot: een('--audio'),
45 houdTitelInTekst: args.includes('--houd-titel'),
46 slug: een('--slug') || '', title: een('--titel') || '',
47 });
48} catch (e) { console.error(`kan niet herstellen: ${e.message}`); process.exit(1); }
49
50const r = uit.rapport;
51console.log('bronnen:');
52for (const b of r.bronnen) console.log(` ${b.rijen.toString().padStart(5)} rij(en) ${b.pad}`);
53console.log(`\nposts gevonden : ${r.posts}`);
54console.log(`periode : ${r.oudste || '?'} tot ${r.nieuwste || '?'}`);
55console.log(`media : ${r.media} gered, ${r.mediaMissing} niet gevonden`);
56console.log(`titels : ${r.titels.length} losgetrokken uit de tekst`);
57
58// De titels horen door een MENS nagelopen te worden. Er is geen sluitend signaal
59// dat een vetgedrukte eerste regel een titel was; de slug is er niet van afgeleid.
60if (r.titels.length) {
61 console.log('\nteruggevonden titels -- loop deze na, een post die echt met een vetgedrukte');
62 console.log('regel begint raakt die regel kwijt aan zijn titel:');
63 for (const t of r.titels.slice(0, 30)) console.log(` ${t.slug.padEnd(28)} ${t.titel}`);
64 if (r.titels.length > 30) console.log(` ... en nog ${r.titels.length - 30}`);
65}
66if (r.gemist.length) {
67 console.log('\nmedia niet gevonden op schijf:');
68 for (const m of r.gemist.slice(0, 20)) console.log(` ${m.slug.padEnd(28)} ${m.url}`);
69 if (r.gemist.length > 20) console.log(` ... en nog ${r.gemist.length - 20}`);
70}
71for (const w of r.waarschuwingen) console.log(`\nLET OP: ${w}`);
72
73console.log('\nNIET herstelbaar uit een tijdlijn-cache: eigen antwoorden, alles van voor het');
74console.log('volgmoment, concepten, en de plek van afbeeldingen in de tekst.');
75
76if (args.includes('--dry-run')) {
77 console.log(`\n--dry-run: ${uit.files.size} bestand(en) zouden geschreven worden. Niets geschreven.`);
78 process.exit(0);
79}
80
81const dir = een('--dir');
82if (dir) {
83 for (const pad of [...uit.files.keys()].sort()) {
84 const doel = path.join(dir, pad);
85 fs.mkdirSync(path.dirname(doel), { recursive: true });
86 fs.writeFileSync(doel, uit.files.get(pad));
87 }
88 console.log(`\ngeschreven naar ${path.resolve(dir)}`);
89 process.exit(0);
90}
91
92const out = een('--out') || 'herstel.zip';
93fs.writeFileSync(out, zipArchive(uit.files));
94console.log(`\ngeschreven: ${out} (${fs.statSync(out).size} bytes)`);
95console.log(`\nterugzetten: node scripts/import-archive.mjs <slug> ${out} --dry-run`);
Note: See TracBrowser for help on using the repository browser.