💬 Secure Private Chat
Sicherer Chat mit Altersverifikation
@@ -3294,25 +2469,335 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
// JAVASCRIPT
// ═══════════════════════════════════════════════════════════
-
+
+// ADMIN DASHBOARD
+const adminStatsGrid = document.getElementById('adminStatsGrid');
+const adminReportsContainer = document.getElementById('adminReportsContainer');
+const adminFlaggedContainer = document.getElementById('adminFlaggedContainer');
+const adminBannedContainer = document.getElementById('adminBannedContainer');
+
+function adminEscapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text ?? '';
+ return div.innerHTML;
+}
+
+function adminFormatDate(value) {
+ if (!value) return '-';
+ try {
+ return new Date(value).toLocaleString('de-DE');
+ } catch (e) {
+ return value;
+ }
+}
+
+async function adminFetch(action, payload = {}) {
+ const formData = new FormData();
+ formData.append('action', action);
+ Object.entries(payload).forEach(([key, val]) => formData.append(key, val));
+
+ const response = await fetch('', { method: 'POST', body: formData });
+ return response.json();
+}
+
+function renderAdminStats(stats) {
+ if (!stats) {
+ adminStatsGrid.innerHTML = '
Keine Statistiken verfügbar.
';
+ return;
+ }
+
+ const statItems = [
+ ['Registrierte Nutzer', stats.total_users],
+ ['U18 Nutzer', stats.u18_users],
+ ['Ü18 Nutzer', stats.o18_users],
+ ['Aktiv online', stats.online_users],
+ ['Nachrichten gesamt', stats.total_messages],
+ ['Markierte Nachrichten', stats.flagged_messages],
+ ['Offene Meldungen', stats.pending_reports],
+ ['Gesperrte Nutzer', stats.banned_users]
+ ];
+
+ adminStatsGrid.innerHTML = statItems.map(([label, value]) => `
+
+ ${adminEscapeHtml(label)}
+ ${Number(value) || 0}
+
+ `).join('');
+}
+
+function renderReports(reports) {
+ if (!reports || reports.length === 0) {
+ adminReportsContainer.innerHTML = '
Aktuell liegen keine offenen Meldungen vor.
';
+ return;
+ }
+
+ const rows = reports.map(report => `
+
+ | ${adminEscapeHtml(report.reporter)} |
+ ${adminEscapeHtml(report.reported)} |
+
+ ${adminEscapeHtml(report.reason)}
+ ${report.message ? ` ${adminEscapeHtml(report.message)} ` : ''}
+ |
+ ${adminEscapeHtml(adminFormatDate(report.timestamp))} |
+
+
+
+
+
+ |
+
+ `).join('');
+
+ adminReportsContainer.innerHTML = `
+
+
+
+ | Melder |
+ Gemeldeter |
+ Grund & Nachricht |
+ Zeitpunkt |
+ Aktionen |
+
+
+ ${rows}
+
+ `;
+
+ adminReportsContainer.querySelectorAll('button[data-action="ban"]').forEach(btn => {
+ btn.addEventListener('click', () => adminBanUser(btn.dataset.user));
+ });
+
+ adminReportsContainer.querySelectorAll('button[data-action="resolve"]').forEach(btn => {
+ btn.addEventListener('click', () => adminResolveReport(btn.dataset.report));
+ });
+}
+
+function renderFlagged(flagged) {
+ if (!flagged || flagged.length === 0) {
+ adminFlaggedContainer.innerHTML = '
Keine markierten Nachrichten vorhanden.
';
+ return;
+ }
+
+ const rows = flagged.map(item => `
+
+ | ${adminEscapeHtml(item.user)} |
+ ${adminEscapeHtml(item.message)} |
+ ${adminEscapeHtml(item.reason)} |
+ ${adminEscapeHtml(adminFormatDate(item.timestamp))} |
+
+
+
+
+
+ |
+
+ `).join('');
+
+ adminFlaggedContainer.innerHTML = `
+
+
+
+ | Nutzer |
+ Nachricht |
+ Grund |
+ Zeitpunkt |
+ Aktionen |
+
+
+ ${rows}
+
+ `;
+
+ adminFlaggedContainer.querySelectorAll('button[data-action="ban"]').forEach(btn => {
+ btn.addEventListener('click', () => adminBanUser(btn.dataset.user));
+ });
+
+ adminFlaggedContainer.querySelectorAll('button[data-action="delete"]').forEach(btn => {
+ btn.addEventListener('click', () => adminDeleteMessage(btn.dataset.message));
+ });
+}
+
+function renderBanned(banned) {
+ if (!banned || banned.length === 0) {
+ adminBannedContainer.innerHTML = '
Keine Nutzer gesperrt.
';
+ return;
+ }
+
+ const rows = banned.map(user => `
+
+ | ${adminEscapeHtml(user.display_name)} |
+ ${adminEscapeHtml(user.reason)} |
+ ${adminEscapeHtml(adminFormatDate(user.last_seen))} |
+
+
+
+
+ |
+
+ `).join('');
+
+ adminBannedContainer.innerHTML = `
+
+
+
+ | Nutzer |
+ Grund |
+ Zuletzt aktiv |
+ Aktionen |
+
+
+ ${rows}
+
+ `;
+
+ adminBannedContainer.querySelectorAll('button[data-action="unban"]').forEach(btn => {
+ btn.addEventListener('click', () => adminUnbanUser(btn.dataset.user));
+ });
+}
+
+async function loadAdminStats() {
+ const response = await fetch('?action=admin_get_stats');
+ const result = await response.json();
+ if (result.success) {
+ renderAdminStats(result.stats);
+ }
+}
+
+async function loadAdminReports() {
+ const response = await fetch('?action=admin_get_reports');
+ const result = await response.json();
+ if (result.success) {
+ renderReports(result.reports);
+ }
+}
+
+async function loadAdminFlagged() {
+ const response = await fetch('?action=admin_get_flagged');
+ const result = await response.json();
+ if (result.success) {
+ renderFlagged(result.flagged);
+ }
+}
+
+async function loadAdminBanned() {
+ const result = await fetch('?action=admin_get_banned_users');
+ const data = await result.json();
+ if (data.success) {
+ renderBanned(data.banned);
+ }
+}
+
+async function adminBanUser(userId) {
+ const reason = prompt('Grund für die Sperre eingeben:', 'Verstoß gegen Nutzungsbedingungen');
+ if (reason === null) return;
+
+ const result = await adminFetch('admin_ban_user', { user_id: userId, reason });
+ if (!result.success) {
+ alert(result.error || 'Aktion fehlgeschlagen');
+ return;
+ }
+ await refreshAdminData();
+}
+
+async function adminResolveReport(reportId) {
+ const actionTaken = prompt('Status für Report festlegen (z.B. resolved, dismissed):', 'resolved');
+ if (actionTaken === null) return;
+
+ const result = await adminFetch('admin_resolve_report', { report_id: reportId, action_taken: actionTaken });
+ if (!result.success) {
+ alert(result.error || 'Aktion fehlgeschlagen');
+ return;
+ }
+ await refreshAdminData();
+}
+
+async function adminDeleteMessage(messageId) {
+ if (!confirm('Markierte Nachricht wirklich löschen?')) return;
+
+ const result = await adminFetch('admin_delete_message', { message_id: messageId });
+ if (!result.success) {
+ alert(result.error || 'Aktion fehlgeschlagen');
+ return;
+ }
+ await refreshAdminData();
+}
+
+async function adminUnbanUser(userId) {
+ const result = await adminFetch('admin_unban_user', { user_id: userId });
+ if (!result.success) {
+ alert(result.error || 'Aktion fehlgeschlagen');
+ return;
+ }
+ await refreshAdminData();
+}
+
+async function refreshAdminData() {
+ await Promise.all([
+ loadAdminStats(),
+ loadAdminReports(),
+ loadAdminFlagged(),
+ loadAdminBanned()
+ ]);
+}
+
+document.getElementById('adminLogoutBtn').addEventListener('click', async () => {
+ await adminFetch('logout');
+ window.location.href = '?';
+});
+
+refreshAdminData();
+setInterval(refreshAdminData, 30000);
+
+
+// ADMIN LOGIN
+const adminLoginForm = document.getElementById('adminLoginForm');
+const adminError = document.getElementById('adminError');
+
+adminLoginForm.addEventListener('submit', async (e) => {
+ e.preventDefault();
+ adminError.style.display = 'none';
+
+ const formData = new FormData();
+ formData.append('action', 'admin_login');
+ formData.append('username', document.getElementById('adminUsername').value.trim());
+ formData.append('password', document.getElementById('adminPassword').value);
+
+ try {
+ const response = await fetch('', { method: 'POST', body: formData });
+ const result = await response.json();
+
+ if (result.success) {
+ window.location.href = '?admin=1';
+ } else {
+ adminError.textContent = result.error || 'Anmeldung fehlgeschlagen';
+ adminError.style.display = 'block';
+ }
+ } catch (error) {
+ adminError.textContent = 'Server nicht erreichbar';
+ adminError.style.display = 'block';
+ }
+});
+
+
// REGISTRATION
document.getElementById('registerForm').addEventListener('submit', async (e) => {
e.preventDefault();
-
+
const username = document.getElementById('username').value.trim();
const birthdate = document.getElementById('birthdate').value;
const agreedTerms = document.getElementById('agreeTerms').checked;
-
+
const formData = new FormData();
formData.append('action', 'register');
formData.append('username', username);
formData.append('birthdate', birthdate);
formData.append('agreed_terms', agreedTerms);
-
+
try {
const response = await fetch('', { method: 'POST', body: formData });
const result = await response.json();
-
+
if (result.success) {
window.location.reload();
} else {
@@ -3337,79 +2822,140 @@ const state = {
eventSource: null
};
-// Load Users
async function loadUsers() {
const response = await fetch('?action=get_users');
const result = await response.json();
-
+
if (result.success) {
state.users = result.users;
renderUserList();
}
}
-// Render User List
function renderUserList() {
const userList = document.getElementById('userList');
- const searchTerm = document.getElementById('userSearch').value.toLowerCase();
-
- const filtered = state.users.filter(u => u.display_name.toLowerCase().includes(searchTerm));
-
- userList.innerHTML = filtered.map(user => `
-
-
- ${user.username.charAt(0).toUpperCase()}
-
-
-
-
${user.display_name}
-
${user.is_online ? 'Online' : 'Offline'}
-
- ${user.unread_count > 0 ? `
${user.unread_count}
` : ''}
-
- `).join('');
+ if (!userList) {
+ return;
+ }
+
+ const searchInput = document.getElementById('userSearch');
+ const searchTerm = (searchInput?.value || '').toLowerCase();
+
+ const filtered = state.users.filter(u => (u.display_name || '').toLowerCase().includes(searchTerm));
+
+ if (filtered.length === 0) {
+ userList.innerHTML = '
Keine Nutzer gefunden.
';
+ return;
+ }
+
+ userList.innerHTML = filtered.map(user => {
+ const displayNameRaw = (user.display_name || user.username || '').trim();
+ const displayName = displayNameRaw.length > 0 ? displayNameRaw : 'Unbekannt';
+ const initial = displayName.charAt(0).toUpperCase() || '?';
+
+ return `
+
+ `;
+ }).join('');
+
+ userList.querySelectorAll('.user-item').forEach(item => {
+ item.addEventListener('click', () => {
+ const userId = Number(item.dataset.userId);
+ const user = state.users.find(u => u.id === userId);
+ if (!user) {
+ return;
+ }
+ selectUser(user.id, user.display_name || user.username);
+ });
+ });
}
-// Select User
function selectUser(userId, displayName) {
state.selectedUserId = userId;
-
- document.getElementById('chatWelcome').style.display = 'none';
- document.getElementById('chatMessagesContainer').style.display = 'flex';
-
- document.getElementById('chatMessagesHeader').innerHTML = `
-
${displayName.charAt(0).toUpperCase()}
-
- `;
-
+
+ const welcome = document.getElementById('chatWelcome');
+ if (welcome) {
+ welcome.style.display = 'none';
+ }
+
+ const container = document.getElementById('chatMessagesContainer');
+ if (container) {
+ container.style.display = 'flex';
+ }
+
+ const safeDisplayName = (displayName || '').trim() || 'Unbekannt';
+ const initial = safeDisplayName.charAt(0).toUpperCase() || '?';
+
+ const header = document.getElementById('chatMessagesHeader');
+ if (header) {
+ header.innerHTML = `
+
${escapeHtml(initial)}
+
${escapeHtml(safeDisplayName)}
+ `;
+ }
+
+ const messageList = document.getElementById('chatMessages');
+ if (messageList) {
+ messageList.innerHTML = '
Nachrichten werden geladen…
';
+ }
+
loadMessages(userId);
renderUserList();
}
-// Load Messages
async function loadMessages(userId) {
- const response = await fetch(`?action=get_messages&user_id=${userId}`);
- const result = await response.json();
-
- if (result.success) {
- state.messages = result.messages;
- renderMessages();
- markAsRead(userId);
-
- if (result.messages.length > 0) {
- state.lastMessageId = Math.max(...result.messages.map(m => m.id));
+ const messageList = document.getElementById('chatMessages');
+
+ try {
+ const response = await fetch(`?action=get_messages&user_id=${userId}`);
+ const result = await response.json();
+
+ if (result.success) {
+ state.messages = result.messages;
+ renderMessages();
+ markAsRead(userId);
+
+ if (result.messages.length > 0) {
+ state.lastMessageId = Math.max(...result.messages.map(m => m.id));
+ }
+ } else if (messageList) {
+ state.messages = [];
+ messageList.innerHTML = `
${escapeHtml(result.error || 'Nachrichten konnten nicht geladen werden.')}
`;
+ }
+ } catch (error) {
+ if (messageList) {
+ state.messages = [];
+ messageList.innerHTML = '
Verbindung fehlgeschlagen. Bitte versuche es erneut.
';
}
}
}
-// Render Messages
function renderMessages() {
const container = document.getElementById('chatMessages');
-
+
+ if (!container) {
+ return;
+ }
+
+ if (!state.messages || state.messages.length === 0) {
+ container.innerHTML = '
Noch keine Nachrichten. Starte den Chat!
';
+ return;
+ }
+
container.innerHTML = state.messages.map(msg => {
const isSent = msg.from_user_id === state.currentUserId;
const time = new Date(msg.timestamp).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
-
+
return `
${escapeHtml(msg.message)}
@@ -3417,25 +2963,24 @@ function renderMessages() {
`;
}).join('');
-
+
container.scrollTop = container.scrollHeight;
}
-// Send Message
async function sendMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
-
+
if (!message || !state.selectedUserId) return;
-
+
const formData = new FormData();
formData.append('action', 'send_message');
formData.append('to_user_id', state.selectedUserId);
formData.append('message', message);
-
+
const response = await fetch('', { method: 'POST', body: formData });
const result = await response.json();
-
+
if (result.success) {
input.value = '';
} else {
@@ -3443,36 +2988,34 @@ async function sendMessage() {
}
}
-// Mark as Read
async function markAsRead(userId) {
const formData = new FormData();
formData.append('action', 'mark_read');
formData.append('user_id', userId);
-
+
await fetch('', { method: 'POST', body: formData });
loadUsers();
}
-// SSE
function startSSE() {
state.eventSource = new EventSource(`?stream=events&last_message_id=${state.lastMessageId}`);
-
+
state.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
-
+
if (data.type === 'messages' && data.messages) {
data.messages.forEach(msg => {
if (msg.id > state.lastMessageId) {
state.lastMessageId = msg.id;
-
- if (state.selectedUserId &&
+
+ if (state.selectedUserId &&
((msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) ||
(msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId))) {
-
+
if (!state.messages.find(m => m.id === msg.id)) {
state.messages.push(msg);
renderMessages();
-
+
if (msg.to_user_id === state.currentUserId) {
markAsRead(msg.from_user_id);
}
@@ -3480,20 +3023,18 @@ function startSSE() {
}
}
});
-
+
loadUsers();
}
};
}
-// Utility
function escapeHtml(text) {
const div = document.createElement('div');
- div.textContent = text;
+ div.textContent = text ?? '';
return div.innerHTML;
}
-// Event Listeners
document.getElementById('sendButton').addEventListener('click', sendMessage);
document.getElementById('chatInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
@@ -3509,20 +3050,17 @@ document.getElementById('logoutBtn').addEventListener('click', async () => {
window.location.reload();
});
-// Auto-resize textarea
document.getElementById('chatInput').addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 100) + 'px';
});
-// Ping every 10s
setInterval(async () => {
const formData = new FormData();
formData.append('action', 'ping');
await fetch('', { method: 'POST', body: formData });
}, 10000);
-// Init
loadUsers();
startSSE();
setInterval(loadUsers, 30000);