Files
dogfather-universe/server/mess-aushang.mjs
T
DogFatherGitandClaude Opus 5 9d219e17ce Ein Bild vom Aushang: zwei Knoepfe, und man sieht welcher mehr bewirkt
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 <[email protected]>
2026-09-25 14:14:35 +02:00

187 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
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);