server.js (4024B)
1 const fs = require("fs"); 2 const path = require("path"); 3 const http = require("http"); 4 const https = require("https"); 5 6 const CONFIG_PATH = path.join(__dirname, "config.json"); 7 8 function readJson(filePath, fallback) { 9 try { 10 return JSON.parse(fs.readFileSync(filePath, "utf8")); 11 } catch { 12 return fallback; 13 } 14 } 15 16 function writeJson(filePath, value) { 17 fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8"); 18 } 19 20 function normalizeUrl(s) { 21 const raw = String(s || "").trim(); 22 if (!raw) return ""; 23 try { 24 const u = new URL(raw); 25 if (u.protocol !== "http:" && u.protocol !== "https:") return ""; 26 u.hash = ""; 27 return u.toString().replace(/\/+$/, ""); 28 } catch { 29 return ""; 30 } 31 } 32 33 function postJson(targetUrl, token, payload) { 34 return new Promise((resolve) => { 35 let u; 36 try { 37 u = new URL(targetUrl); 38 } catch { 39 resolve({ ok: false, status: 0, error: "Invalid URL" }); 40 return; 41 } 42 43 const transport = u.protocol === "http:" ? http : https; 44 const port = u.port ? Number(u.port) : u.protocol === "http:" ? 80 : 443; 45 const body = Buffer.from(JSON.stringify(payload), "utf8"); 46 const req = transport.request( 47 { 48 method: "POST", 49 hostname: u.hostname, 50 port, 51 path: u.pathname + u.search, 52 headers: { 53 "Content-Type": "application/json", 54 "Content-Length": body.length, 55 Authorization: token ? `Bearer ${token}` : "" 56 } 57 }, 58 (res) => { 59 let out = ""; 60 res.on("data", (d) => (out += String(d || ""))); 61 res.on("end", () => resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode || 0, body: out })); 62 } 63 ); 64 req.on("error", (e) => resolve({ ok: false, status: 0, error: e?.message || String(e) })); 65 req.end(body); 66 }); 67 } 68 69 module.exports = function init(api) { 70 const config = readJson(CONFIG_PATH, { 71 directoryUrl: "", 72 token: "", 73 instance: { id: "", url: "", name: "", description: "", bzlVersion: "", requiresRegistrationCode: false }, 74 publicHives: [] 75 }); 76 77 api.registerWs("getConfig", (ws) => { 78 if (ws?.user?.role !== "owner") return; 79 api.sendToUsers([ws.user.username], { type: "plugin:directory-publisher:config", config }); 80 }); 81 82 api.registerWs("setConfig", (ws, msg) => { 83 if (ws?.user?.role !== "owner") return; 84 const next = msg?.config || {}; 85 config.directoryUrl = normalizeUrl(next.directoryUrl || config.directoryUrl); 86 config.token = String(next.token || config.token || "").trim(); 87 config.instance = { ...(config.instance || {}), ...(next.instance || {}) }; 88 config.instance.url = normalizeUrl(config.instance.url); 89 config.instance.id = String(config.instance.id || "").trim(); 90 config.instance.name = String(config.instance.name || "").trim(); 91 config.instance.description = String(config.instance.description || "").trim(); 92 config.instance.bzlVersion = String(config.instance.bzlVersion || "").trim(); 93 config.instance.requiresRegistrationCode = Boolean(config.instance.requiresRegistrationCode); 94 config.publicHives = Array.isArray(next.publicHives) ? next.publicHives : config.publicHives; 95 writeJson(CONFIG_PATH, config); 96 api.sendToUsers([ws.user.username], { type: "plugin:directory-publisher:configSaved" }); 97 }); 98 99 api.registerWs("publishNow", async (ws) => { 100 if (ws?.user?.role !== "owner") return; 101 const urlBase = normalizeUrl(config.directoryUrl); 102 if (!urlBase) return api.sendToUsers([ws.user.username], { type: "plugin:directory-publisher:result", ok: false, error: "Missing directory URL." }); 103 const endpoint = `${urlBase}/api/plugins/directory-server/announce`; 104 const payload = { instance: config.instance, publicHives: config.publicHives }; 105 const r = await postJson(endpoint, config.token, payload); 106 api.sendToUsers([ws.user.username], { type: "plugin:directory-publisher:result", ...r }); 107 }); 108 109 api.log("info", "directory-publisher loaded"); 110 };