1359 lines
45 KiB
JavaScript
1359 lines
45 KiB
JavaScript
import crypto from 'crypto';
|
|
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';
|
|
import { join } from 'path';
|
|
import axios from 'axios';
|
|
|
|
const TIMEOUT_SECONDS = 1800; // 30 Minuten
|
|
|
|
class Client {
|
|
constructor(sessionId) {
|
|
this.sessionId = sessionId;
|
|
this.gender = '';
|
|
this.country = '';
|
|
this.isoCountryCode = '';
|
|
this.userName = '';
|
|
this.age = 0;
|
|
this.conversations = {};
|
|
this.lastActivity = new Date();
|
|
this.loginTimeStamp = new Date();
|
|
this.blockedUsers = new Set();
|
|
this.socket = null; // Socket.IO Socket-Objekt
|
|
this.chatAuth = null; // { username, rights: Set<string> }
|
|
this.pendingChatLogin = null; // { step: 'username'|'password', username: string }
|
|
}
|
|
|
|
setActivity() {
|
|
this.lastActivity = new Date();
|
|
}
|
|
|
|
activitiesTimedOut() {
|
|
const secondsSinceActivity = (new Date() - this.lastActivity) / 1000;
|
|
return secondsSinceActivity > TIMEOUT_SECONDS;
|
|
}
|
|
|
|
remainingSecondsToTimeout() {
|
|
const secondsSinceActivity = (new Date() - this.lastActivity) / 1000;
|
|
return Math.max(0, TIMEOUT_SECONDS - secondsSinceActivity);
|
|
}
|
|
|
|
currentlyLoggedInSeconds() {
|
|
return Math.floor((new Date() - this.loginTimeStamp) / 1000);
|
|
}
|
|
|
|
toJSON() {
|
|
return {
|
|
sessionId: this.sessionId,
|
|
userName: this.userName,
|
|
gender: this.gender,
|
|
country: this.country,
|
|
isoCountryCode: this.isoCountryCode,
|
|
age: this.age
|
|
};
|
|
}
|
|
}
|
|
|
|
const CHAT_USERS_FILE_NAME = 'chat-users.json';
|
|
const CHAT_RIGHTS = {
|
|
STAT: 'stat',
|
|
KICK: 'kick'
|
|
};
|
|
|
|
function getLogsDir(__dirname) {
|
|
return join(__dirname, '../logs');
|
|
}
|
|
|
|
function ensureLogsDir(__dirname) {
|
|
const logsDir = getLogsDir(__dirname);
|
|
if (!existsSync(logsDir)) {
|
|
mkdirSync(logsDir, { recursive: true });
|
|
}
|
|
return logsDir;
|
|
}
|
|
|
|
function getChatUsersPath(__dirname) {
|
|
return join(ensureLogsDir(__dirname), CHAT_USERS_FILE_NAME);
|
|
}
|
|
|
|
function sha256(value) {
|
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
}
|
|
|
|
function ensureChatUsersFile(__dirname) {
|
|
const usersPath = getChatUsersPath(__dirname);
|
|
if (existsSync(usersPath)) {
|
|
return;
|
|
}
|
|
// Security: never create predictable default credentials.
|
|
// Admin users must be configured explicitly in logs/chat-users.json.
|
|
writeFileSync(usersPath, '[]\n', 'utf-8');
|
|
console.warn(
|
|
`[Auth] ${CHAT_USERS_FILE_NAME} wurde neu erstellt. Bitte mindestens einen Admin-User mit Passwort-Hash konfigurieren.`
|
|
);
|
|
}
|
|
|
|
function loadChatUsers(__dirname) {
|
|
ensureChatUsersFile(__dirname);
|
|
const usersPath = getChatUsersPath(__dirname);
|
|
const raw = readFileSync(usersPath, 'utf-8').trim();
|
|
if (!raw) return [];
|
|
|
|
let users = [];
|
|
try {
|
|
users = JSON.parse(raw);
|
|
} catch (error) {
|
|
throw new Error(`Ungültige ${CHAT_USERS_FILE_NAME}: ${error.message}`);
|
|
}
|
|
|
|
if (!Array.isArray(users)) {
|
|
throw new Error(`${CHAT_USERS_FILE_NAME} muss ein Array sein`);
|
|
}
|
|
|
|
return users
|
|
.filter((entry) => entry && typeof entry.username === 'string')
|
|
.map((entry) => ({
|
|
username: entry.username.trim(),
|
|
passwordHash: typeof entry.passwordHash === 'string' ? entry.passwordHash.trim() : '',
|
|
rights: Array.isArray(entry.rights) ? entry.rights.map((r) => String(r).toLowerCase()) : []
|
|
}))
|
|
.filter((entry) => entry.username && entry.passwordHash);
|
|
}
|
|
|
|
function parseLoginRecord(line) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed) return null;
|
|
const parts = trimmed.split(',');
|
|
if (parts.length < 5) return null;
|
|
|
|
const timestamp = parts[0].trim();
|
|
const userName = parts[1].trim();
|
|
const gender = parts[parts.length - 1].trim();
|
|
const ageRaw = parts[parts.length - 2].trim();
|
|
const country = parts.slice(2, parts.length - 2).join(',').trim();
|
|
const age = Number.parseInt(ageRaw, 10);
|
|
|
|
const date = new Date(timestamp);
|
|
if (!timestamp || !userName || !country || Number.isNaN(age) || Number.isNaN(date.getTime())) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
timestamp,
|
|
date,
|
|
day: timestamp.slice(0, 10),
|
|
userName,
|
|
country,
|
|
age,
|
|
gender
|
|
};
|
|
}
|
|
|
|
let clients = new Map();
|
|
let conversations = new Map(); // Key: "user1:user2" (alphabetisch sortiert)
|
|
|
|
// Map: Socket-ID -> Express-Session-ID (für Session-Wiederherstellung)
|
|
let socketToSessionMap = new Map();
|
|
|
|
// Exportiere clients Map für Zugriff von außen
|
|
export function getClientsMap() {
|
|
return clients;
|
|
}
|
|
|
|
// Exportiere Funktion zum Abrufen der Session-ID für einen Socket
|
|
export function getSessionIdForSocket(socketId) {
|
|
return socketToSessionMap.get(socketId);
|
|
}
|
|
|
|
export function extractSessionId(handshakeOrRequest) {
|
|
// Unterstützt sowohl Socket.IO handshake als auch Express request
|
|
const cookies = handshakeOrRequest.headers?.cookie || handshakeOrRequest.cookies || '';
|
|
|
|
console.log('extractSessionId - Cookies:', cookies);
|
|
|
|
const sessionMatch = cookies.match(/connect\.sid=([^;]+)/);
|
|
if (sessionMatch) {
|
|
let sessionId = sessionMatch[1];
|
|
|
|
console.log('extractSessionId - Gefundenes Cookie:', sessionId);
|
|
|
|
// Express-Session speichert die Session-ID als signierten Wert: s:xxxxx.signature
|
|
// Die tatsächliche Session-ID ist nur xxxxx
|
|
if (sessionId.startsWith('s:')) {
|
|
const parts = sessionId.split('.');
|
|
if (parts.length > 0) {
|
|
sessionId = parts[0].substring(2); // Entferne 's:' Präfix
|
|
console.log('extractSessionId - Bereinigte Session-ID:', sessionId);
|
|
}
|
|
}
|
|
|
|
return sessionId;
|
|
}
|
|
|
|
console.log('extractSessionId - Kein connect.sid Cookie gefunden');
|
|
// Fallback: Generiere temporäre UUID
|
|
return crypto.randomUUID();
|
|
}
|
|
|
|
// Exportiere Funktion zum Prüfen des Session-Status
|
|
export function getSessionStatus(req) {
|
|
// Verwende req.sessionID von Express-Session
|
|
const sessionId = req.sessionID || extractSessionId(req);
|
|
|
|
if (!sessionId) {
|
|
return null;
|
|
}
|
|
|
|
const client = clients.get(sessionId);
|
|
|
|
if (!client || !client.userName) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
sessionId: client.sessionId,
|
|
userName: client.userName,
|
|
gender: client.gender,
|
|
age: client.age,
|
|
country: client.country,
|
|
isoCountryCode: client.isoCountryCode
|
|
};
|
|
}
|
|
|
|
function getConversationKey(user1, user2) {
|
|
return [user1, user2].sort().join(':');
|
|
}
|
|
|
|
function logClientLogin(client, __dirname) {
|
|
try {
|
|
const logsDir = join(__dirname, '../logs');
|
|
// Erstelle logs-Verzeichnis falls es nicht existiert
|
|
if (!existsSync(logsDir)) {
|
|
try {
|
|
mkdirSync(logsDir, { recursive: true });
|
|
console.log(`[Log] Logs-Verzeichnis erstellt: ${logsDir}`);
|
|
} catch (mkdirError) {
|
|
console.error(`[Log] Fehler beim Erstellen des Logs-Verzeichnisses: ${mkdirError.message}`);
|
|
return; // Beende Funktion, wenn Verzeichnis nicht erstellt werden kann
|
|
}
|
|
}
|
|
const logPath = join(logsDir, 'logins.log');
|
|
const logEntry = `${new Date().toISOString()},${client.userName},${client.country},${client.age},${client.gender}\n`;
|
|
appendFileSync(logPath, logEntry, 'utf-8');
|
|
} catch (error) {
|
|
console.error('Fehler beim Loggen des Logins:', error.message);
|
|
}
|
|
}
|
|
|
|
function checkAndLogStart(__dirname) {
|
|
try {
|
|
const logsDir = join(__dirname, '../logs');
|
|
// Erstelle logs-Verzeichnis falls es nicht existiert
|
|
if (!existsSync(logsDir)) {
|
|
try {
|
|
mkdirSync(logsDir, { recursive: true });
|
|
console.log(`[Log] Logs-Verzeichnis erstellt: ${logsDir}`);
|
|
} catch (mkdirError) {
|
|
console.error(`[Log] Fehler beim Erstellen des Logs-Verzeichnisses: ${mkdirError.message}`);
|
|
return; // Beende Funktion, wenn Verzeichnis nicht erstellt werden kann
|
|
}
|
|
}
|
|
const logPath = join(logsDir, 'starts.log');
|
|
const logEntry = `${new Date().toISOString()}\n`;
|
|
appendFileSync(logPath, logEntry, 'utf-8');
|
|
} catch (error) {
|
|
console.error('Fehler beim Loggen des Starts:', error.message);
|
|
}
|
|
}
|
|
|
|
export function setupBroadcast(io, __dirname) {
|
|
// Länderliste beim Start laden
|
|
let countriesMap = {};
|
|
|
|
async function downloadCountries() {
|
|
try {
|
|
const response = await axios.get('https://pkgstore.datahub.io/core/country-list/data_csv/data/d7c9d7cfb42cb69f4422dec222dbbaa8/data_csv.csv');
|
|
const lines = response.data.split('\n');
|
|
countriesMap = {};
|
|
|
|
// Parse CSV-Zeile mit Berücksichtigung von Anführungszeichen
|
|
const parseCSVLine = (line) => {
|
|
const result = [];
|
|
let current = '';
|
|
let inQuotes = false;
|
|
|
|
for (let i = 0; i < line.length; i++) {
|
|
const char = line[i];
|
|
if (char === '"') {
|
|
// Ignoriere Anführungszeichen, sie werden nicht zum Wert hinzugefügt
|
|
inQuotes = !inQuotes;
|
|
} else if (char === ',' && !inQuotes) {
|
|
result.push(current.trim());
|
|
current = '';
|
|
} else {
|
|
current += char;
|
|
}
|
|
}
|
|
result.push(current.trim());
|
|
return result;
|
|
};
|
|
|
|
for (let i = 1; i < lines.length; i++) {
|
|
const line = lines[i].trim();
|
|
if (!line) continue;
|
|
|
|
const [name, code] = parseCSVLine(line);
|
|
if (name && code) {
|
|
// Entferne alle Anführungszeichen (auch am Anfang/Ende)
|
|
const cleanName = name.replace(/^["']+|["']+$/g, '').replace(/["']/g, '').trim();
|
|
const cleanCode = code.replace(/^["']+|["']+$/g, '').replace(/["']/g, '').trim();
|
|
if (cleanName && cleanCode) {
|
|
countriesMap[cleanName] = cleanCode.toLowerCase();
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`Länderliste geladen: ${Object.keys(countriesMap).length} Länder`);
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Länderliste:', error);
|
|
}
|
|
}
|
|
|
|
downloadCountries();
|
|
setInterval(downloadCountries, 24 * 60 * 60 * 1000); // Täglich aktualisieren
|
|
|
|
function sendCommandResult(socket, lines, kind = 'info') {
|
|
const payload = Array.isArray(lines) ? lines : [String(lines)];
|
|
socket.emit('commandResult', { lines: payload, kind });
|
|
}
|
|
|
|
function hasRight(client, right) {
|
|
return !!client.chatAuth && client.chatAuth.rights instanceof Set && client.chatAuth.rights.has(right);
|
|
}
|
|
|
|
function getLoginsPath() {
|
|
return join(ensureLogsDir(__dirname), 'logins.log');
|
|
}
|
|
|
|
function readLoginRecords() {
|
|
const logPath = getLoginsPath();
|
|
if (!existsSync(logPath)) return [];
|
|
const raw = readFileSync(logPath, 'utf-8');
|
|
return raw
|
|
.split('\n')
|
|
.map(parseLoginRecord)
|
|
.filter(Boolean);
|
|
}
|
|
|
|
function aggregateTop(items, keySelector, limit = 10) {
|
|
const counts = new Map();
|
|
for (const item of items) {
|
|
const key = keySelector(item);
|
|
counts.set(key, (counts.get(key) || 0) + 1);
|
|
}
|
|
return Array.from(counts.entries())
|
|
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
.slice(0, limit);
|
|
}
|
|
|
|
function buildAllStats(records) {
|
|
if (records.length === 0) {
|
|
return ['Keine Login-Daten vorhanden.'];
|
|
}
|
|
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
const todayCount = records.filter((r) => r.day === today).length;
|
|
const uniqueNames = new Set(records.map((r) => r.userName)).size;
|
|
const ages = records.map((r) => r.age);
|
|
const minAge = Math.min(...ages);
|
|
const maxAge = Math.max(...ages);
|
|
const youngest = records.find((r) => r.age === minAge);
|
|
const oldest = records.find((r) => r.age === maxAge);
|
|
const topCountries = aggregateTop(records, (r) => r.country, 5)
|
|
.map(([name, count]) => `${name}(${count})`)
|
|
.join(', ');
|
|
|
|
return [
|
|
`Logins gesamt: ${records.length}`,
|
|
`Logins heute (${today}): ${todayCount}`,
|
|
`Unterschiedliche Namen: ${uniqueNames}`,
|
|
`Jüngster Nutzer: ${youngest.userName} (${youngest.age})`,
|
|
`Ältester Nutzer: ${oldest.userName} (${oldest.age})`,
|
|
`Top Länder: ${topCountries || 'keine'}`
|
|
];
|
|
}
|
|
|
|
function executeStatsCommand(socket, client, parts) {
|
|
if (!hasRight(client, CHAT_RIGHTS.STAT)) {
|
|
sendCommandResult(socket, 'Keine Berechtigung: Recht "stat" fehlt.');
|
|
return;
|
|
}
|
|
|
|
const records = readLoginRecords();
|
|
if (records.length === 0) {
|
|
sendCommandResult(socket, 'Keine Login-Daten vorhanden.');
|
|
return;
|
|
}
|
|
|
|
const sub = (parts[1] || '').toLowerCase();
|
|
|
|
if (!sub || sub === 'help') {
|
|
sendCommandResult(socket, [
|
|
'Stat-Befehle:',
|
|
'/stat today',
|
|
'/stat date YYYY-MM-DD',
|
|
'/stat range YYYY-MM-DD YYYY-MM-DD',
|
|
'/stat ages',
|
|
'/stat names',
|
|
'/stat countries',
|
|
'/all-stats'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
if (sub === 'today') {
|
|
const day = new Date().toISOString().slice(0, 10);
|
|
const dayRecords = records.filter((r) => r.day === day);
|
|
sendCommandResult(socket, `Logins heute (${day}): ${dayRecords.length}`);
|
|
return;
|
|
}
|
|
|
|
if (sub === 'date') {
|
|
const day = parts[2];
|
|
if (!day || !/^\d{4}-\d{2}-\d{2}$/.test(day)) {
|
|
sendCommandResult(socket, 'Nutzung: /stat date YYYY-MM-DD');
|
|
return;
|
|
}
|
|
const dayRecords = records.filter((r) => r.day === day);
|
|
sendCommandResult(socket, `Logins am ${day}: ${dayRecords.length}`);
|
|
return;
|
|
}
|
|
|
|
if (sub === 'range') {
|
|
const from = parts[2];
|
|
const to = parts[3];
|
|
if (!from || !to || !/^\d{4}-\d{2}-\d{2}$/.test(from) || !/^\d{4}-\d{2}-\d{2}$/.test(to)) {
|
|
sendCommandResult(socket, 'Nutzung: /stat range YYYY-MM-DD YYYY-MM-DD');
|
|
return;
|
|
}
|
|
const filtered = records.filter((r) => r.day >= from && r.day <= to);
|
|
const perDay = aggregateTop(filtered, (r) => r.day, 1000)
|
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
.map(([day, count]) => `${day}: ${count}`);
|
|
sendCommandResult(socket, [`Logins ${from} bis ${to}: ${filtered.length}`, ...perDay]);
|
|
return;
|
|
}
|
|
|
|
if (sub === 'ages') {
|
|
const ages = records.map((r) => r.age);
|
|
const minAge = Math.min(...ages);
|
|
const maxAge = Math.max(...ages);
|
|
const youngest = records.find((r) => r.age === minAge);
|
|
const oldest = records.find((r) => r.age === maxAge);
|
|
sendCommandResult(socket, [
|
|
`Jüngster Nutzer: ${youngest.userName} (${youngest.age})`,
|
|
`Ältester Nutzer: ${oldest.userName} (${oldest.age})`
|
|
]);
|
|
return;
|
|
}
|
|
|
|
if (sub === 'names') {
|
|
const topNames = aggregateTop(records, (r) => r.userName, 20);
|
|
sendCommandResult(socket, [
|
|
`Namen gesamt (verschieden): ${new Set(records.map((r) => r.userName)).size}`,
|
|
...topNames.map(([name, count]) => `${name}: ${count}`)
|
|
]);
|
|
return;
|
|
}
|
|
|
|
if (sub === 'countries') {
|
|
const topCountries = aggregateTop(records, (r) => r.country, 20);
|
|
sendCommandResult(socket, topCountries.map(([country, count]) => `${country}: ${count}`));
|
|
return;
|
|
}
|
|
|
|
sendCommandResult(socket, 'Unbekannter /stat-Befehl. Nutze /stat help');
|
|
}
|
|
|
|
function executeAllStatsCommand(socket, client) {
|
|
if (!hasRight(client, CHAT_RIGHTS.STAT)) {
|
|
sendCommandResult(socket, 'Keine Berechtigung: Recht "stat" fehlt.');
|
|
return;
|
|
}
|
|
sendCommandResult(socket, buildAllStats(readLoginRecords()));
|
|
}
|
|
|
|
function executeKickCommand(socket, client, parts) {
|
|
if (!hasRight(client, CHAT_RIGHTS.KICK)) {
|
|
sendCommandResult(socket, 'Keine Berechtigung: Recht "kick" fehlt.');
|
|
return;
|
|
}
|
|
|
|
const targetName = (parts[1] || '').trim();
|
|
if (!targetName) {
|
|
sendCommandResult(socket, 'Nutzung: /kick <username>');
|
|
return;
|
|
}
|
|
|
|
let targetSessionId = null;
|
|
let targetClient = null;
|
|
for (const [sid, c] of clients.entries()) {
|
|
if (c.userName === targetName && c.socket && c.socket.connected) {
|
|
targetSessionId = sid;
|
|
targetClient = c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!targetClient) {
|
|
sendCommandResult(socket, `User "${targetName}" ist nicht online.`);
|
|
return;
|
|
}
|
|
|
|
if (targetClient.socket) {
|
|
targetClient.socket.emit('error', { message: 'Du wurdest vom Chat getrennt (kick).' });
|
|
targetClient.socket.disconnect(true);
|
|
}
|
|
clients.delete(targetSessionId);
|
|
broadcastUserList();
|
|
sendCommandResult(socket, `User "${targetName}" wurde gekickt.`);
|
|
}
|
|
|
|
function verifyChatUser(username, password) {
|
|
const users = loadChatUsers(__dirname);
|
|
const user = users.find((u) => u.username.toLowerCase() === username.toLowerCase());
|
|
if (!user) return null;
|
|
|
|
const [algo, hash] = user.passwordHash.split(':');
|
|
if (algo !== 'sha256' || !hash) return null;
|
|
const inputHash = sha256(password);
|
|
if (inputHash !== hash) return null;
|
|
|
|
return {
|
|
username: user.username,
|
|
rights: new Set(user.rights)
|
|
};
|
|
}
|
|
|
|
function executeCommand(socket, client, rawInput) {
|
|
const input = rawInput.trim();
|
|
|
|
if (!input.startsWith('/')) return false;
|
|
|
|
const parts = input.split(/\s+/);
|
|
const command = parts[0].toLowerCase();
|
|
|
|
// Laufender Login-Dialog: Nur Eingaben ohne Slash als Username/Passwort behandeln.
|
|
if (client.pendingChatLogin && !input.startsWith('/')) {
|
|
if (client.pendingChatLogin.step === 'username') {
|
|
const enteredUser = input;
|
|
if (!enteredUser) {
|
|
sendCommandResult(socket, 'Username darf nicht leer sein. Bitte Username eingeben:', 'loginPromptUsername');
|
|
return true;
|
|
}
|
|
client.pendingChatLogin = { step: 'password', username: enteredUser };
|
|
sendCommandResult(socket, 'Passwort eingeben:', 'loginPromptPassword');
|
|
return true;
|
|
}
|
|
|
|
if (client.pendingChatLogin.step === 'password') {
|
|
const username = client.pendingChatLogin.username;
|
|
const auth = verifyChatUser(username, input);
|
|
client.pendingChatLogin = null;
|
|
if (!auth) {
|
|
sendCommandResult(socket, 'Login fehlgeschlagen. Benutzername oder Passwort falsch.', 'loginError');
|
|
return true;
|
|
}
|
|
|
|
client.chatAuth = auth;
|
|
sendCommandResult(
|
|
socket,
|
|
`Login erfolgreich als ${auth.username}. Rechte: ${Array.from(auth.rights).join(', ') || 'keine'}`,
|
|
'loginSuccess'
|
|
);
|
|
return true;
|
|
}
|
|
} else if (client.pendingChatLogin && input.startsWith('/')) {
|
|
// Ein neuer /Befehl bricht den Login-Vorgang ab.
|
|
client.pendingChatLogin = null;
|
|
sendCommandResult(socket, 'Login-Vorgang abgebrochen.', 'loginAbort');
|
|
// und läuft unten als normaler Befehl weiter
|
|
}
|
|
|
|
if (command === '/login') {
|
|
const username = (parts[1] || '').trim();
|
|
if (username) {
|
|
client.pendingChatLogin = { step: 'password', username };
|
|
sendCommandResult(socket, 'Passwort eingeben:', 'loginPromptPassword');
|
|
} else {
|
|
client.pendingChatLogin = { step: 'username', username: '' };
|
|
sendCommandResult(socket, 'Username eingeben:', 'loginPromptUsername');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (command === '/logout-admin') {
|
|
const wasLoggedIn = !!client.chatAuth;
|
|
client.chatAuth = null;
|
|
client.pendingChatLogin = null;
|
|
sendCommandResult(
|
|
socket,
|
|
wasLoggedIn
|
|
? 'Admin/Command-Login wurde abgemeldet.'
|
|
: 'Es war kein Admin/Command-Login aktiv.',
|
|
'loginLogout'
|
|
);
|
|
return true;
|
|
}
|
|
|
|
if (command === '/whoami-rights') {
|
|
if (!client.chatAuth) {
|
|
sendCommandResult(socket, 'Nicht per Command-Login angemeldet.', 'whoami');
|
|
return true;
|
|
}
|
|
sendCommandResult(socket, [
|
|
`Angemeldet als: ${client.chatAuth.username}`,
|
|
`Rechte: ${Array.from(client.chatAuth.rights).join(', ') || 'keine'}`
|
|
], 'whoami');
|
|
return true;
|
|
}
|
|
|
|
if (command === '/help' || command === '/?') {
|
|
sendCommandResult(socket, [
|
|
'Verfügbare Befehle:',
|
|
'/login [username] - Admin-/Command-Login starten',
|
|
'/logout-admin - Admin-/Command-Login beenden',
|
|
'/whoami-rights - Aktuelle Admin-Rechte anzeigen',
|
|
'/stat help - Hilfe zu Statistikbefehlen',
|
|
'/all-stats - Zusammenfassung wichtiger Statistiken',
|
|
'/kick <username> - Benutzer aus dem Chat werfen',
|
|
'/help oder /? - Diese Hilfe'
|
|
], 'help');
|
|
return true;
|
|
}
|
|
|
|
if (command === '/stat') {
|
|
executeStatsCommand(socket, client, parts);
|
|
return true;
|
|
}
|
|
|
|
if (command === '/all-stats') {
|
|
executeAllStatsCommand(socket, client);
|
|
return true;
|
|
}
|
|
|
|
if (command === '/kick') {
|
|
executeKickCommand(socket, client, parts);
|
|
return true;
|
|
}
|
|
|
|
sendCommandResult(socket, `Unbekannter Befehl: ${command}`, 'unknown');
|
|
return true;
|
|
}
|
|
|
|
// Socket.IO-Verbindungshandler
|
|
io.on('connection', (socket) => {
|
|
const request = socket.handshake;
|
|
|
|
// Versuche Session-ID aus Cookie zu extrahieren
|
|
let sessionId = extractSessionId(request);
|
|
|
|
console.log('Socket.IO Connect - Session-ID (aus Cookie):', sessionId);
|
|
console.log('Socket.IO Connect - Cookie:', request.headers?.cookie);
|
|
|
|
// Wenn keine gültige Session-ID gefunden wurde, generiere eine temporäre UUID
|
|
// Diese wird beim Login durch die Express-Session-ID ersetzt (siehe handleLogin)
|
|
if (!sessionId || sessionId.length < 10 || !sessionId.match(/^[a-zA-Z0-9_-]+$/)) {
|
|
sessionId = crypto.randomUUID();
|
|
console.log('Socket.IO Connect - Keine gültige Session-ID gefunden, verwende temporäre UUID:', sessionId);
|
|
socket.data.temporarySessionId = true; // Markiere als temporär
|
|
}
|
|
|
|
// Speichere Socket-ID mit Session-ID
|
|
socket.data.sessionId = sessionId;
|
|
|
|
let client = clients.get(sessionId);
|
|
if (!client) {
|
|
console.log('Socket.IO Connect - Neuer Client erstellt für Session-ID:', sessionId);
|
|
client = new Client(sessionId);
|
|
clients.set(sessionId, client);
|
|
} else {
|
|
console.log('Socket.IO Connect - Bestehender Client gefunden für Session-ID:', sessionId, 'User:', client.userName);
|
|
}
|
|
|
|
// Speichere Socket mit Session-ID
|
|
client.socket = socket;
|
|
|
|
// Bestätigung an Client senden, inklusive Benutzerdaten falls bereits eingeloggt
|
|
const connectedData = { sessionId };
|
|
if (client.userName) {
|
|
connectedData.user = {
|
|
userName: client.userName,
|
|
gender: client.gender,
|
|
age: client.age,
|
|
country: client.country,
|
|
isoCountryCode: client.isoCountryCode
|
|
};
|
|
connectedData.loggedIn = true;
|
|
}
|
|
socket.emit('connected', connectedData);
|
|
|
|
socket.on('disconnect', (reason) => {
|
|
console.log(`[Disconnect] Socket getrennt für Session-ID: ${sessionId}, Grund: ${reason}`);
|
|
const client = clients.get(sessionId);
|
|
if (client) {
|
|
console.log(`[Disconnect] Client gefunden: ${client.userName || 'unbekannt'}, Socket war verbunden: ${client.socket ? client.socket.connected : 'null'}`);
|
|
|
|
// Setze Socket auf null, damit keine Nachrichten mehr an diesen Client gesendet werden
|
|
// ABER: Lösche den Client NICHT, damit die Session beim Reload wiederhergestellt werden kann
|
|
client.socket = null;
|
|
|
|
// Aktualisiere Benutzerliste, damit andere Clients sehen, dass dieser Benutzer offline ist
|
|
if (client.userName) {
|
|
console.log(`[Disconnect] Aktualisiere Benutzerliste nach Disconnect von ${client.userName}`);
|
|
broadcastUserList();
|
|
}
|
|
// Client bleibt in der Map, damit Session-Wiederherstellung funktioniert
|
|
} else {
|
|
console.log(`[Disconnect] Kein Client gefunden für Session-ID: ${sessionId}`);
|
|
}
|
|
});
|
|
|
|
socket.on('error', (error) => {
|
|
console.error('Socket.IO-Fehler:', error);
|
|
});
|
|
|
|
// Socket.IO Event-Handler: Setze Express-Session-ID
|
|
socket.on('setSessionId', (data) => {
|
|
const { expressSessionId } = data;
|
|
if (expressSessionId) {
|
|
console.log('setSessionId - Express-Session-ID erhalten:', expressSessionId);
|
|
const currentSessionId = socket.data.sessionId;
|
|
|
|
if (currentSessionId !== expressSessionId) {
|
|
console.log('setSessionId - Aktualisiere Session-ID von', currentSessionId, 'zu', expressSessionId);
|
|
|
|
// Prüfe, ob bereits ein Client mit dieser Session-ID existiert
|
|
let existingClient = clients.get(expressSessionId);
|
|
|
|
// Wenn kein Client mit dieser Session-ID gefunden wurde, prüfe ob ein Client mit dem aktuellen Socket existiert
|
|
// und bereits eingeloggt ist (für Session-Wiederherstellung nach Reload)
|
|
if (!existingClient) {
|
|
const currentClient = clients.get(currentSessionId);
|
|
if (currentClient && currentClient.userName) {
|
|
console.log('setSessionId - Client mit Session-ID', currentSessionId, 'ist bereits eingeloggt:', currentClient.userName);
|
|
// Verwende bestehenden Client und aktualisiere nur die Session-ID
|
|
existingClient = currentClient;
|
|
clients.delete(currentSessionId);
|
|
clients.set(expressSessionId, existingClient);
|
|
existingClient.sessionId = expressSessionId;
|
|
} else {
|
|
// Wenn kein Client mit der aktuellen Session-ID gefunden wurde, suche nach einem Client,
|
|
// der bereits eingeloggt ist und keinen aktiven Socket hat (für Session-Wiederherstellung)
|
|
// Dies ist ein Fallback für den Fall, dass die Session-ID beim Reload geändert wurde
|
|
for (const [sid, c] of clients.entries()) {
|
|
if (c.userName && (!c.socket || !c.socket.connected)) {
|
|
console.log('setSessionId - Gefundener Client ohne aktiven Socket:', sid, c.userName);
|
|
// Verwende diesen Client und aktualisiere die Session-ID
|
|
existingClient = c;
|
|
clients.delete(sid);
|
|
clients.set(expressSessionId, existingClient);
|
|
existingClient.sessionId = expressSessionId;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (existingClient) {
|
|
// Verwende bestehenden Client
|
|
console.log('setSessionId - Verwende bestehenden Client, User:', existingClient.userName || 'nicht eingeloggt');
|
|
existingClient.socket = socket;
|
|
socket.data.sessionId = expressSessionId;
|
|
|
|
// Entferne alten Client, falls unterschiedlich
|
|
if (currentSessionId !== expressSessionId && clients.has(currentSessionId)) {
|
|
clients.delete(currentSessionId);
|
|
}
|
|
|
|
// Sende Login-Status zurück, falls bereits eingeloggt
|
|
if (existingClient.userName) {
|
|
console.log('setSessionId - Sende Login-Status zurück für User:', existingClient.userName);
|
|
socket.emit('connected', {
|
|
sessionId: expressSessionId,
|
|
loggedIn: true,
|
|
user: existingClient.toJSON()
|
|
});
|
|
|
|
// Aktualisiere Userliste für alle Clients, damit der wiederhergestellte Client die Liste erhält
|
|
broadcastUserList();
|
|
}
|
|
} else {
|
|
// Erstelle neuen Client mit Express-Session-ID
|
|
console.log('setSessionId - Erstelle neuen Client');
|
|
const newClient = new Client(expressSessionId);
|
|
newClient.socket = socket;
|
|
clients.set(expressSessionId, newClient);
|
|
socket.data.sessionId = expressSessionId;
|
|
|
|
// Entferne alten Client
|
|
if (clients.has(currentSessionId)) {
|
|
clients.delete(currentSessionId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Socket.IO Event-Handler
|
|
// WICHTIG: Hole den Client immer dynamisch basierend auf socket.data.sessionId,
|
|
// da sich die Session-ID nach setSessionId ändern kann
|
|
socket.on('login', async (data) => {
|
|
try {
|
|
const currentClient = clients.get(socket.data.sessionId);
|
|
if (!currentClient) {
|
|
socket.emit('error', { message: 'Client nicht gefunden' });
|
|
return;
|
|
}
|
|
currentClient.setActivity();
|
|
await handleLogin(socket, currentClient, data);
|
|
} catch (error) {
|
|
console.error('Fehler beim Verarbeiten der Login-Nachricht:', error);
|
|
socket.emit('error', { message: error.message });
|
|
}
|
|
});
|
|
|
|
socket.on('message', async (data) => {
|
|
try {
|
|
const currentClient = clients.get(socket.data.sessionId);
|
|
if (!currentClient) {
|
|
socket.emit('error', { message: 'Client nicht gefunden' });
|
|
return;
|
|
}
|
|
currentClient.setActivity();
|
|
handleMessage(socket, currentClient, data);
|
|
} catch (error) {
|
|
console.error('Fehler beim Verarbeiten der Message-Nachricht:', error);
|
|
socket.emit('error', { message: error.message });
|
|
}
|
|
});
|
|
|
|
socket.on('requestConversation', async (data) => {
|
|
try {
|
|
const currentClient = clients.get(socket.data.sessionId);
|
|
if (!currentClient) {
|
|
socket.emit('error', { message: 'Client nicht gefunden' });
|
|
return;
|
|
}
|
|
currentClient.setActivity();
|
|
handleRequestConversation(socket, currentClient, data);
|
|
} catch (error) {
|
|
console.error('Fehler beim Verarbeiten der RequestConversation-Nachricht:', error);
|
|
socket.emit('error', { message: error.message });
|
|
}
|
|
});
|
|
|
|
socket.on('userSearch', async (data) => {
|
|
try {
|
|
const currentClient = clients.get(socket.data.sessionId);
|
|
if (!currentClient) {
|
|
socket.emit('error', { message: 'Client nicht gefunden' });
|
|
return;
|
|
}
|
|
currentClient.setActivity();
|
|
handleUserSearch(socket, currentClient, data);
|
|
} catch (error) {
|
|
console.error('Fehler beim Verarbeiten der UserSearch-Nachricht:', error);
|
|
socket.emit('error', { message: error.message });
|
|
}
|
|
});
|
|
|
|
socket.on('requestHistory', async () => {
|
|
try {
|
|
const currentClient = clients.get(socket.data.sessionId);
|
|
if (!currentClient) {
|
|
socket.emit('error', { message: 'Client nicht gefunden' });
|
|
return;
|
|
}
|
|
currentClient.setActivity();
|
|
handleRequestHistory(socket, currentClient);
|
|
} catch (error) {
|
|
console.error('Fehler beim Verarbeiten der RequestHistory-Nachricht:', error);
|
|
socket.emit('error', { message: error.message });
|
|
}
|
|
});
|
|
|
|
socket.on('requestOpenConversations', async () => {
|
|
try {
|
|
const currentClient = clients.get(socket.data.sessionId);
|
|
if (!currentClient) {
|
|
socket.emit('error', { message: 'Client nicht gefunden' });
|
|
return;
|
|
}
|
|
currentClient.setActivity();
|
|
handleRequestOpenConversations(socket, currentClient);
|
|
} catch (error) {
|
|
console.error('Fehler beim Verarbeiten der RequestOpenConversations-Nachricht:', error);
|
|
socket.emit('error', { message: error.message });
|
|
}
|
|
});
|
|
|
|
socket.on('blockUser', async (data) => {
|
|
try {
|
|
const currentClient = clients.get(socket.data.sessionId);
|
|
if (!currentClient) {
|
|
socket.emit('error', { message: 'Client nicht gefunden' });
|
|
return;
|
|
}
|
|
currentClient.setActivity();
|
|
handleBlockUser(socket, currentClient, data);
|
|
} catch (error) {
|
|
console.error('Fehler beim Verarbeiten der BlockUser-Nachricht:', error);
|
|
socket.emit('error', { message: error.message });
|
|
}
|
|
});
|
|
|
|
socket.on('unblockUser', async (data) => {
|
|
try {
|
|
const currentClient = clients.get(socket.data.sessionId);
|
|
if (!currentClient) {
|
|
socket.emit('error', { message: 'Client nicht gefunden' });
|
|
return;
|
|
}
|
|
currentClient.setActivity();
|
|
handleUnblockUser(socket, currentClient, data);
|
|
} catch (error) {
|
|
console.error('Fehler beim Verarbeiten der UnblockUser-Nachricht:', error);
|
|
socket.emit('error', { message: error.message });
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
async function handleLogin(socket, client, data) {
|
|
const { userName, gender, age, country, expressSessionId } = data;
|
|
|
|
// Validierung
|
|
if (!userName || userName.trim().length < 3) {
|
|
socket.emit('error', { message: 'Benutzername muss mindestens 3 Zeichen lang sein' });
|
|
return;
|
|
}
|
|
|
|
// Wenn eine Express-Session-ID übergeben wurde, verwende diese statt der Socket-ID
|
|
if (expressSessionId) {
|
|
console.log('handleLogin - Express-Session-ID erhalten:', expressSessionId);
|
|
|
|
// Entferne alten Client mit temporärer Socket-ID, falls vorhanden
|
|
const oldSessionId = socket.data.sessionId;
|
|
if (oldSessionId && oldSessionId !== expressSessionId && clients.has(oldSessionId)) {
|
|
console.log('handleLogin - Entferne alten Client mit Session-ID:', oldSessionId);
|
|
clients.delete(oldSessionId);
|
|
}
|
|
|
|
// Prüfe, ob bereits ein Client mit dieser Session-ID existiert
|
|
let existingClient = clients.get(expressSessionId);
|
|
if (existingClient) {
|
|
// Verwende bestehenden Client
|
|
console.log('handleLogin - Verwende bestehenden Client mit Session-ID:', expressSessionId);
|
|
client = existingClient;
|
|
// Aktualisiere Socket-Verbindung
|
|
client.socket = socket;
|
|
socket.data.sessionId = expressSessionId;
|
|
} else {
|
|
// Erstelle neuen Client mit Express-Session-ID
|
|
console.log('handleLogin - Erstelle neuen Client mit Session-ID:', expressSessionId);
|
|
client = new Client(expressSessionId);
|
|
clients.set(expressSessionId, client);
|
|
client.socket = socket;
|
|
socket.data.sessionId = expressSessionId;
|
|
}
|
|
} else {
|
|
console.log('handleLogin - Keine Express-Session-ID erhalten, verwende Socket-ID');
|
|
}
|
|
|
|
// Prüfe, ob Name bereits verwendet wird
|
|
for (const [sid, c] of clients.entries()) {
|
|
if (c.userName === userName && sid !== client.sessionId) {
|
|
socket.emit('error', { message: 'Dieser Benutzername ist bereits vergeben' });
|
|
return;
|
|
}
|
|
}
|
|
|
|
client.userName = userName.trim();
|
|
client.gender = gender;
|
|
client.age = age;
|
|
client.country = country;
|
|
|
|
// ISO-Code aus Länderliste ermitteln
|
|
client.isoCountryCode = countriesMap[country] || 'unknown';
|
|
|
|
client.loginTimeStamp = new Date();
|
|
client.setActivity();
|
|
|
|
// Stelle sicher, dass der Socket gesetzt ist
|
|
if (!client.socket) {
|
|
console.log('handleLogin - WARNUNG: Socket nicht gesetzt, setze ihn jetzt');
|
|
client.socket = socket;
|
|
}
|
|
|
|
console.log('handleLogin - Client nach Login:', {
|
|
sessionId: client.sessionId,
|
|
userName: client.userName,
|
|
hasSocket: !!client.socket,
|
|
socketConnected: client.socket ? client.socket.connected : false
|
|
});
|
|
|
|
logClientLogin(client, __dirname);
|
|
checkAndLogStart(__dirname);
|
|
|
|
// Benutzerliste an alle senden
|
|
broadcastUserList();
|
|
|
|
// Aktualisiere ungelesene Nachrichten für den neuen Benutzer
|
|
updateUnreadCount(client);
|
|
|
|
console.log('handleLogin - Sende loginSuccess an Socket');
|
|
socket.emit('loginSuccess', {
|
|
sessionId: client.sessionId,
|
|
user: client.toJSON()
|
|
});
|
|
}
|
|
|
|
function handleMessage(socket, client, data) {
|
|
if (!client.userName) {
|
|
socket.emit('error', { message: 'Nicht eingeloggt' });
|
|
return;
|
|
}
|
|
|
|
const { toUserName, message, messageId, isImage, imageType, imageUrl } = data;
|
|
|
|
// Chat-Befehle werden direkt serverseitig verarbeitet
|
|
if (!isImage && typeof message === 'string' && executeCommand(socket, client, message)) {
|
|
return;
|
|
}
|
|
|
|
if (!toUserName) {
|
|
socket.emit('error', { message: 'Empfänger fehlt' });
|
|
return;
|
|
}
|
|
|
|
// Finde Empfänger
|
|
let receiver = null;
|
|
for (const [sid, c] of clients.entries()) {
|
|
if (c.userName === toUserName) {
|
|
receiver = c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!receiver) {
|
|
socket.emit('error', { message: 'Benutzer nicht gefunden' });
|
|
return;
|
|
}
|
|
|
|
// Prüfe, ob Empfänger noch verbunden ist
|
|
if (!receiver.socket || !receiver.socket.connected) {
|
|
// Empfänger ist nicht mehr verbunden, entferne ihn aus der Liste
|
|
clients.delete(receiver.sessionId);
|
|
broadcastUserList();
|
|
socket.emit('error', { message: 'Benutzer nicht gefunden' });
|
|
return;
|
|
}
|
|
|
|
// Prüfe Blockierung
|
|
if (receiver.blockedUsers.has(client.userName)) {
|
|
socket.emit('error', { message: 'Du wurdest von diesem Benutzer blockiert' });
|
|
return;
|
|
}
|
|
|
|
// Speichere Nachricht in Konversation
|
|
const convKey = getConversationKey(client.userName, toUserName);
|
|
if (!conversations.has(convKey)) {
|
|
conversations.set(convKey, []);
|
|
}
|
|
|
|
const conversation = conversations.get(convKey);
|
|
conversation.push({
|
|
from: client.userName,
|
|
to: toUserName,
|
|
message: isImage && imageUrl ? imageUrl : message, // Verwende URL für Bilder
|
|
messageId,
|
|
timestamp: new Date().toISOString(),
|
|
read: false,
|
|
isImage: isImage || false,
|
|
imageType: imageType || null,
|
|
imageUrl: imageUrl || null,
|
|
imageCode: isImage ? message : null
|
|
});
|
|
|
|
// Sende an Empfänger (wenn online)
|
|
const messagePayload = {
|
|
from: client.userName,
|
|
message: isImage && imageUrl ? imageUrl : message, // Verwende URL für Bilder
|
|
messageId,
|
|
timestamp: new Date().toISOString(),
|
|
isImage: isImage || false,
|
|
imageType: imageType || null,
|
|
imageUrl: imageUrl || null,
|
|
imageCode: isImage ? message : null // Code für Server-Referenz
|
|
};
|
|
|
|
// Debug-Logging für Bilder
|
|
if (isImage) {
|
|
console.log(`[Bild] Sende Bild von ${client.userName} an ${toUserName}, Größe: ${message ? message.length : 0} Zeichen, Typ: ${imageType || 'unbekannt'}`);
|
|
console.log(`[Bild] Absender Socket verbunden: ${socket.connected}, Empfänger Socket verbunden: ${receiver.socket ? receiver.socket.connected : 'null'}`);
|
|
}
|
|
|
|
// Prüfe, ob Absender noch verbunden ist
|
|
if (!socket.connected) {
|
|
console.error(`[Bild] Absender ${client.userName} Socket nicht mehr verbunden beim Senden!`);
|
|
return;
|
|
}
|
|
|
|
// Prüfe, ob Empfänger-Socket noch existiert und verbunden ist
|
|
if (!receiver.socket || !receiver.socket.connected) {
|
|
console.error(`[Bild] Empfänger ${toUserName} Socket nicht mehr verbunden beim Senden!`);
|
|
socket.emit('error', { message: 'Empfänger ist nicht mehr online' });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
receiver.socket.emit('message', messagePayload);
|
|
|
|
// Bestätigung an Absender (nur wenn noch verbunden)
|
|
if (socket.connected) {
|
|
socket.emit('messageSent', {
|
|
messageId,
|
|
to: toUserName
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error(`[Bild] Fehler beim Senden der Nachricht:`, error);
|
|
if (socket.connected) {
|
|
socket.emit('error', { message: 'Fehler beim Senden der Nachricht' });
|
|
}
|
|
}
|
|
|
|
// Aktualisiere ungelesene Nachrichten für den Empfänger
|
|
updateUnreadCount(receiver);
|
|
}
|
|
|
|
function handleRequestConversation(socket, client, data) {
|
|
if (!client.userName) {
|
|
socket.emit('error', { message: 'Nicht eingeloggt' });
|
|
return;
|
|
}
|
|
|
|
const { withUserName } = data;
|
|
const convKey = getConversationKey(client.userName, withUserName);
|
|
const conversation = conversations.get(convKey) || [];
|
|
|
|
// Markiere alle Nachrichten von diesem Benutzer als gelesen
|
|
for (const msg of conversation) {
|
|
if (msg.to === client.userName && msg.from === withUserName) {
|
|
msg.read = true;
|
|
}
|
|
}
|
|
|
|
// Aktualisiere ungelesene Nachrichten nach dem Markieren als gelesen
|
|
updateUnreadCount(client);
|
|
|
|
socket.emit('conversation', {
|
|
with: withUserName,
|
|
messages: conversation.map(msg => ({
|
|
from: msg.from,
|
|
message: msg.message,
|
|
timestamp: msg.timestamp,
|
|
isImage: msg.isImage || false,
|
|
imageType: msg.imageType || null
|
|
}))
|
|
});
|
|
}
|
|
|
|
function handleUserSearch(socket, client, data) {
|
|
if (!client.userName) {
|
|
socket.emit('error', { message: 'Nicht eingeloggt' });
|
|
return;
|
|
}
|
|
|
|
const { nameIncludes, minAge, maxAge, countries, genders } = data;
|
|
|
|
const results = [];
|
|
for (const [sid, c] of clients.entries()) {
|
|
if (!c.userName || c.userName === client.userName) continue;
|
|
|
|
// Name-Filter
|
|
if (nameIncludes && !c.userName.toLowerCase().includes(nameIncludes.toLowerCase())) {
|
|
continue;
|
|
}
|
|
|
|
// Alter-Filter
|
|
if (minAge && c.age < minAge) continue;
|
|
if (maxAge && c.age > maxAge) continue;
|
|
|
|
// Länder-Filter
|
|
if (countries && countries.length > 0 && !countries.includes(c.country)) {
|
|
continue;
|
|
}
|
|
|
|
// Geschlecht-Filter
|
|
if (genders && genders.length > 0 && !genders.includes(c.gender)) {
|
|
continue;
|
|
}
|
|
|
|
results.push(c.toJSON());
|
|
}
|
|
|
|
socket.emit('searchResults', {
|
|
results
|
|
});
|
|
}
|
|
|
|
function handleRequestHistory(socket, client) {
|
|
if (!client.userName) {
|
|
socket.emit('error', { message: 'Nicht eingeloggt' });
|
|
return;
|
|
}
|
|
|
|
const history = [];
|
|
for (const [convKey, messages] of conversations.entries()) {
|
|
const [user1, user2] = convKey.split(':');
|
|
if (user1 === client.userName || user2 === client.userName) {
|
|
const otherUser = user1 === client.userName ? user2 : user1;
|
|
history.push({
|
|
userName: otherUser,
|
|
lastMessage: messages.length > 0 ? messages[messages.length - 1] : null
|
|
});
|
|
}
|
|
}
|
|
|
|
socket.emit('historyResults', {
|
|
results: history
|
|
});
|
|
}
|
|
|
|
function handleRequestOpenConversations(socket, client) {
|
|
if (!client.userName) {
|
|
socket.emit('error', { message: 'Nicht eingeloggt' });
|
|
return;
|
|
}
|
|
|
|
const inbox = [];
|
|
for (const [convKey, messages] of conversations.entries()) {
|
|
const [user1, user2] = convKey.split(':');
|
|
if (user1 === client.userName || user2 === client.userName) {
|
|
const otherUser = user1 === client.userName ? user2 : user1;
|
|
const unreadCount = messages.filter(m =>
|
|
m.to === client.userName && !m.read
|
|
).length;
|
|
|
|
if (unreadCount > 0) {
|
|
inbox.push({
|
|
userName: otherUser,
|
|
unreadCount
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
socket.emit('inboxResults', {
|
|
results: inbox
|
|
});
|
|
}
|
|
|
|
function handleBlockUser(socket, client, data) {
|
|
if (!client.userName) {
|
|
socket.emit('error', { message: 'Nicht eingeloggt' });
|
|
return;
|
|
}
|
|
|
|
const { userName } = data;
|
|
client.blockedUsers.add(userName);
|
|
|
|
socket.emit('userBlocked', {
|
|
userName
|
|
});
|
|
}
|
|
|
|
function handleUnblockUser(socket, client, data) {
|
|
if (!client.userName) {
|
|
socket.emit('error', { message: 'Nicht eingeloggt' });
|
|
return;
|
|
}
|
|
|
|
const { userName } = data;
|
|
client.blockedUsers.delete(userName);
|
|
|
|
socket.emit('userUnblocked', {
|
|
userName
|
|
});
|
|
}
|
|
|
|
function updateUnreadCount(client) {
|
|
if (!client.userName || !client.socket || !client.socket.connected) {
|
|
return;
|
|
}
|
|
|
|
// Zähle Personen mit ungelesenen Nachrichten für diesen Benutzer
|
|
let unreadPersons = 0;
|
|
for (const [convKey, messages] of conversations.entries()) {
|
|
const [user1, user2] = convKey.split(':');
|
|
if (user1 === client.userName || user2 === client.userName) {
|
|
const unreadCount = messages.filter(m =>
|
|
m.to === client.userName && !m.read
|
|
).length;
|
|
if (unreadCount > 0) {
|
|
unreadPersons++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sende Update an Client
|
|
client.socket.emit('unreadChats', {
|
|
count: unreadPersons
|
|
});
|
|
}
|
|
|
|
function broadcastUserList() {
|
|
// Filtere nur eingeloggte Benutzer mit aktiver Verbindung
|
|
const userList = Array.from(clients.values())
|
|
.filter(c => {
|
|
const hasUserName = !!c.userName;
|
|
const hasSocket = !!c.socket;
|
|
const isConnected = c.socket && c.socket.connected;
|
|
if (hasUserName && (!hasSocket || !isConnected)) {
|
|
console.log('broadcastUserList - Client ohne Socket oder nicht verbunden:', c.userName, 'socket:', hasSocket, 'connected:', isConnected);
|
|
}
|
|
return hasUserName && hasSocket && isConnected;
|
|
})
|
|
.map(c => c.toJSON());
|
|
|
|
console.log('broadcastUserList - Sende Userliste mit', userList.length, 'Benutzern:', userList.map(u => u.userName));
|
|
|
|
// Sende an alle verbundenen Clients
|
|
io.emit('userList', {
|
|
users: userList
|
|
});
|
|
}
|
|
|
|
function broadcastToUser(userName, data) {
|
|
// Finde den Client mit diesem Benutzernamen und sende die Nachricht
|
|
for (const [sid, c] of clients.entries()) {
|
|
if (c.userName === userName && c.socket) {
|
|
c.socket.emit(data.type, data);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Timeout-Check alle 60 Sekunden
|
|
setInterval(() => {
|
|
for (const [sid, client] of clients.entries()) {
|
|
if (client.activitiesTimedOut()) {
|
|
console.log(`Client ${client.userName} hat Timeout erreicht`);
|
|
clients.delete(sid);
|
|
broadcastUserList();
|
|
}
|
|
}
|
|
}, 60000);
|
|
}
|
|
|