'use strict'; // ── State ───────────────────────────────────────────────────────────────── const S = { token: localStorage.getItem('bleeter_token') || null, account: null, profiles: [], activeId: Number(localStorage.getItem('bleeter_profile')) || null, canModerate: false, view: 'home', openComments: new Set(), }; const PROFILE_TYPE_LABEL = { private: 'Privat', small_business: 'Kleingewerbe', company: 'Unternehmen', authority: 'Behörde', lifeinvader: 'Lifeinvader', }; const BUSINESS_TYPES = ['small_business', 'company', 'authority']; function activeProfile() { return S.profiles.find(p => p.id === S.activeId) || S.profiles[0] || null; } function canPostHere(profile, feedType) { if (!profile) return false; const rights = { private: { home: 1 }, small_business: { advertising: 1 }, company: { advertising: 1 }, authority: { home: 1, advertising: 1 }, lifeinvader: {} }; return !!(rights[profile.profile_type] || {})[feedType]; } // ── API ─────────────────────────────────────────────────────────────────── async function api(method, path, body, isForm) { const headers = {}; if (S.token) headers['Authorization'] = 'Bearer ' + S.token; let payload; if (isForm) { payload = body; } else if (body !== undefined) { headers['Content-Type'] = 'application/json'; payload = JSON.stringify(body); } const res = await fetch(path, { method, headers, body: payload }); if (res.status === 401) { logout(); throw new Error('unauthorized'); } let data = null; try { data = await res.json(); } catch (e) {} if (!res.ok) { const e = new Error((data && data.error) || 'error'); e.data = data; throw e; } return data; } function withProfile(params) { const p = activeProfile(); return Object.assign({ profileId: p ? p.id : '' }, params || {}); } function qs(obj) { return '?' + new URLSearchParams(obj).toString(); } function apiGet(path, params) { return api('GET', path + qs(withProfile(params))); } function apiSend(method, path, body) { return api(method, path, withProfile(body)); } // ── Helpers ───────────────────────────────────────────────────────────────── function h(html) { const t = document.createElement('template'); t.innerHTML = html.trim(); return t.content.firstElementChild; } function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } function initials(name) { return String(name || '?').trim().split(/\s+/).slice(0, 2).map(w => w[0] || '').join('').toUpperCase() || '?'; } function avatar(url, name, size) { size = size || 'md'; if (url) return ``; return `
${esc(initials(name))}
`; } function badge(p) { let out = ''; if (p.is_verified) out += ' '; if (p.is_lifeinvader_staff) out += ' Staff'; return out; } function timeAgo(str) { if (!str) return ''; const t = new Date(str.replace(' ', 'T')).getTime(); if (isNaN(t)) return str; const s = Math.floor((Date.now() - t) / 1000); if (s < 60) return 'gerade eben'; if (s < 3600) return Math.floor(s / 60) + ' Min.'; if (s < 86400) return Math.floor(s / 3600) + ' Std.'; if (s < 604800) return Math.floor(s / 86400) + ' T.'; return new Date(t).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: '2-digit' }); } function toast(msg, type) { const t = h(`
${esc(msg)}
`); document.getElementById('toast-layer').appendChild(t); setTimeout(() => { t.style.opacity = '0'; t.style.transition = '0.3s'; setTimeout(() => t.remove(), 300); }, 3200); } const ERR_DE = { invalid_login: 'Handle/Mail oder Passwort falsch.', empty_post: 'Dein Post ist leer.', not_allowed_here: 'Dieses Profil darf hier nicht posten.', media_rejected: 'Bild-URL abgelehnt.', handle_taken: 'Dieses Handle ist bereits vergeben.', reserved: 'Dieses Handle ist reserviert.', already_registered: 'Es existiert bereits ein Privatprofil.', name_required: 'Name darf nicht leer sein.', cannot_block: 'Dieses Profil kann nicht blockiert werden.', no_lifeinvader_rights: 'Keine Lifeinvader-Rechte.', invalid_date: 'Ungültiges Datum.', invalid_time: 'Ungültige Uhrzeit.', title_required: 'Titel fehlt.', not_owner: 'Nur eigene Einträge können bearbeitet werden.', upload_failed: 'Upload fehlgeschlagen.', }; function errText(e) { return ERR_DE[e && e.message] || 'Es ist ein Fehler aufgetreten.'; } function modal(title, bodyEl, onSubmit, submitLabel) { const layer = document.getElementById('modal-layer'); const m = h(``); m.appendChild(bodyEl); const actions = h(``); m.appendChild(actions); layer.innerHTML = ''; layer.appendChild(m); layer.classList.add('show'); const close = () => { layer.classList.remove('show'); layer.innerHTML = ''; }; actions.querySelector('[data-x=cancel]').onclick = close; actions.querySelector('[data-x=ok]').onclick = async () => { try { const ok = await onSubmit(); if (ok !== false) close(); } catch (e) { toast(errText(e), 'error'); } }; layer.onclick = (e) => { if (e.target === layer) close(); }; return close; } // ── Auth ──────────────────────────────────────────────────────────────────── async function boot() { if (!S.token) return renderLogin(); try { const me = await api('GET', '/api/auth/me'); S.account = me.account; S.profiles = me.profiles || []; S.canModerate = !!me.can_moderate; if (!S.activeId || !S.profiles.some(p => p.id === S.activeId)) S.activeId = S.profiles[0] ? S.profiles[0].id : null; renderApp(); } catch (e) { renderLogin(); } } async function doLogin(login, password, errBox) { try { const r = await api('POST', '/api/auth/login', { login, password }); S.token = r.token; localStorage.setItem('bleeter_token', r.token); S.account = r.account; S.profiles = r.profiles || []; S.activeId = S.profiles[0] ? S.profiles[0].id : null; await boot(); } catch (e) { errBox.textContent = errText(e); errBox.style.display = 'block'; } } function logout() { S.token = null; S.account = null; S.profiles = []; localStorage.removeItem('bleeter_token'); localStorage.removeItem('bleeter_profile'); renderLogin(); } function setActive(id) { S.activeId = Number(id); localStorage.setItem('bleeter_profile', S.activeId); go(S.view); } // ── Login view ──────────────────────────────────────────────────────────── function renderLogin() { document.getElementById('app').innerHTML = ''; const wrap = h(`
BleeterLifeinvader Network

Anmelden

Melde dich mit deinem Handle oder deiner IC-Mail und deinem ingame gesetzten Bleeter-Passwort an.

`); document.getElementById('app').appendChild(wrap); const errBox = wrap.querySelector('#login-error'); wrap.querySelector('#login-form').addEventListener('submit', (e) => { e.preventDefault(); const login = wrap.querySelector('#l-login').value.trim().replace(/^@/, ''); const pass = wrap.querySelector('#l-pass').value; if (!login || !pass) { errBox.textContent = 'Bitte alle Felder ausfüllen.'; errBox.style.display = 'block'; return; } doLogin(login, pass, errBox); }); } // ── App shell ─────────────────────────────────────────────────────────────── const NAV = [ { key: 'home', label: 'Feed', ico: '🏠' }, { key: 'ads', label: 'Werbefeed', ico: '📣' }, { key: 'people', label: 'Leute', ico: '🧭' }, { key: 'social', label: 'Follower', ico: '👥' }, { key: 'market', label: 'Marktplatz', ico: '🛒' }, { key: 'calendar', label: 'Kalender', ico: '📅' }, { key: 'business', label: 'Gewerbe', ico: '💼' }, { key: 'profile', label: 'Mein Profil', ico: '👤' }, { key: 'legal', label: 'Rechtliches', ico: '📘' }, ]; function navItems() { const n = NAV.slice(); if (S.canModerate) n.push({ key: 'moderation', label: 'Moderation', ico: '🛡️' }); return n; } function renderApp() { const p = activeProfile(); const profOptions = S.profiles.map(pp => ``).join(''); const shell = h(`
`); const mob = h(`
${navItems().slice(0, 5).map(n => ``).join('')}
`); const app = document.getElementById('app'); app.innerHTML = ''; app.appendChild(shell); app.appendChild(mob); document.querySelectorAll('[data-nav]').forEach(b => b.addEventListener('click', () => { const k = b.getAttribute('data-nav'); if (k === '__logout') return logout(); go(k); })); const sel = shell.querySelector('#prof-select'); if (sel) sel.addEventListener('change', e => setActive(e.target.value)); go(S.view); } function setActiveNav() { document.querySelectorAll('[data-nav]').forEach(b => { const k = b.getAttribute('data-nav'); if (k && k[0] !== '_') b.classList.toggle('active', k === S.view); }); } function main() { return document.getElementById('main'); } function loading() { main().innerHTML = `
🐦
Lädt…
`; } // ── Router ──────────────────────────────────────────────────────────────── async function go(view) { S.view = view; setActiveNav(); loading(); try { if (view === 'home' || view === 'ads') return viewFeed(view === 'ads' ? 'advertising' : 'home'); if (view === 'people') return viewPeople(); if (view === 'social') return viewSocial(); if (view === 'market') return viewMarket(); if (view === 'calendar') return viewCalendar(); if (view === 'business') return viewBusiness(); if (view === 'profile') return viewProfile(); if (view === 'legal') return viewLegal(); if (view === 'moderation') return viewModeration(); } catch (e) { if (e.message !== 'unauthorized') main().innerHTML = `
⚠️
${errText(e)}
`; } } // ── Feed / Werbefeed ───────────────────────────────────────────────────────── function topbar(title, sub) { return `

${esc(title)}

${sub ? `
${esc(sub)}
` : ''}
`; } async function viewFeed(feedType) { const { posts } = await apiGet('/api/feed', { type: feedType }); const p = activeProfile(); const canPost = canPostHere(p, feedType); const c = h(`
${topbar(feedType === 'advertising' ? 'Werbefeed' : 'Feed', feedType === 'advertising' ? 'Angebote & Ankündigungen von Gewerben und Behörden' : 'Was in Los Santos gerade passiert')}
`); if (canPost) c.appendChild(composer(feedType)); else if (p) c.appendChild(h(`
Als ${esc(PROFILE_TYPE_LABEL[p.profile_type])}-Profil kannst du hier nicht posten. ${feedType === 'home' ? 'Wechsle auf ein Behördenprofil.' : 'Wechsle auf ein Gewerbe- oder Behördenprofil.'}
`)); if (!posts.length) c.appendChild(h(`
🐦
Noch keine Bleets hier.
`)); posts.forEach(post => c.appendChild(postCard(post))); main().innerHTML = ''; main().appendChild(c); } function composer(feedType) { const p = activeProfile(); const el = h(`
${avatar(p.avatar_url, p.display_name, 'md')}
0 / 2000
`); const ta = el.querySelector('textarea'), cc = el.querySelector('.char-count'); ta.addEventListener('input', () => cc.textContent = `${ta.value.length} / 2000`); el.querySelector('[data-post]').addEventListener('click', async (ev) => { const body = ta.value.trim(), mediaUrl = el.querySelector('.media-url').value.trim(); if (!body && !mediaUrl) return toast('Dein Post ist leer.', 'error'); ev.target.disabled = true; try { await apiSend('POST', '/api/posts', { feedType, body, mediaUrl }); toast('Gepostet.', 'success'); go(S.view); } catch (e) { toast(errText(e), 'error'); ev.target.disabled = false; } }); return el; } function postCard(post) { const a = post.author; const el = h(`
${avatar(a.avatar_url, a.display_name, 'md')}
${esc(a.display_name)}${badge(a)} @${esc(a.handle)}· ${timeAgo(post.created_at)}
${post.body ? `
${esc(post.body)}
` : ''} ${post.media_url ? `
` : ''}
${post.can_delete ? `` : ''}
`); if (S.openComments.has(post.id)) renderComments(el.querySelector(`[data-comments="${post.id}"]`), post); return el; } function renderComments(box, post) { box.innerHTML = ''; const list = h(`
`); (post.comments_list || []).forEach(cm => { const cmEl = h(`
${avatar('', cm.author, 'sm')}
@${esc(cm.author)} · ${timeAgo(cm.created_at)}
${esc(cm.body)}
${cm.can_delete ? `` : ''}
`); list.appendChild(cmEl); }); box.appendChild(list); const form = h(`
`); form.addEventListener('submit', async (e) => { e.preventDefault(); const inp = form.querySelector('input'); const body = inp.value.trim(); if (!body) return; try { const r = await apiSend('POST', `/api/posts/${post.id}/comments`, { body }); post.comments_list = r.comments; post.comments = r.comments.length; inp.value = ''; renderComments(box, post); refreshCount(post); } catch (err) { toast(errText(err), 'error'); } }); box.appendChild(form); } function refreshCount(post) { const btn = document.querySelector(`[data-post-id="${post.id}"] [data-act="toggle-comments"] span`); if (btn) btn.textContent = post.comments; } // Delegated actions for feed-like interactions document.addEventListener('click', async (e) => { const btn = e.target.closest('[data-act]'); if (!btn) return; const act = btn.getAttribute('data-act'); const id = Number(btn.getAttribute('data-id')); try { if (act === 'like-post') { await apiSend('POST', `/api/posts/${id}/like`); go(S.view); } else if (act === 'like-comment') { await apiSend('POST', `/api/comments/${id}/like`); go(S.view); } else if (act === 'del-post') { if (confirm('Diesen Post löschen?')) { await apiSend('DELETE', `/api/posts/${id}`); toast('Gelöscht.', 'success'); go(S.view); } } else if (act === 'del-comment') { if (confirm('Kommentar löschen?')) { await apiSend('DELETE', `/api/comments/${id}`); go(S.view); } } else if (act === 'toggle-comments') { S.openComments.has(id) ? S.openComments.delete(id) : S.openComments.add(id); go(S.view); } else if (act === 'follow') { await apiSend('POST', `/api/profiles/${id}/follow`); go(S.view); } else if (act === 'unfollow') { await apiSend('POST', `/api/profiles/${id}/unfollow`); go(S.view); } else if (act === 'block') { if (confirm('Profil blockieren?')) { await apiSend('POST', `/api/profiles/${id}/block`); toast('Blockiert.', 'success'); go(S.view); } } else if (act === 'unblock') { await apiSend('POST', `/api/profiles/${id}/unblock`); go(S.view); } else if (act === 'del-market') { if (confirm('Inserat löschen?')) { await apiSend('DELETE', `/api/marketplace/${id}`); go(S.view); } } else if (act === 'del-event') { if (confirm('Termin löschen?')) { await apiSend('DELETE', `/api/events/${id}`); go(S.view); } } else if (act === 'mod') { await modAction(btn); } } catch (err) { if (err.message !== 'unauthorized') toast(errText(err), 'error'); } }); // ── Leute (Directory) ─────────────────────────────────────────────────────── async function viewPeople() { const [{ profiles }, social] = await Promise.all([apiGet('/api/directory'), apiGet('/api/social')]); const followingIds = new Set(social.following.map(x => x.id)); const me = activeProfile(); const c = h(`
${topbar('Leute', 'Profile entdecken und folgen')}
`); const grid = h(`
`); profiles.filter(p => !me || p.id !== me.id).forEach(p => { const following = followingIds.has(p.id); grid.appendChild(h(`
${avatar(p.avatar_url, p.display_name, 'md')}
${esc(p.display_name)}${badge(p)}@${esc(p.handle)} · ${PROFILE_TYPE_LABEL[p.profile_type] || ''} · ${p.followers_count || 0} Follower
`)); }); if (!profiles.length) grid.appendChild(h(`
Keine Profile.
`)); c.appendChild(grid); main().innerHTML = ''; main().appendChild(c); } // ── Follower / Social ──────────────────────────────────────────────────────── let socialTab = 'following'; async function viewSocial() { const social = await apiGet('/api/social'); const tabs = [['following', 'Ich folge', social.following], ['followers', 'Follower', social.followers], ['blocked', 'Blockiert', social.blocked]]; const c = h(`
${topbar('Follower', 'Dein Netzwerk')}
${tabs.map(([k, l, arr]) => ``).join('')}
`); const listWrap = h(`
`); const render = () => { listWrap.innerHTML = ''; const arr = (tabs.find(t => t[0] === socialTab) || [, , []])[2]; if (!arr.length) { listWrap.appendChild(h(`
Niemand hier.
`)); return; } const grid = h(`
`); arr.forEach(p => { let action = ''; if (socialTab === 'following') action = ``; else if (socialTab === 'blocked') action = ``; else if (p.can_be_blocked) action = ``; grid.appendChild(h(`
${avatar(p.avatar_url, p.display_name, 'md')}
${esc(p.display_name)}${badge(p)}@${esc(p.handle)} · ${PROFILE_TYPE_LABEL[p.profile_type] || ''}
${action}
`)); }); listWrap.appendChild(grid); }; c.appendChild(listWrap); render(); c.querySelectorAll('[data-stab]').forEach(b => b.addEventListener('click', () => { socialTab = b.getAttribute('data-stab'); c.querySelectorAll('[data-stab]').forEach(x => x.classList.toggle('active', x === b)); render(); })); main().innerHTML = ''; main().appendChild(c); } // ── Marktplatz ──────────────────────────────────────────────────────────── async function viewMarket() { const { items } = await apiGet('/api/marketplace'); const c = h(`

Marktplatz

Angebote der Community
`); c.querySelector('#new-market').addEventListener('click', marketModal); if (!items.length) c.appendChild(h(`
🛒
Noch keine Inserate.
`)); const grid = h(`
`); items.forEach(it => { grid.appendChild(h(`
${it.media_url ? `
` : ''} ${esc(it.title)} ${it.price_label ? `
${esc(it.price_label)}
` : ''}
${esc(it.body || '')}
@${esc(it.author.handle)}${it.author.email ? ' · ' + esc(it.author.email) : ''}
${it.can_delete ? `` : ''}
`)); }); c.appendChild(grid); main().innerHTML = ''; main().appendChild(c); } function marketModal() { const body = h(`
`); modal('Neues Inserat', body, async () => { await apiSend('POST', '/api/marketplace', { title: body.querySelector('#m-title').value, description: body.querySelector('#m-desc').value, priceLabel: body.querySelector('#m-price').value, mediaUrl: body.querySelector('#m-media').value, }); toast('Inserat erstellt.', 'success'); go('market'); }, 'Erstellen'); } // ── Kalender ──────────────────────────────────────────────────────────────── async function viewCalendar() { const { days } = await apiGet('/api/calendar'); const p = activeProfile(); const canCreate = p && BUSINESS_TYPES.includes(p.profile_type); const c = h(`

Kalender

Veranstaltungen der nächsten 4 Wochen
${canCreate ? '' : ''}
`); if (canCreate) c.querySelector('#new-event').addEventListener('click', eventModal); const withEvents = days.filter(d => d.events.length); if (!withEvents.length) c.appendChild(h(`
📅
Keine anstehenden Termine.
`)); withEvents.forEach(d => { const day = h(`

${esc(d.title)}

`); d.events.forEach(ev => day.appendChild(h(`
${esc(ev.time)}
${esc(ev.title)}${ev.location ? `
📍 ${esc(ev.location)}
` : ''}
@${esc(ev.author)}
${ev.can_delete ? `` : ''}
`))); c.appendChild(day); }); main().innerHTML = ''; main().appendChild(c); } function eventModal() { const today = new Date().toISOString().slice(0, 10); const body = h(`
`); modal('Neuer Termin', body, async () => { await apiSend('POST', '/api/events', { title: body.querySelector('#e-title').value, date: body.querySelector('#e-date').value, time: body.querySelector('#e-time').value, location: body.querySelector('#e-loc').value, }); toast('Termin erstellt.', 'success'); go('calendar'); }, 'Erstellen'); } // ── Gewerbe ───────────────────────────────────────────────────────────────── async function viewBusiness() { const { businesses } = await apiGet('/api/businesses'); const p = activeProfile(); const canToggle = p && BUSINESS_TYPES.includes(p.profile_type) && p.can_edit_profile; const mine = canToggle ? businesses.find(b => b.handle === p.handle) : null; const c = h(`
${topbar('Gewerbe', 'Geöffnete und geschlossene Betriebe')}
`); if (canToggle) { const cur = mine ? mine.status : 'closed'; const toggle = h(`
Dein Betrieb: @${esc(p.handle)}
Aktueller Status: ${cur === 'open' ? 'Geöffnet' : 'Geschlossen'}
`); toggle.querySelectorAll('[data-bstatus]').forEach(b => b.addEventListener('click', async () => { try { await apiSend('POST', '/api/business/status', { status: b.getAttribute('data-bstatus') }); toast('Status aktualisiert.', 'success'); go('business'); } catch (e) { toast(errText(e), 'error'); } })); c.appendChild(toggle); } const grid = h(`
`); businesses.forEach(b => grid.appendChild(h(`
${avatar(b.avatar_url, b.display_name, 'md')}
${esc(b.display_name)}${b.is_verified ? ' ' : ''}@${esc(b.handle)}
${b.status === 'open' ? 'Geöffnet' : 'Geschlossen'}
`))); if (!businesses.length) grid.appendChild(h(`
Keine Gewerbe gelistet.
`)); c.appendChild(grid); main().innerHTML = ''; main().appendChild(c); } // ── Mein Profil ───────────────────────────────────────────────────────────── async function viewProfile() { const p = activeProfile(); if (!p) { return renderRegister(); } const data = await apiGet('/api/profiles/' + encodeURIComponent(p.handle)); const prof = data.profile; const c = h(`
${topbar('Mein Profil', '')}
${avatar(prof.avatar_url, prof.display_name, 'lg')}

${esc(prof.display_name)}${badge(prof)}

@${esc(prof.handle)} · ${PROFILE_TYPE_LABEL[prof.profile_type] || ''}
${p.can_edit_profile ? '' : ''}
${prof.bio ? `
${esc(prof.bio)}
` : ''}
${prof.followers_count || 0}Follower
${prof.following_count || 0}Folge ich
${data.posts.length}Bleets
`); if (p.can_edit_profile) c.querySelector('#edit-profile').addEventListener('click', () => editProfileModal(prof)); if (!data.posts.length) c.appendChild(h(`
Noch keine Bleets.
`)); data.posts.forEach(post => { c.appendChild(h(`
${prof.feed_type === 'advertising' ? '📣 Werbung' : ''}${timeAgo(post.created_at)}
${post.body ? `
${esc(post.body)}
` : ''} ${post.media_url ? `
` : ''}
❤️ ${post.likes}💬 ${post.comments}
`)); }); main().innerHTML = ''; main().appendChild(c); } function editProfileModal(prof) { const bioMax = prof.profile_type === 'private' ? 200 : 500; const body = h(`
`); modal('Profil bearbeiten', body, async () => { await apiSend('PATCH', '/api/profile', { displayName: body.querySelector('#p-name').value, bio: body.querySelector('#p-bio').value, avatarUrl: body.querySelector('#p-avatar').value, bannerUrl: body.querySelector('#p-banner').value, }); toast('Profil gespeichert.', 'success'); await boot(); go('profile'); }); } function renderRegister() { const c = h(`
${topbar('Willkommen bei Bleeter', '')}

Du hast noch kein Bleeter-Profil. Lege jetzt dein privates Profil an.

`); c.querySelector('#r-go').addEventListener('click', async () => { try { const r = await api('POST', '/api/register', { handle: c.querySelector('#r-handle').value.trim().replace(/^@/, ''), displayName: c.querySelector('#r-name').value.trim() }); S.profiles = r.profiles; S.activeId = S.profiles[0].id; toast('Profil erstellt!', 'success'); renderApp(); } catch (e) { toast(errText(e), 'error'); } }); main().innerHTML = ''; main().appendChild(c); } // ── Rechtliches ───────────────────────────────────────────────────────────── function viewLegal() { const c = h(`
${topbar('Rechtliches', '')}
`); main().innerHTML = ''; main().appendChild(c); } // ── Moderation (Lifeinvader) ────────────────────────────────────────────────── async function viewModeration() { const { profiles } = await apiGet('/api/directory'); const c = h(`
${topbar('Moderation', 'Lifeinvader-Werkzeuge')}
Verifizieren, Staff markieren oder Profile sperren. Aktionen werden protokolliert.
`); const grid = h(`
`); profiles.forEach(p => grid.appendChild(h(`
${avatar(p.avatar_url, p.display_name, 'md')}
${esc(p.display_name)}${badge(p)}@${esc(p.handle)} · ${PROFILE_TYPE_LABEL[p.profile_type] || ''}${p.is_locked ? ' · gesperrt' : ''}
`))); c.appendChild(grid); main().innerHTML = ''; main().appendChild(c); } async function modAction(btn) { const action = btn.getAttribute('data-modaction'); const id = Number(btn.getAttribute('data-id')); await apiSend('POST', '/api/moderation/profile', { action, targetId: id }); toast('Aktion ausgeführt.', 'success'); go('moderation'); } // ── Start ──────────────────────────────────────────────────────────────────── boot();