#!/usr/bin/env node /** * Seekarte, Block 1: Vollabzug des gelisteten Plugin-Bestands. * * Holt ueber api.wordpress.org/plugins/info/1.2 (action=query_plugins, * browse=updated) alle gelisteten Plugins mit den Metrikfeldern und legt sie * seitenweise als JSON ab. Nur die Felder, die eine Kennzahl tragen; sections, * description, screenshots und banners sind ausgeblendet, das druckt den * Abzug von rund 518 MB auf rund 47 MB. * * Fallen, die am 27.08.2026 gemessen wurden und hier beruecksichtigt sind: * - browse=popular schneidet den Bestand ab (66.709 statt 70.826). Nur updated. * - info.pages ist unzuverlaessig. Abbruch daher an einer leeren Seite, nicht * an der gemeldeten Seitenzahl. * - page wird still bei 999 geklemmt. Weit darunter, aber als Riegel drin. * - per_page ist bei browse auf 250 gedeckelt, hoehere Werte liefern trotzdem 250. * * Aufruf: * node bin/seekarte-bestand.mjs [--out VERZEICHNIS] [--takt MS] * Bereits vorhandene Seiten werden uebersprungen, der Lauf ist also * wiederaufnehmbar. Kein Schluesselmaterial noetig, die API ist offen. */ import { writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; const args = process.argv.slice(2); const wert = (name, standard) => { const i = args.indexOf(name); return i >= 0 && args[i + 1] ? args[i + 1] : standard; }; const OUT = wert('--out', join(process.cwd(), 'seekarte-daten', 'bestand')); const TAKT = Number(wert('--takt', '250')); // Millisekunden zwischen Abrufen const MAX_SEITEN = 999; // harte Klemmgrenze der API const FELDER = { 'request[per_page]': '250', 'request[browse]': 'updated', 'request[fields][sections]': '0', 'request[fields][description]': '0', 'request[fields][short_description]': '0', 'request[fields][screenshots]': '0', 'request[fields][banners]': '0', 'request[fields][icons]': '0', 'request[fields][ratings]': '0', 'request[fields][contributors]': '0', 'request[fields][versions]': '0', /* Nachgefordert, weil sonst nicht in 1.2 enthalten: */ 'request[fields][downloaded]': '1', 'request[fields][active_installs]': '1', 'request[fields][last_updated]': '1', 'request[fields][added]': '1', 'request[fields][tested]': '1', 'request[fields][requires]': '1', 'request[fields][requires_php]': '1', 'request[fields][support_threads]': '1', 'request[fields][support_threads_resolved]': '1', 'request[fields][num_ratings]': '1', 'request[fields][rating]': '1', 'request[fields][homepage]': '1', 'request[fields][author_profile]': '1' }; const url = (seite) => { const p = new URLSearchParams({ action: 'query_plugins', ...FELDER, 'request[page]': String(seite) }); return `https://api.wordpress.org/plugins/info/1.2/?${p}`; }; const schlafen = (ms) => new Promise(r => setTimeout(r, ms)); async function hole(seite, versuch = 1) { try { const r = await fetch(url(seite), { headers: { 'user-agent': 'hafenstudios-seekarte/1.0 (+https://hafenstudios.com/seekarte/)' } }); if (!r.ok) { throw new Error('HTTP ' + r.status); } return await r.json(); } catch (e) { if (versuch >= 4) { throw e; } await schlafen(1000 * versuch); return hole(seite, versuch + 1); } } mkdirSync(OUT, { recursive: true }); const vorhanden = new Set(readdirSync(OUT).filter(n => n.endsWith('.json'))); let seite = 1, gesamt = 0, neu = 0, uebersprungen = 0, gemeldeteSeiten = null; const start = Date.now(); while (seite <= MAX_SEITEN) { const name = `seite-${String(seite).padStart(4, '0')}.json`; if (vorhanden.has(name)) { uebersprungen++; seite++; continue; } const d = await hole(seite); const plugins = d.plugins || []; if (gemeldeteSeiten === null && d.info) { gemeldeteSeiten = d.info.pages; } /* Abbruch an der leeren Seite, NICHT an info.pages. Die gemeldete Seitenzahl war in der Vorpruefung nachweislich falsch. */ if (plugins.length === 0) { console.log(`Seite ${seite} leer, Abzug vollstaendig.`); break; } writeFileSync(join(OUT, name), JSON.stringify(plugins)); gesamt += plugins.length; neu++; if (neu % 25 === 0) { const s = Math.round((Date.now() - start) / 1000); console.log(` Seite ${seite}, ${gesamt} Plugins, ${s}s`); } seite++; await schlafen(TAKT); } const dauer = Math.round((Date.now() - start) / 1000); console.log(`\nFertig. ${neu} Seiten neu geholt, ${uebersprungen} bereits vorhanden, ${gesamt} Plugin-Datensaetze, ${dauer}s.`); console.log(`API meldete ${gemeldeteSeiten} Seiten, tatsaechlich abgerufen bis Seite ${seite - 1}.`); console.log(`Ablage: ${OUT}`);