First commit

This commit is contained in:
2026-04-13 09:11:52 +00:00
commit f515fae4e3
6 changed files with 337 additions and 0 deletions

151
server.mjs Normal file
View File

@@ -0,0 +1,151 @@
import http from "http";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const DIR = path.join(process.cwd(), "screens");
if (!fs.existsSync(DIR)) fs.mkdirSync(DIR, { recursive: true });
const PORT = 8765;
const HTML = `<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>📸 Screens Upload</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #0a0a0a; color: #fff; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.card { background: #111; border: 1px solid #222; border-radius: 16px; padding: 40px; width: 100%; max-width: 480px; }
h1 { font-size: 1.4rem; margin-bottom: 8px; }
p { color: #666; font-size: 0.9rem; margin-bottom: 24px; }
.drop-zone { border: 2px dashed #333; border-radius: 12px; padding: 40px; text-align: center; cursor: pointer; transition: all 0.2s; }
.drop-zone:hover, .drop-zone.over { border-color: #fff; background: #1a1a1a; }
.drop-zone input { display: none; }
.drop-zone label { cursor: pointer; font-size: 2rem; display: block; margin-bottom: 8px; }
.drop-zone span { color: #555; font-size: 0.85rem; }
.btn { display: block; width: 100%; margin-top: 16px; padding: 12px; background: #fff; color: #000; border: none; border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer; }
.btn:hover { background: #ddd; }
.status { margin-top: 16px; padding: 12px; border-radius: 8px; font-size: 0.85rem; display: none; }
.status.ok { background: #0d2b0d; color: #4ade80; border: 1px solid #166534; display: block; }
.status.err { background: #2b0d0d; color: #f87171; border: 1px solid #991b1b; display: block; }
.files { margin-top: 8px; font-size: 0.8rem; color: #888; }
</style>
</head>
<body>
<div class="card">
<h1>📸 Screens Upload</h1>
<p>Screenshots direkt in den <code>screens/</code> Ordner im aktuellen Verzeichnis hochladen.</p>
<div class="drop-zone" id="zone">
<label for="file">🖼️</label>
<input type="file" id="file" multiple accept="image/*">
<span>Drag & Drop oder klicken zum Auswählen</span>
<div class="files" id="filelist"></div>
</div>
<button class="btn" onclick="upload()">Hochladen</button>
<div class="status" id="status"></div>
</div>
<script>
const zone = document.getElementById("zone");
const fileInput = document.getElementById("file");
const filelist = document.getElementById("filelist");
const status = document.getElementById("status");
fileInput.addEventListener("change", updateList);
zone.addEventListener("dragover", e => { e.preventDefault(); zone.classList.add("over"); });
zone.addEventListener("dragleave", () => zone.classList.remove("over"));
zone.addEventListener("drop", e => {
e.preventDefault(); zone.classList.remove("over");
fileInput.files = e.dataTransfer.files;
updateList();
});
function updateList() {
const names = [...fileInput.files].map(f => f.name).join(", ");
filelist.textContent = names ? "📎 " + names : "";
}
async function upload() {
if (!fileInput.files.length) { showStatus("Keine Datei ausgewählt.", "err"); return; }
const form = new FormData();
for (const f of fileInput.files) form.append("file", f);
try {
const res = await fetch("/upload", { method: "POST", body: form });
const text = await res.text();
if (res.ok) { showStatus("✅ " + text, "ok"); fileInput.value = ""; filelist.textContent = ""; }
else showStatus("❌ " + text, "err");
} catch(e) { showStatus("❌ Verbindungsfehler", "err"); }
}
function showStatus(msg, type) {
status.textContent = msg;
status.className = "status " + type;
}
</script>
</body>
</html>`;
function parseMultipart(body, boundary) {
const files = [];
const sep = Buffer.from("--" + boundary);
const parts = [];
let start = body.indexOf(sep) + sep.length + 2;
while (start < body.length) {
const end = body.indexOf(sep, start);
if (end === -1) break;
parts.push(body.slice(start, end - 2));
start = end + sep.length + 2;
}
for (const part of parts) {
const headerEnd = part.indexOf("\r\n\r\n");
if (headerEnd === -1) continue;
const header = part.slice(0, headerEnd).toString();
const content = part.slice(headerEnd + 4);
const nameMatch = header.match(/name="([^"]+)"/);
const fileMatch = header.match(/filename="([^"]+)"/);
if (nameMatch && fileMatch) {
files.push({ name: fileMatch[1], data: content });
}
}
return files;
}
const server = http.createServer((req, res) => {
if (req.method === "GET" && req.url === "/") {
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
return res.end(HTML);
}
if (req.method === "POST" && req.url === "/upload") {
const ct = req.headers["content-type"] || "";
const match = ct.match(/boundary=(.+)/);
if (!match) {
res.writeHead(400); return res.end("Kein Boundary");
}
const boundary = match[1];
const chunks = [];
req.on("data", c => chunks.push(c));
req.on("end", () => {
const body = Buffer.concat(chunks);
const files = parseMultipart(body, boundary);
if (!files.length) { res.writeHead(400); return res.end("Keine Datei gefunden"); }
const saved = [];
for (const file of files) {
const safeName = path.basename(file.name).replace(/[^a-zA-Z0-9._\- ()äöüÄÖÜß]/g, "_");
const dest = path.join(DIR, safeName);
fs.writeFileSync(dest, file.data);
saved.push(safeName);
console.log(`✅ Gespeichert: ${safeName}`);
}
res.writeHead(200); res.end(saved.join(", ") + " gespeichert.");
});
return;
}
res.writeHead(404); res.end("Not found");
});
server.listen(PORT, "0.0.0.0", () => {
console.log(`📸 Screens Upload Server läuft auf http://0.0.0.0:${PORT}`);
console.log(`📁 Speicherort: ${DIR}`);
});