Der Dreamcodes InfinityChat ist eine leistungsstarke und zugleich leicht installierbare Messanger Komplettlösung für Echtzeit-Kommunikation. Entwickelt mit modernsten Web-Technologien bietet es eine sichere, flexible und optisch ansprechende Plattform, die sowohl für kleine Teams als auch für große Unternehmen geeignet ist.
Mit umfassenden Funktionen wie WebSocket-Echtzeitkommunikation, Chaträumen, Dateiuploads und Admin-Dashboard ermöglicht InfinityChat eine nahtlose und professionelle Chat-Erfahrung. Sicherheit wird großgeschrieben durch verschlüsselte Passwörter, Zwei-Faktor-Authentifizierung, CSRF-Schutz und strikte CORS-Regeln.
Die Plattform lässt sich einfach an Unternehmensumgebungen anpassen, unterstützt LDAP/SSO und bietet ein durchdachtes Rollen- und Rechtekonzept für Benutzer, Moderatoren und Administratoren.
Hauptfunktionen im Überblick
- One-File-Installation
Nach dem ersten Start erstellt das Script automatisch alle erforderlichen Dateien und Verzeichnisse. Keine komplizierte Einrichtung notwendig. - Modernes Design
Professionelles UI/UX, responsive für Desktop, Tablet und Smartphone. - Sichere Anmeldung & Authentifizierung
- Verschlüsselte Passwörter (bcrypt)
- Zwei-Faktor-Authentifizierung (TOTP z. B. Google Authenticator)
- E-Mail-gestützte Backup-Codes für Notfälle
- Optional LDAP/SSO-Anbindung für Unternehmensumgebungen
- Echtzeit-Kommunikation mit WebSockets
Nachrichten werden sofort übertragen – keine Verzögerung, kein Polling.
Unterstützt sowohl TCP (ws/wss) als auch Unix Domain Sockets für höchste Performance. - Chaträume & Gruppen
- Öffentliche und private Räume
- Direktnachrichten
- Moderationsrechte für einzelne Räume
- Dateiverwaltung & Uploads
- Persistente Dateispeicherung
- Automatische Thumbnail-Erstellung für Bilder
- Upload-Filter (Typ & Größe) für maximale Sicherheit
- Rollen- und Rechtekonzept
- Benutzer: Chatten und Dateien austauschen
- Moderatoren: Inhalte prüfen, Meldungen bearbeiten
- Admins: Verwaltung von Nutzern, Räumen, Moderationsreports
- Admin Dashboard
- Übersichtliche Benutzerverwaltung
- Live-Statistiken: aktive Nutzer, Nachrichtenanzahl, Speicherverbrauch
- Reportsystem: gemeldete Nachrichten einsehen und bearbeiten
- Automatische Moderation gegen verbotene Wörter / Spam
- Sicherheit & Datenschutz
- CSRF-Schutz bei Formularen
- Strikte CORS-Regeln für Produktiv-Domains
- Prepared Statements in allen SQL-Abfragen
- HTTPS-Unterstützung für wss-Verbindungen
Einsatzmöglichkeiten
- Unternehmen: Interne Kommunikation, Projektteams, schnelle Abstimmung
- Communities: Eigene Chat-Server mit Gruppen- und Admin-Rechten
- Kunden-Support: Direkter Support-Chat mit Rollen- und Rechteverwaltung
- Bildungseinrichtungen: Virtuelle Arbeitsgruppen, Kurs-Chats, Projektkommunikation
Highlights für Unternehmen
- Single-Sign-On & LDAP-Unterstützung
- Erweiterbare API-Schnittstelle
- Rollenbasiertes Berechtigungsmodell
- Vollständige Kontrolle über Datenhaltung und Hosting
<?php
// configuration
$db_host = 'localhost';
$db_user = 'root';
$db_pass = '';
$db_name = 'chat_app';
$upload_max_filesize_bytes = 10 * 1024 * 1024; // 10MB
$allowed_upload_types = [
'image/jpeg', 'image/png', 'image/gif',
'application/pdf', 'text/plain'
];
$production_origins = [
'https://example.com'
];
$site_footer = 'http://www.deinedomain.tld';
$ipc_socket = __DIR__ . '/tmp/chat_ipc.sock'; // unix domain socket path for IPC between PHP and WS server
// moderation config
$banned_words = ['badword', 'violation', 'spamword']; // einfache Liste, anpassen
$auto_flag_threshold = 1; // number of matches to auto flag
// helpers
function create_file($path, $content) {
$dir = dirname($path);
if (!is_dir($dir)) mkdir($dir, 0755, true);
file_put_contents($path, $content);
}
// first run installer
if (!file_exists(__DIR__ . '/.installed')) {
@mkdir(__DIR__ . '/json', 0755, true);
@mkdir(__DIR__ . '/uploads', 0755, true);
@mkdir(__DIR__ . '/tmp', 0755, true);
if (!file_exists(__DIR__ . '/json/messages.json')) file_put_contents(__DIR__ . '/json/messages.json', json_encode([]));
if (!file_exists(__DIR__ . '/json/users.json')) file_put_contents(__DIR__ . '/json/users.json', json_encode([]));
// websocket server for CLI listens on TCP for browsers and on UNIX socket for IPC from PHP
$ws = <<<'PHP'
<?php
// Starten via CLI: php ws_server.php
set_time_limit(0);
$tcpAddress = '0.0.0.0';
$tcpPort = 9000;
$unixPath = __DIR__ . '/tmp/chat_ipc.sock';
if (file_exists($unixPath)) @unlink($unixPath);
// create tcp socket for browser connections
$tcp = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($tcp, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($tcp, $tcpAddress, $tcpPort);
socket_listen($tcp);
// create unix domain socket for IPC from PHP
$unix = socket_create(AF_UNIX, SOCK_STREAM, 0);
socket_bind($unix, $unixPath);
socket_listen($unix);
$clients = [$tcp, $unix];
$meta = []; // metadata for tcp client sockets keyed by resource id
function send_tcp($client, $msg){
@socket_write($client, $msg . "
");
}
function broadcast_room($clientsMeta, $room, $msg){
foreach ($clientsMeta as $cid => $info){
if (($info['room'] ?? null) === $room && isset($info['socket'])){
@socket_write($info['socket'], $msg . "
");
}
}
}
while (true){
$read = $clients;
// add client sockets to read array
foreach ($clients as $c){ if (is_resource($c)) continue; }
$read = $clients;
$all = $read;
// also add dynamic client sockets
foreach ($all as $s){ $read[] = $s; }
$write = null; $except = null;
if (socket_select($read, $write, $except, 1) < 1) continue;
foreach ($read as $sock){
if ($sock === $tcp){
$newsock = socket_accept($tcp);
if ($newsock){
$id = intval($newsock);
$clients[] = $newsock;
$meta[$id] = ['socket' => $newsock, 'room' => 'global', 'username' => null];
send_tcp($newsock, json_encode(['type' => 'welcome', 'msg' => 'connected']));
}
continue;
}
if ($sock === $unix){
$uconn = socket_accept($unix);
// read one line json from PHP and process broadcast
$data = '';
while ($b = socket_read($uconn, 2048)){
$data .= $b;
if (substr($b, -1) === "
") break;
}
$data = trim($data);
if ($data){
$p = json_decode($data, true);
if (is_array($p) && isset($p['type']) && $p['type'] === 'message'){
broadcast_room($meta, $p['room'] ?? 'global', json_encode(['type' => 'message', 'room' => $p['room'] ?? 'global', 'username' => $p['username'] ?? 'system', 'message' => $p['message'] ?? '', 'time' => date('c')]));
}
}
socket_close($uconn);
continue;
}
// existing tcp client sent data
$id = intval($sock);
$data = @socket_read($sock, 4096, PHP_NORMAL_READ);
if ($data === false || $data === ''){
if (isset($meta[$id]['socket'])) socket_close($meta[$id]['socket']);
unset($meta[$id]);
$key = array_search($sock, $clients, true);
if ($key !== false) unset($clients[$key]);
continue;
}
$data = trim($data);
if (!$data) continue;
$p = json_decode($data, true);
if (!is_array($p)) continue;
if ($p['type'] === 'join'){
$meta[$id]['room'] = $p['room'] ?? 'global';
$meta[$id]['username'] = $p['username'] ?? null;
send_tcp($sock, json_encode(['type' => 'joined', 'room' => $meta[$id]['room']]));
}
if ($p['type'] === 'message'){
$room = $p['room'] ?? 'global';
broadcast_room($meta, $room, json_encode(['type' => 'message', 'room' => $room, 'username' => $meta[$id]['username'] ?? 'anon', 'message' => $p['message'] ?? '', 'time' => date('c')]));
}
}
}
PHP;
create_file(__DIR__ . '/ws_server.php', $ws);
// API stub
create_file(__DIR__ . '/api.php', "<?php require_once __DIR__ . '/index.php'; exit; ");
// create DB and tables with extra columns for TOTP, backup codes, reports
$mysqli = @new mysqli($db_host, $db_user, $db_pass);
if ($mysqli && !$mysqli->connect_error){
$mysqli->query("CREATE DATABASE IF NOT EXISTS `" . $db_name . "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$mysqli->select_db($db_name);
$mysqli->query("CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
displayname VARCHAR(150) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role ENUM('user','moderator','admin') NOT NULL DEFAULT 'user',
totp_secret VARCHAR(64) NULL,
totp_enabled TINYINT(1) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$mysqli->query("CREATE TABLE IF NOT EXISTS rooms (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(150) NOT NULL UNIQUE,
displayname VARCHAR(255) NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$mysqli->query("CREATE TABLE IF NOT EXISTS messages (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
room VARCHAR(150) NOT NULL DEFAULT 'global',
sender_id INT UNSIGNED NULL,
sender_username VARCHAR(100) NULL,
message TEXT NULL,
attachment VARCHAR(255) NULL,
flagged TINYINT(1) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$mysqli->query("CREATE TABLE IF NOT EXISTS reports (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
message_id INT UNSIGNED NULL,
reporter_id INT UNSIGNED NULL,
reason VARCHAR(255) NULL,
handled TINYINT(1) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$mysqli->query("CREATE TABLE IF NOT EXISTS backup_codes (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
code_hash VARCHAR(255) NOT NULL,
used TINYINT(1) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$mysqli->query("INSERT IGNORE INTO rooms (name, displayname) VALUES ('global','Allgemeiner Raum')");
$mysqli->close();
}
file_put_contents(__DIR__ . '/.installed', 'installed on ' . date('c'));
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
exit;
}
session_start();
// CORS strict rules
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ($origin && in_array($origin, $production_origins, true)){
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-CSRF-Token');
} else {
// for local testing allow localhost
if ($origin && (strpos($origin, 'localhost') !== false || strpos($origin, '127.0.0.1') !== false)){
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-CSRF-Token');
}
}
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
$action = $_REQUEST['action'] ?? 'ui';
function db(){
global $db_host, $db_user, $db_pass, $db_name;
$m = new mysqli($db_host, $db_user, $db_pass, $db_name);
if ($m->connect_error){ http_response_code(500); echo json_encode(['error' => 'Datenbankverbindung fehlgeschlagen']); exit; }
$m->set_charset('utf8mb4');
return $m;
}
// csrf helpers
function csrf_token(){
if (empty($_SESSION['csrf_token'])){
$_SESSION['csrf_token'] = bin2hex(random_bytes(16));
}
return $_SESSION['csrf_token'];
}
function csrf_check(){
$header = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
$post = $_POST['_csrf'] ?? null;
$token = $header ?: $post;
if (!$token || !hash_equals($_SESSION['csrf_token'] ?? '', $token)){
http_response_code(403);
echo json_encode(['ok' => false, 'msg' => 'Ungültiges CSRF Token']);
exit;
}
}
// sanitizers
function clean_username($s){ $s = trim($s); $s = preg_replace('/[^a-zA-Z0-9_\.\@-]/u', '', $s); return substr($s,0,100); }
function clean_text($s, $max = 5000){ $s = trim($s); $s = strip_tags($s); return substr($s,0,$max); }
// TOTP helpers base32 and TOTP algorithm
function base32_encode_secret($bytes){
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$bits = '';
foreach (str_split($bytes) as $c) $bits .= str_pad(decbin(ord($c)), 8, '0', STR_PAD_LEFT);
$out = '';
while (strlen($bits) >= 5){ $chunk = substr($bits,0,5); $bits = substr($bits,5); $out .= $alphabet[bindec($chunk)]; }
if (strlen($bits) > 0) $out .= $alphabet[bindec(str_pad($bits,5,'0'))];
return $out;
}
function hotp($secret, $counter){
$key = base32_decode($secret);
$bin_counter = pack('N*', 0) . pack('N*', $counter);
$hash = hash_hmac('sha1', $bin_counter, $key, true);
$offset = ord($hash[19]) & 0xf;
$code = (ord($hash[$offset]) & 0x7f) << 24 | (ord($hash[$offset+1]) & 0xff) << 16 | (ord($hash[$offset+2]) & 0xff) << 8 | (ord($hash[$offset+3]) & 0xff);
return $code % 1000000;
}
function totp($secret, $timeSlice = null){
if ($timeSlice === null) $timeSlice = floor(time() / 30);
return str_pad(hotp($secret, $timeSlice), 6, '0', STR_PAD_LEFT);
}
function base32_decode($b32){
$b32 = strtoupper($b32);
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$bits = '';
foreach (str_split($b32) as $c){ $val = strpos($alphabet, $c); if ($val === false) continue; $bits .= str_pad(decbin($val),5,'0',STR_PAD_LEFT); }
$bytes = '';
while (strlen($bits) >= 8){ $chunk = substr($bits,0,8); $bits = substr($bits,8); $bytes .= chr(bindec($chunk)); }
return $bytes;
}
// optional LDAP auth
$ldap_enabled = false;
$ldap_server = 'ldap://ldap.example.com';
$ldap_base_dn = 'ou=users,dc=example,dc=com';
function ldap_auth($user, $pass){
global $ldap_server, $ldap_base_dn;
if (!extension_loaded('ldap')) return false;
$ldap = ldap_connect($ldap_server);
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
$filter = "(uid=" . ldap_escape($user, '', LDAP_ESCAPE_FILTER) . ")";
$res = ldap_search($ldap, $ldap_base_dn, $filter);
$entries = ldap_get_entries($ldap, $res);
if (empty($entries[0])) return false;
$dn = $entries[0]['dn'];
return @ldap_bind($ldap, $dn, $pass);
}
// helper: generate backup codes
function generate_backup_codes($count = 6){
$codes = [];
for ($i=0;$i<$count;$i++){
$codes[] = substr(bin2hex(random_bytes(4)),0,8);
}
return $codes;
}
function store_backup_codes($user_id, $codes){
$mysqli = db();
$stmt = $mysqli->prepare('INSERT INTO backup_codes (user_id, code_hash) VALUES (?, ?)');
foreach ($codes as $c){ $hash = password_hash($c, PASSWORD_DEFAULT); $stmt->bind_param('is', $user_id, $hash); $stmt->execute(); }
$stmt->close(); $mysqli->close();
}
function consume_backup_code($user_id, $code){
$mysqli = db();
$res = $mysqli->query('SELECT id, code_hash FROM backup_codes WHERE user_id = ' . intval($user_id) . ' AND used = 0');
while ($r = $res->fetch_assoc()){
if (password_verify($code, $r['code_hash'])){
$stmt = $mysqli->prepare('UPDATE backup_codes SET used = 1 WHERE id = ?'); $stmt->bind_param('i', $r['id']); $stmt->execute(); $stmt->close(); $mysqli->close(); return true;
}
}
$mysqli->close(); return false;
}
// endpoints
if ($action === 'register'){
csrf_check();
$username = clean_username($_POST['username'] ?? '');
$display = trim($_POST['display'] ?? $username);
$password = $_POST['password'] ?? '';
if ($username === '' || $password === ''){ echo json_encode(['ok' => false, 'msg' => 'Benutzername und Passwort erforderlich']); exit; }
if (strlen($password) < 8){ echo json_encode(['ok' => false, 'msg' => 'Passwort zu kurz']); exit; }
$hash = password_hash($password, PASSWORD_DEFAULT);
$mysqli = db();
$stmt = $mysqli->prepare('INSERT INTO users (username, displayname, password_hash) VALUES (?, ?, ?)');
$stmt->bind_param('sss', $username, $display, $hash);
$ok = $stmt->execute();
if (!$ok){ echo json_encode(['ok' => false, 'msg' => 'Benutzer existiert bereits oder fehler']); $stmt->close(); $mysqli->close(); exit; }
$userid = $mysqli->insert_id;
$stmt->close(); $mysqli->close();
$_SESSION['user_id'] = $userid; $_SESSION['username'] = $username; $_SESSION['displayname'] = $display; $_SESSION['role'] = 'user';
echo json_encode(['ok' => true, 'username' => $username, 'display' => $display]); exit;
}
if ($action === 'login'){
$username = clean_username($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
// sso header check
$sso_user = $_SERVER['HTTP_X_SSO_USER'] ?? null;
if ($sso_user){
$username = clean_username($sso_user);
// trust sso then create session if user exists
$mysqli = db();
$stmt = $mysqli->prepare('SELECT id, username, displayname, role, totp_enabled FROM users WHERE username = ? LIMIT 1');
$stmt->bind_param('s', $username);
$stmt->execute(); $res = $stmt->get_result();
if ($row = $res->fetch_assoc()){
$_SESSION['user_id'] = $row['id']; $_SESSION['username'] = $row['username']; $_SESSION['displayname'] = $row['displayname']; $_SESSION['role'] = $row['role'];
echo json_encode(['ok' => true]); $stmt->close(); $mysqli->close(); exit;
}
$stmt->close(); $mysqli->close();
}
// ldap fallback
global $ldap_enabled;
if ($ldap_enabled){
if (ldap_auth($username, $password)){
// create local user if not exists
$mysqli = db();
$stmt = $mysqli->prepare('SELECT id, username, displayname, role FROM users WHERE username = ? LIMIT 1');
$stmt->bind_param('s', $username); $stmt->execute(); $res = $stmt->get_result();
if ($row = $res->fetch_assoc()){
$_SESSION['user_id'] = $row['id']; $_SESSION['username'] = $row['username']; $_SESSION['displayname'] = $row['displayname']; $_SESSION['role'] = $row['role'];
echo json_encode(['ok' => true]); $stmt->close(); $mysqli->close(); exit;
} else {
$display = $username;
$pw = password_hash(random_bytes(16), PASSWORD_DEFAULT);
$ins = $mysqli->prepare('INSERT INTO users (username, displayname, password_hash) VALUES (?, ?, ?)');
$ins->bind_param('sss', $username, $display, $pw); $ins->execute();
$_SESSION['user_id'] = $ins->insert_id; $_SESSION['username'] = $username; $_SESSION['displayname'] = $display; $_SESSION['role'] = 'user';
echo json_encode(['ok' => true]); $ins->close(); $mysqli->close(); exit;
}
}
}
// local login
$mysqli = db();
$stmt = $mysqli->prepare('SELECT id, username, displayname, password_hash, role, totp_enabled, totp_secret FROM users WHERE username = ? LIMIT 1');
$stmt->bind_param('s', $username); $stmt->execute(); $res = $stmt->get_result();
if ($row = $res->fetch_assoc()){
if (password_verify($password, $row['password_hash'])){
// if user has totp enabled require code
if (intval($row['totp_enabled']) === 1){
// store temp flag and prompt for code
$_SESSION['tmp_user_id'] = $row['id']; $_SESSION['tmp_username'] = $row['username'];
echo json_encode(['ok' => true, 'require_totp' => true]);
} else {
$_SESSION['user_id'] = $row['id']; $_SESSION['username'] = $row['username']; $_SESSION['displayname'] = $row['displayname']; $_SESSION['role'] = $row['role'];
echo json_encode(['ok' => true]);
}
} else echo json_encode(['ok' => false, 'msg' => 'Ungültige Zugangsdaten']);
} else echo json_encode(['ok' => false, 'msg' => 'Ungültige Zugangsdaten']);
$stmt->close(); $mysqli->close(); exit;
}
// verify totp code after password
if ($action === 'verify_totp'){
$code = trim($_POST['code'] ?? '');
if (empty($_SESSION['tmp_user_id'])){ echo json_encode(['ok' => false, 'msg' => 'Keine Authentifizierung ausstehend']); exit; }
$uid = intval($_SESSION['tmp_user_id']);
$mysqli = db();
$stmt = $mysqli->prepare('SELECT id, username, totp_secret FROM users WHERE id = ? LIMIT 1');
$stmt->bind_param('i', $uid); $stmt->execute(); $res = $stmt->get_result();
if ($row = $res->fetch_assoc()){
$secret = $row['totp_secret'];
if ($secret && (totp($secret) === $code || consume_backup_code($uid, $code))){
// complete login
$_SESSION['user_id'] = $row['id']; $_SESSION['username'] = $row['username']; // load role
$r2 = $mysqli->prepare('SELECT role, displayname FROM users WHERE id = ?'); $r2->bind_param('i', $uid); $r2->execute(); $rr = $r2->get_result()->fetch_assoc(); $_SESSION['role'] = $rr['role']; $_SESSION['displayname'] = $rr['displayname'];
unset($_SESSION['tmp_user_id'], $_SESSION['tmp_username']);
echo json_encode(['ok' => true]);
} else echo json_encode(['ok' => false, 'msg' => 'Ungültiger Code oder Backup Code']);
} else echo json_encode(['ok' => false, 'msg' => 'Benutzer nicht gefunden']);
$stmt->close(); $mysqli->close(); exit;
}
// generate and email backup codes
if ($action === 'generate_backup_codes'){
if (!isset($_SESSION['user_id'])){ echo json_encode(['ok' => false, 'msg' => 'Nicht angemeldet']); exit; }
csrf_check();
$uid = intval($_SESSION['user_id']);
$codes = generate_backup_codes(8);
store_backup_codes($uid, $codes);
// try to send email to user - assumes users have email column; if not, admin can fetch codes
$mysqli = db(); $stmt = $mysqli->prepare('SELECT displayname FROM users WHERE id = ?'); $stmt->bind_param('i', $uid); $stmt->execute(); $res = $stmt->get_result(); $emailtext = implode("
", $codes); $stmt->close(); $mysqli->close();
// NOTE: mail() may not be configured. Adjust to your mail system.
@mail('admin@example.com', 'Your Backup Codes', "Backup Codes:
" . $emailtext);
echo json_encode(['ok' => true, 'codes' => $codes]); exit;
}
if ($action === 'logout'){
session_unset(); session_destroy(); echo json_encode(['ok' => true]); exit;
}
// upload with thumbnail generation
if ($action === 'upload'){
if (!isset($_SESSION['user_id'])){ echo json_encode(['ok' => false, 'msg' => 'Nicht angemeldet']); exit; }
csrf_check();
if (empty($_FILES['file'])){ echo json_encode(['ok' => false, 'msg' => 'Keine Datei']); exit; }
$file = $_FILES['file']; if ($file['error'] !== UPLOAD_ERR_OK){ echo json_encode(['ok' => false, 'msg' => 'Upload Fehler']); exit; }
global $upload_max_filesize_bytes, $allowed_upload_types;
if ($file['size'] > $upload_max_filesize_bytes){ echo json_encode(['ok' => false, 'msg' => 'Datei zu groß']); exit; }
$finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file($finfo, $file['tmp_name']); finfo_close($finfo);
if (!in_array($mime, $allowed_upload_types, true)){ echo json_encode(['ok' => false, 'msg' => 'Dateityp nicht erlaubt']); exit; }
$ext = pathinfo($file['name'], PATHINFO_EXTENSION); $safe = bin2hex(random_bytes(12)) . '.' . $ext; $dest = __DIR__ . '/uploads/' . $safe;
if (!move_uploaded_file($file['tmp_name'], $dest)){ echo json_encode(['ok' => false, 'msg' => 'Konnte Datei nicht speichern']); exit; }
$thumb = null;
if (strpos($mime, 'image/') === 0 && extension_loaded('gd')){
$thumbname = 'thumb_' . $safe;
$thumbpath = __DIR__ . '/uploads/' . $thumbname;
list($w, $h) = getimagesize($dest);
$nw = 240; $nh = intval($h * $nw / $w);
$dst = imagecreatetruecolor($nw, $nh);
if ($mime === 'image/jpeg'){ $src = imagecreatefromjpeg($dest); }
elseif ($mime === 'image/png'){ $src = imagecreatefrompng($dest); }
elseif ($mime === 'image/gif'){ $src = imagecreatefromgif($dest); }
else $src = null;
if ($src){ imagecopyresampled($dst, $src, 0,0,0,0, $nw, $nh, $w, $h); imagejpeg($dst, $thumbpath, 80); imagedestroy($dst); imagedestroy($src); $thumb = 'uploads/' . $thumbname; }
}
echo json_encode(['ok' => true, 'file' => 'uploads/' . $safe, 'thumb' => $thumb, 'mime' => $mime]); exit;
}
// automatic moderation check helper
function moderation_check($text){
global $banned_words, $auto_flag_threshold;
$found = 0;
foreach ($banned_words as $w){ if (stripos($text, $w) !== false) $found++; }
return $found >= $auto_flag_threshold;
}
// send message with IPC to websocket server and moderation & reporting
if ($action === 'send'){
if (!isset($_SESSION['user_id'])){ echo json_encode(['ok' => false, 'msg' => 'Nicht angemeldet']); exit; }
csrf_check();
$room = clean_username($_POST['room'] ?? 'global');
$msg = clean_text($_POST['msg'] ?? '');
$attachment = null; if (!empty($_POST['attachment'])) $attachment = basename($_POST['attachment']);
if ($msg === '' && $attachment === null){ echo json_encode(['ok' => false, 'msg' => 'Nachricht leer']); exit; }
$flagged = moderation_check($msg) ? 1 : 0;
$mysqli = db();
$stmt = $mysqli->prepare('INSERT INTO messages (room, sender_id, sender_username, message, attachment, flagged) VALUES (?, ?, ?, ?, ?, ?)');
$uid = intval($_SESSION['user_id']); $uname = $_SESSION['username'];
$stmt->bind_param('sisssi', $room, $uid, $uname, $msg, $attachment, $flagged);
$stmt->execute(); $id = $mysqli->insert_id; $stmt->close();
if ($flagged){
$rep = $mysqli->prepare('INSERT INTO reports (message_id, reporter_id, reason) VALUES (?, ?, ?)');
$reason = 'Automatische Moderation'; $rep->bind_param('iis', $id, $uid, $reason); $rep->execute(); $rep->close();
}
$mysqli->close();
// json backup
$file = __DIR__ . '/json/messages.json'; $arr = json_decode(file_get_contents($file), true); if (!is_array($arr)) $arr = [];
$arr[] = ['id' => $id, 'room' => $room, 'sender_id' => $uid, 'sender_username' => $uname, 'message' => $msg, 'attachment' => $attachment, 'flagged' => $flagged, 'created_at' => date('c')];
file_put_contents($file, json_encode($arr, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
// send notification to websocket server via unix domain socket if available
global $ipc_socket;
if (file_exists($ipc_socket)){
$sock = socket_create(AF_UNIX, SOCK_STREAM, 0);
if ($sock && @socket_connect($sock, $ipc_socket)){
$payload = json_encode(['type' => 'message', 'room' => $room, 'username' => $uname, 'message' => $msg]);
@socket_write($sock, $payload . "
");
socket_close($sock);
}
}
echo json_encode(['ok' => true, 'id' => $id, 'flagged' => $flagged]); exit;
}
// report message endpoint (user reports a message)
if ($action === 'report'){
if (!isset($_SESSION['user_id'])){ echo json_encode(['ok' => false, 'msg' => 'Nicht angemeldet']); exit; }
csrf_check(); $mid = intval($_POST['message_id'] ?? 0); $reason = clean_text($_POST['reason'] ?? ''); if (!$mid) { echo json_encode(['ok' => false]); exit; }
$mysqli = db(); $stmt = $mysqli->prepare('INSERT INTO reports (message_id, reporter_id, reason) VALUES (?, ?, ?)'); $uid = intval($_SESSION['user_id']); $stmt->bind_param('iis', $mid, $uid, $reason); $stmt->execute(); $stmt->close(); $mysqli->close(); echo json_encode(['ok' => true]); exit;
}
// fetch messages
if ($action === 'fetch'){
if (!isset($_SESSION['user_id'])){ echo json_encode(['ok' => false, 'msg' => 'Nicht angemeldet']); exit; }
$room = clean_username($_GET['room'] ?? 'global'); $limit = intval($_GET['limit'] ?? 200);
$mysqli = db();
$stmt = $mysqli->prepare('SELECT id, room, sender_username, message, attachment, flagged, created_at FROM messages WHERE room = ? ORDER BY id DESC LIMIT ?');
$stmt->bind_param('si', $room, $limit); $stmt->execute(); $res = $stmt->get_result(); $out = [];
while ($r = $res->fetch_assoc()) $out[] = $r; $stmt->close(); $mysqli->close();
echo json_encode(array_reverse($out)); exit;
}
// rooms
if ($action === 'rooms'){
$mysqli = db(); $res = $mysqli->query('SELECT name, displayname FROM rooms ORDER BY displayname ASC'); $out = [];
while ($r = $res->fetch_assoc()) $out[] = $r; $mysqli->close(); echo json_encode($out); exit;
}
// admin endpoints
function require_role($role){ if (!isset($_SESSION['role'])){ http_response_code(403); echo json_encode(['ok' => false, 'msg' => 'Forbidden']); exit; } if ($role === 'admin' && $_SESSION['role'] !== 'admin'){ http_response_code(403); echo json_encode(['ok' => false, 'msg' => 'Forbidden']); exit; } }
if ($action === 'admin_list_users'){
require_role('admin'); $mysqli = db(); $res = $mysqli->query('SELECT id, username, displayname, role, totp_enabled, created_at FROM users ORDER BY created_at DESC LIMIT 1000'); $out = [];
while ($r = $res->fetch_assoc()) $out[] = $r; $mysqli->close(); echo json_encode($out); exit;
}
if ($action === 'admin_delete_message'){
require_role('admin'); csrf_check(); $id = intval($_POST['id'] ?? 0); if (!$id){ echo json_encode(['ok' => false]); exit; }
$mysqli = db(); $stmt = $mysqli->prepare('DELETE FROM messages WHERE id = ?'); $stmt->bind_param('i', $id); $stmt->execute(); $stmt->close(); $mysqli->close(); echo json_encode(['ok' => true]); exit;
}
// admin set role
if ($action === 'admin_set_role'){
require_role('admin'); csrf_check(); $uid = intval($_POST['id'] ?? 0); $role = $_POST['role'] ?? 'user'; if (!in_array($role, ['user','moderator','admin'])) $role = 'user'; $mysqli = db(); $stmt = $mysqli->prepare('UPDATE users SET role = ? WHERE id = ?'); $stmt->bind_param('si', $role, $uid); $stmt->execute(); $stmt->close(); $mysqli->close(); echo json_encode(['ok' => true]); exit;
}
// admin reports list
if ($action === 'admin_reports'){
require_role('admin'); $mysqli = db(); $res = $mysqli->query('SELECT r.id, r.message_id, r.reporter_id, r.reason, r.handled, r.created_at, m.message, m.sender_username FROM reports r LEFT JOIN messages m ON r.message_id = m.id ORDER BY r.created_at DESC LIMIT 500'); $out = [];
while ($r = $res->fetch_assoc()) $out[] = $r; $mysqli->close(); echo json_encode($out); exit;
}
// admin mark report handled
if ($action === 'admin_handle_report'){
require_role('admin'); csrf_check(); $id = intval($_POST['id'] ?? 0); if (!$id){ echo json_encode(['ok' => false]); exit; }
$mysqli = db(); $stmt = $mysqli->prepare('UPDATE reports SET handled = 1 WHERE id = ?'); $stmt->bind_param('i', $id); $stmt->execute(); $stmt->close(); $mysqli->close(); echo json_encode(['ok' => true]); exit;
}
// statistics endpoint for admin
if ($action === 'admin_stats'){
require_role('admin'); $mysqli = db();
$res1 = $mysqli->query('SELECT COUNT(*) AS users FROM users'); $users = $res1->fetch_assoc()['users'];
$res2 = $mysqli->query("SELECT room, COUNT(*) AS cnt FROM messages GROUP BY room ORDER BY cnt DESC LIMIT 10"); $rooms = []; while ($r = $res2->fetch_assoc()) $rooms[] = $r;
// storage usage
$size = 0; foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__ . '/uploads', FilesystemIterator::SKIP_DOTS)) as $f){ $size += $f->getSize(); }
$mysqli->close(); echo json_encode(['users' => intval($users), 'rooms' => $rooms, 'storage_bytes' => $size]); exit;
}
// ui main with admin dashboard embedded
if ($action === 'ui'){
$me = $_SESSION['username'] ?? ''; $display = $_SESSION['displayname'] ?? $me; $csrf = csrf_token(); $role = $_SESSION['role'] ?? '';
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dreamcodes InfinityChat</title>
<style>
:root{ --bg:#f5f7fb; --accent:#0b64d4; --card:#fff; --muted:#6b7280 }
html,body{height:100%;margin:0;font-family:Inter,system-ui,Arial;background:var(--bg)}
.wrap{display:grid;grid-template-columns:320px 1fr;height:100vh;gap:28px;padding:28px}
.panel{background:var(--card);border-radius:12px;box-shadow:0 6px 18px rgba(15,23,42,0.06);overflow:hidden}
.sidebar{padding:18px;height:calc(100vh - 56px);box-sizing:border-box}
.brand h1{margin:0;font-size:18px}
.contacts{overflow:auto;max-height:calc(100% - 180px)}
.contact{padding:10px;border-radius:8px;cursor:pointer;display:flex;justify-content:space-between;align-items:center}
.header{padding:18px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #eef2ff}
.chatbox{padding:18px;flex:1;overflow:auto}
.composer{padding:14px;border-top:1px solid #eef2ff;display:flex;gap:10px;align-items:center}
.msg{max-width:70%;padding:10px 12px;border-radius:10px;margin:8px 0}
.msg.me{margin-left:auto;background:linear-gradient(180deg,#daf6ff,#bfefff)}
.msg.they{margin-right:auto;background:#f7f8fb}
.footer{padding:10px 0;text-align:center;color:var(--muted);font-size:13px}
.admin-panel{padding:16px}
table{width:100%;border-collapse:collapse}
th,td{padding:8px;border-bottom:1px solid #eee;text-align:left}
.btn{padding:6px 8px;border-radius:6px;border:1px solid #ddd;background:#fff;cursor:pointer}
.stats{display:flex;gap:12px;margin-bottom:12px}
.stat-card{background:#f8fafc;padding:12px;border-radius:8px;flex:1}
</style>
</head>
<body>
<div class="wrap">
<div class="panel sidebar">
<div class="sidebar">
<div class="brand"><div><h1>Dreamcodes InfinityChat</h1><div style="font-size:13px;color:#6b7280">Angemeldet als <strong id="meName"><?php echo htmlspecialchars($display); ?></strong></div></div></div>
<div style="margin:12px 0"><input id="roomFilter" placeholder="Raum wählen oder neu erstellen" style="width:100%;padding:10px;border-radius:8px;border:1px solid #e6eefc"></div>
<div style="display:flex;gap:8px;margin-bottom:10px"><button id="btnNewRoom" class="btn">Neuen Raum</button><button id="btnUsers" class="btn">Benutzer</button></div>
<div class="contacts" id="roomList"></div>
<?php if ($role === 'admin'){ echo '<div style="margin-top:12px"><button id="adminBtn" class="btn">Admin Bereich</button></div>'; } ?>
<div style="margin-top:12px"><a href="#" id="logoutBtn">Abmelden</a></div>
</div>
</div>
<div class="panel main">
<div class="header"><div><h2 id="roomTitle">Wähle einen Raum</h2><div style="font-size:13px;color:#6b7280" id="roomSub">Tippe auf einen Raum um zu starten</div></div><div><input id="fileInput" type="file" style="display:none"><button id="attachBtn" class="btn">Anhang</button></div></div>
<div class="chatbox" id="chatWindow"><div style="font-size:13px;color:#6b7280">Noch keine Unterhaltung</div></div>
<div class="composer"><input type="text" id="msgInput" placeholder="Nachricht eingeben" style="flex:1;padding:10px;border-radius:8px;border:1px solid #e6eefc"><button id="sendBtn" class="btn">Senden</button></div>
<div class="footer">
© <?php echo date("Y"); ?>
<a href="http://www.dreamcodes.net" target="_blank">Dreamcodes</a> —
Besuche auch <a href="<?php echo $site_footer; ?>" target="_blank"><?php echo $site_footer; ?></a>
</div>
</div>
</div>
<script>
const csrf = '<?php echo $csrf; ?>';
let me = '<?php echo addslashes($me); ?>'; let room = 'global'; let attachment = null; let role = '<?php echo $role; ?>';
async function api(path, data, method = 'GET'){
if (method === 'GET'){
const q = new URLSearchParams(data).toString(); const res = await fetch(path + '?' + q, { credentials: 'include' }); return res.json();
} else {
const fd = new FormData(); for (const k in data) fd.append(k, data[k]); fd.append('_csrf', csrf); const res = await fetch(path, { method: 'POST', body: fd, credentials: 'include' }); return res.json();
}
}
async function loadRooms(){ const r = await api('?', { action: 'rooms' }); const list = document.getElementById('roomList'); list.innerHTML = ''; r.forEach(x => { const el = document.createElement('div'); el.className = 'contact'; el.textContent = x.displayname + ' (' + x.name + ')'; el.onclick = () => { selectRoom(x.name, x.displayname); }; list.appendChild(el); }); }
async function selectRoom(name, display){ room = name; document.getElementById('roomTitle').textContent = display; fetchMessages(); joinWsRoom(); }
async function fetchMessages(){ const res = await api('?', { action: 'fetch', room: room, limit: 200 }); const w = document.getElementById('chatWindow'); w.innerHTML = ''; res.forEach(m => { const d = document.createElement('div'); d.className = 'msg ' + ((m.sender_username === me) ? 'me' : 'they'); let html = '<div style="font-size:0.85rem;margin-bottom:6px"><strong>' + m.sender_username + '</strong></div>'; html += '<div>' + escapeHtml(m.message || '') + '</div>'; if (m.attachment) html += '<div><a href="' + m.attachment + '" target="_blank">Anhang öffnen</a></div>'; if (m.flagged) html += '<div style="color:#b91c1c;font-weight:600">Diese Nachricht wurde markiert</div>'; html += '<div style="font-size:12px;color:#8b93a7">' + m.created_at + '</div>'; d.innerHTML = html; w.appendChild(d); }); w.scrollTop = w.scrollHeight; }
function escapeHtml(s){ return s.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); }
document.getElementById('sendBtn').onclick = async function(){ const msg = document.getElementById('msgInput').value.trim(); if (!msg && !attachment) return; const res = await api('?', { action: 'send', room: room, msg: msg, attachment: attachment }, 'POST'); if (res.ok){ document.getElementById('msgInput').value = ''; attachment = null; fetchMessages(); sendWsMessage({ type: 'message', room: room, message: msg }); if (res.flagged) alert('Hinweis: Ihre Nachricht wurde automatisch markiert und wird überprüft.'); } else alert(res.msg || 'Fehler'); };
document.getElementById('attachBtn').onclick = function(){ document.getElementById('fileInput').click(); };
document.getElementById('fileInput').addEventListener('change', async function(e){ const f = e.target.files[0]; if (!f) return; if (f.size > <?php echo $upload_max_filesize_bytes; ?>) { alert('Datei zu groß'); return; } const fd = new FormData(); fd.append('action','upload'); fd.append('file', f); fd.append('_csrf', csrf); const res = await fetch('?', { method: 'POST', body: fd, credentials: 'include' }); const j = await res.json(); if (j.ok) { attachment = j.file; alert('Upload erfolgreich'); } else alert(j.msg || 'Upload fehlgeschlagen'); });
document.getElementById('btnNewRoom').onclick = async function(){ const name = prompt('Name des neuen raums'); if (!name) return; const dn = prompt('Anzeigename'); const fd = new FormData(); fd.append('action','create_room'); fd.append('name', name); fd.append('displayname', dn || name); fd.append('_csrf', csrf); const res = await fetch('?', { method: 'POST', body: fd, credentials: 'include' }); const j = await res.json(); if (j.ok) loadRooms(); else alert(j.msg || 'Fehler'); };
document.getElementById('logoutBtn').onclick = async function(){ const r = await api('?', { action: 'logout' }); if (r.ok) location.reload(); };
// admin button
document.getElementById('adminBtn')?.addEventListener('click', async function(){ if (role !== 'admin') return; const main = document.querySelector('.panel.main'); main.innerHTML = '<div class="admin-panel"><h2>Admin Dashboard</h2><div class="stats"><div class="stat-card" id="statUsers">Lade...</div><div class="stat-card" id="statStorage">Lade...</div><div class="stat-card" id="statRooms">Lade...</div></div><div id="adminContent">Lade Berichte...</div></div>'; const s = await api('?', { action: 'admin_stats' }); document.getElementById('statUsers').innerHTML = '<strong>Benutzer</strong><div>' + s.users + '</div>'; document.getElementById('statStorage').innerHTML = '<strong>Speicher</strong><div>' + Math.round(s.storage_bytes/1024) + ' KB</div>'; let roomsHtml = '<strong>Top Räume</strong><ul>'; s.rooms.forEach(r => { roomsHtml += '<li>' + r.room + ': ' + r.cnt + '</li>'; }); roomsHtml += '</ul>'; document.getElementById('statRooms').innerHTML = roomsHtml; const reports = await api('?', { action: 'admin_reports' }); let html = '<h3>Meldungen</h3><table><tr><th>ID</th><th>Nachricht</th><th>Grund</th><th>Reporter</th><th>Aktion</th></tr>'; reports.forEach(r => { html += '<tr><td>' + r.id + '</td><td>' + (r.message ? r.message.substring(0,80) : '') + '</td><td>' + r.reason + '</td><td>' + r.reporter_id + '</td><td><button class="handleBtn" data-id="'+r.id+'">Erledigt</button></td></tr>'; }); html += '</table>'; document.getElementById('adminContent').innerHTML = html; document.querySelectorAll('.handleBtn').forEach(b => b.addEventListener('click', async function(){ const id = this.getAttribute('data-id'); const res = await api('?', { action: 'admin_handle_report', id: id }, 'POST'); if (res.ok) this.parentElement.parentElement.style.opacity = 0.5; else alert('Fehler'); })); });
// websocket client
let ws = null;
function connectWs(){
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
ws = new WebSocket(proto + '://' + location.hostname + ':9000');
ws.onopen = function(){ console.log('ws open'); joinWsRoom(); };
ws.onmessage = function(e){ try { const d = JSON.parse(e.data); if (d.type === 'message') fetchMessages(); } catch(err){} };
ws.onclose = function(){ setTimeout(connectWs, 3000); };
}
function joinWsRoom(){ if (!ws || ws.readyState !== 1) return; ws.send(JSON.stringify({ type: 'join', room: room, username: me })); }
function sendWsMessage(obj){ if (!ws || ws.readyState !== 1) return; ws.send(JSON.stringify(obj)); }
connectWs();
setInterval(function(){ if (room) fetchMessages(); }, 5000);
loadRooms();
</script>
</body>
</html>
<?php
exit;
}
// create room
if ($action === 'create_room'){
if (!isset($_SESSION['user_id'])){ echo json_encode(['ok' => false, 'msg' => 'Nicht angemeldet']); exit; }
csrf_check(); $name = clean_username($_POST['name'] ?? ''); $displayname = clean_text($_POST['displayname'] ?? $name, 200); if ($name === ''){ echo json_encode(['ok' => false, 'msg' => 'Name erforderlich']); exit; }
$mysqli = db(); $stmt = $mysqli->prepare('INSERT IGNORE INTO rooms (name, displayname) VALUES (?, ?)'); $stmt->bind_param('ss', $name, $displayname); $ok = $stmt->execute(); if (!$ok) echo json_encode(['ok' => false, 'msg' => 'Fehler']); else echo json_encode(['ok' => true]); $stmt->close(); $mysqli->close(); exit;
}
// admin delete user endpoint
if ($action === 'admin_delete_user'){
require_role('admin'); csrf_check(); $id = intval($_POST['id'] ?? 0); if (!$id){ echo json_encode(['ok' => false]); exit; }
$mysqli = db(); $stmt = $mysqli->prepare('DELETE FROM users WHERE id = ?'); $stmt->bind_param('i', $id); $stmt->execute(); $stmt->close(); $mysqli->close(); echo json_encode(['ok' => true]); exit;
}
http_response_code(404); echo json_encode(['ok' => false, 'msg' => 'Nicht gefunden']);
?>