/* ===================================================================== WIE DER AUSHANG MIT SEINEN ZWEI KNOEPFEN AUSSIEHT (25.09.2026) ===================================================================== Filipe: „jeder soll das fixierte individuel für sich lösen können aber niemals so dass es sich für alle löst ... dogfather und die rechte hand sollen die option haben für sich selbst oder für alle zu lösen." `pruef-pin-fuer-mich` misst, WAS PASSIERT (37 Pruefungen). Hier geht es um das, was man nur sieht: Stehen die zwei Knoepfe nebeneinander, ohne sich zu draengen? Sind sie auf dem Handy zu treffen? Sieht man auf den ersten Blick, dass der zweite mehr bewirkt? ZWEI BLICKE, weil es zwei Antworten gibt: DogFather sieht beide Knoepfe, ein Modi nur einen. Ein Bild vom einen sagt nichts ueber den anderen. Aufrufen mit: node server/mess-aushang.mjs ===================================================================== */ import { mkdtempSync, rmSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { execFileSync } from "node:child_process"; import { request as httpAnfrage } from "node:http"; import { createServer as httpsServer } from "node:https"; const PORT = 5481, HP = 5482; const CREW = "crew.dogfather-universe.com"; const ordner = mkdtempSync(join(tmpdir(), "ws-aushang-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); process.env.PORT = String(PORT); process.env.SITE_ACCESS_SECRET = "lokaler-test"; process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; import { notbremse } from "./helfer-notbremse.mjs"; import { zertifikatBauen } from "./helfer-openssl.mjs"; await import("./index.js"); notbremse(240_000, "mess-aushang"); await new Promise((r) => setTimeout(r, 900)); process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); }); const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); const d = new DatabaseSync(process.env.WORKSPACE_DB); const jetzt = new Date().toISOString(); const sch = randomBytes(32).toString("hex"); d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") .run("code_kennung_schluessel", sch, jetzt); const anlegen = (name, rolle, code) => { const s = randomBytes(16).toString("hex"); const h = scryptSync(code, s, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); return d.prepare("INSERT INTO personen" + " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)" + " VALUES (?,?,?,?,?,?,1,?) RETURNING id") .get(name, rolle, h, s, 32768, createHmac("sha256", sch).update(code).digest("hex"), jetzt).id; }; anlegen("Filipe", "admin", "CODE-DOGI-0001"); const idModi = anlegen("Kessi", "modi", "CODE-MODI-0001"); d.close(); const roh = (keks, pfad, methode = "GET", koerper = null) => new Promise((fertig, schief) => { const daten = koerper === null ? null : JSON.stringify(koerper); const a = httpAnfrage({ host: "127.0.0.1", port: PORT, path: pfad, method: methode, headers: { Host: CREW, ...(keks ? { Cookie: keks } : {}), Origin: `https://${CREW}`, Referer: `https://${CREW}/workspace/chat.html`, ...(daten ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) } : {}), }, }, (r) => { let t = ""; r.on("data", (x) => { t += x; }); r.on("end", () => fertig({ status: r.statusCode, kopf: r.headers, json: () => { try { return JSON.parse(t); } catch { return {}; } } })); }); a.on("error", schief); if (daten) a.write(daten); a.end(); }); const anmelden = async (rolle, code) => { const a = await roh(null, "/workspace/api/anmelden", "POST", { rolle, code }); return [].concat(a.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; "); }; const kDogi = await anmelden("admin", "CODE-DOGI-0001"); const kModi = await anmelden("modi", "CODE-MODI-0001"); const gr = await roh(kDogi, "/workspace/api/chat/gruppe", "POST", { name: "Ansagen", mit: [idModi] }); const raumId = gr.json().raum_id; const n = await roh(kDogi, `/workspace/api/chat/raeume/${raumId}/nachrichten`, "POST", { text: "Guuuuuten Morgeeeen, ihr wundervollen Chaoten! Wir haben uns " + "durch die Woche gekämpft – heute wird gefeiert." }); const nid = n.json().nachricht?.id || n.json().id; await roh(kDogi, `/workspace/api/chat/nachrichten/${nid}/anheften`, "PUT", { an: true }); console.log(`Aushang #${nid} in Raum #${raumId}`); const schl = join(ordner, "b.key"), zert = join(ordner, "b.crt"); zertifikatBauen(schl, zert, CREW); const vorbau = httpsServer({ key: readFileSync(schl), cert: readFileSync(zert) }, (von, nach) => { const w = httpAnfrage({ host: "127.0.0.1", port: PORT, path: von.url, method: von.method, headers: { ...von.headers, host: `${CREW}:${HP}` }, }, (a) => { nach.writeHead(a.statusCode || 502, a.headers); a.pipe(nach); }); von.pipe(w); }); await new Promise((r) => vorbau.listen(HP, "127.0.0.1", r)); const pw = await import( "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); const browser = await pw.chromium.launch({ args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, "--ignore-certificate-errors"], }); for (const [wer, keks, breite] of [ ["dogfather-handy", kDogi, 390], ["modi-handy", kModi, 390], ["dogfather-rechner", kDogi, 1280], ]) { const kontext = await browser.newContext({ viewport: { width: breite, height: 900 }, deviceScaleFactor: breite < 700 ? 2 : 1, isMobile: breite < 700, hasTouch: breite < 700, }); await kontext.addCookies(keks.split("; ").map((k) => { const [x, ...r] = k.split("="); return { name: x, value: r.join("="), domain: CREW, path: "/" }; })); const seite = await kontext.newPage(); await seite.goto(`https://${CREW}:${HP}/workspace/chat.html?raum=${raumId}`, { waitUntil: "networkidle" }); await seite.waitForTimeout(1600); const lage = await seite.evaluate(() => { const pin = document.querySelector(".chat-pin"); if (!pin) return { da: false }; const r = pin.getBoundingClientRect(); const knoepfe = [...pin.querySelectorAll(".chat-pin__los")].map((k) => { const kr = k.getBoundingClientRect(); return { wort: (k.textContent || "").trim(), b: Math.round(kr.width), h: Math.round(kr.height), farbe: getComputedStyle(k).color, }; }); return { da: true, kasten: `${Math.round(r.width)}x${Math.round(r.height)}`, ausDemBild: r.right > window.innerWidth + 1, knoepfe, zuKlein: knoepfe.filter((k) => k.h < 44 && matchMedia("(pointer: coarse)").matches) .map((k) => `${k.wort} ${k.h}px`), }; }); console.log(`\n=== ${wer} (${breite} px) ===`); if (!lage.da) console.log(" KEIN AUSHANG ZU SEHEN"); else { console.log(` Kasten ${lage.kasten}` + (lage.ausDemBild ? " STEHT AUS DEM BILD" : "")); for (const k of lage.knoepfe) { console.log(` "${k.wort}" ${k.b}x${k.h} ${k.farbe}`); } if (lage.zuKlein.length) console.log(` ZU KLEIN: ${lage.zuKlein.join(", ")}`); const el = seite.locator(".chat-pin").first(); await el.screenshot({ path: `server/mess-aushang-${wer}.png` }).catch(() => {}); console.log(` Bild: server/mess-aushang-${wer}.png`); } await kontext.close(); } await browser.close(); vorbau.close(); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } process.exit(0);