From 9d219e17ce18f678610443f4f569ed3fcff75b58 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Fri, 25 Sep 2026 14:14:35 +0200 Subject: [PATCH] Ein Bild vom Aushang: zwei Knoepfe, und man sieht welcher mehr bewirkt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit server/mess-aushang.mjs zeigt, was pruef-pin-fuer-mich nicht messen kann -- wie die zwei Knoepfe nebeneinander aussehen. Drei Blicke: DogFather auf dem Handy (beide Knoepfe), ein Modi auf dem Handy (nur einer), DogFather am Rechner. Gemessen: „lösen" 52x50 in Grau, „bei allen" 68x50 in der Warnfarbe des Hauses. Beide ueber 44 px hoch, beide im Bild, und auf den ersten Blick unterscheidbar -- genau darum ging es: Zwei gleich aussehende Knoepfe nebeneinander waeren die schlechteste Loesung. Co-Authored-By: Claude Opus 5 --- server/mess-aushang.mjs | 186 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 server/mess-aushang.mjs diff --git a/server/mess-aushang.mjs b/server/mess-aushang.mjs new file mode 100644 index 00000000..6fa6d645 --- /dev/null +++ b/server/mess-aushang.mjs @@ -0,0 +1,186 @@ +/* ===================================================================== + 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"; +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"); +execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-keyout", schl, + "-out", zert, "-days", "2", "-subj", `/CN=${CREW}`, "-addext", `subjectAltName=DNS:${CREW}`], +{ stdio: "ignore" }); +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);