var CATEGORY_META = { wrong_rating: { label: 'Wrong Rating', bg: '#ffc107', color: '#000' }, spam: { label: 'Spam', bg: '#6c757d', color: '#fff' }, duplicate: { label: 'Duplicate', bg: '#17a2b8', color: '#fff' }, copyright: { label: 'Copyright', bg: '#fd7e14', color: '#fff' }, illegal: { label: 'Illegal', bg: '#dc3545', color: '#fff' }, other: { label: 'Other', bg: '#495057', color: '#fff' } }; function catBadge(c) { var m = CATEGORY_META[c] || { label: c, bg: '#444', color: '#fff' }; return '' + m.label + ''; } function rpEsc(s) { return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function relTime(d) { var diff = (Date.now() - new Date(d)) / 1000; if (diff < 60) return 'just now'; if (diff < 3600) return Math.floor(diff/60) + 'm ago'; if (diff < 86400) return Math.floor(diff/3600) + 'h ago'; return new Date(d).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); } window.currentPage = window.currentPage || 1; window.loadReports = async function(page) { page = page || 1; window.currentPage = page; var status = document.getElementById('report-status-filter').value; var feed = document.getElementById('reports-feed'); var pag = document.getElementById('reports-pagination'); feed.innerHTML = '
Loading...
'; pag.innerHTML = ''; try { var res = await fetch('/api/v2/mod/reports?status=' + status + '&page=' + page); var data = await res.json(); if (!data.success) { feed.innerHTML = '
Error: ' + rpEsc(data.msg) + '
'; return; } window.currentReports = data.reports; window.emojiMap = new Map(); if (data.emojis) data.emojis.forEach(function(e) { window.emojiMap.set(e.name.toLowerCase(), e.url); }); if (!data.reports.length) { feed.innerHTML = '
No reports found.
'; return; } feed.innerHTML = ''; var isAdmin = window.f0ckSession && window.f0ckSession.admin; data.reports.forEach(function(r) { var card = document.createElement('div'); var cats = Array.isArray(r.categories) ? r.categories : []; card.className = 'rp-card' + (cats.indexOf('illegal') !== -1 ? ' illegal-flag' : ''); var statusBadge = '' + status + ''; var reporter = r.reporter_name ? '' + rpEsc(r.reporter_name) + '' + (r.reporter_ip ? ' (' + rpEsc(r.reporter_ip) + ')' : '') : 'Guest' + (r.reporter_ip ? ' (' + rpEsc(r.reporter_ip) + ')' : '') + ''; var targetHtml = ''; var itemLink = ''; if (r.comment_id) { targetHtml = 'comment #' + r.comment_id + ''; if (r.resolved_item_id) itemLink = ' item #' + r.resolved_item_id + ''; } else if (r.resolved_item_id) { targetHtml = 'item'; itemLink = ' #' + r.resolved_item_id + ''; } else if (r.reported_user_name) { targetHtml = 'user ' + rpEsc(r.reported_user_name) + ''; } var previewHtml = ''; var isItem = !!r.resolved_item_id && r.resolved_item_dest; var isComment = !!r.comment_id; if (isItem && !isComment) { var mime = r.resolved_item_mime || ''; var src = '/b/' + r.resolved_item_dest; var href = '/' + r.resolved_item_id; if (mime === 'video/youtube') { var ytId = r.resolved_item_dest.replace('yt:', ''); previewHtml = '
'; } else if (mime.indexOf('image/') === 0) { previewHtml = '
'; } else if (mime.indexOf('video/') === 0) { previewHtml = '
'; } else if (mime.indexOf('audio/') === 0) { previewHtml = '
'; } else { previewHtml = '
'; } } else if (isComment) { var body = (r.comment_body || '[deleted]').replace(/&/g,'&').replace(//g,'>'); if (window.emojiMap) { body = body.replace(/:([a-z0-9_]+):/g, function(match, code) { var url = window.emojiMap.get(code.toLowerCase()); return url ? '' : match; }); } previewHtml = '
' + body + '
'; } else { previewHtml = '
'; } var catsHtml = cats.length ? '
' + cats.map(catBadge).join('') + '
' : ''; var reasonHtml = r.reason ? '
' + rpEsc(r.reason) + '
' : ''; var actions = ''; if (status === 'pending') { actions += ''; actions += ''; actions += ''; } if (isItem && !isComment) { var isUnav = r.resolved_item_visibility === 3; actions += ''; actions += ''; } if (isComment) { actions += ''; } if (r.reported_user_id && (isAdmin || !r.reported_user_is_admin)) { var who = r.reported_user_name ? rpEsc(r.reported_user_name) : 'user'; actions += ''; actions += ''; actions += ''; } else if (!r.reported_user_id) { actions += 'Anonymous reporter'; } if (r.reporter_id && r.reporter_name) { actions += ''; } var ts = new Date(r.created_at).toLocaleString(); var rt = relTime(r.created_at); var resolverHtml = (status !== 'pending' && r.resolver_name) ? 'by ' + rpEsc(r.resolver_name) + '' : ''; card.innerHTML = '
' + '
' + '#' + r.id + '' + statusBadge + resolverHtml + 'from ' + reporter + '' + (targetHtml ? '→ ' + targetHtml + '' : '') + itemLink + '
' + '' + rt + '' + '
' + '
' + previewHtml + '
' + catsHtml + reasonHtml + '
' + '
' + '
' + actions + '
'; feed.appendChild(card); }); pag.innerHTML = ''; if (data.pages > 1) { if (data.page > 1) pag.innerHTML += ''; pag.innerHTML += 'Page ' + data.page + ' of ' + data.pages + ''; if (data.page < data.pages) pag.innerHTML += ''; } } catch(e) { feed.innerHTML = '
Network error
'; } }; window.resolveReport = function(id, action) { var label = action === 'resolved' ? 'Resolve' : 'Reject'; var desc = action === 'resolved' ? 'Mark report #' + id + ' as resolved.' : 'Reject report #' + id + '. The content will remain as-is.'; window.ModAction.confirm(label + ' Report #' + id, desc, async function() { var params = new URLSearchParams(); params.append('action', action); var csrfToken = window.f0ckSession && window.f0ckSession.csrf_token; var res = await fetch('/api/v2/mod/reports/' + id + '/resolve', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': csrfToken }, body: params }); var data = await res.json(); if (data.success) { window.loadReports(window.currentPage); if (window.NotificationSystemInstance && typeof window.NotificationSystemInstance.pollDebounced === 'function') window.NotificationSystemInstance.pollDebounced(); } else { throw new Error(data.msg || 'Failed to update report'); } }, { hideReason: true }); }; window.adminDeleteComment = function(id) { window.ModAction.confirm('Delete Comment #' + id, 'Are you sure you want to delete this comment? This action is permanent.', async (reason) => { var params = new URLSearchParams(); params.append('reason', reason); var res = await fetch('/api/comments/' + id + '/delete', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params }); var data = await res.json(); if (data.success) { if (window.showFlash) window.showFlash('comment deleted', 'success'); } else throw new Error(data.msg || 'Unknown error'); }); }; window.adminDeleteItem = function(id) { window.ModAction.confirm('Delete Item #' + id, 'Are you sure you want to delete this item? This action is permanent.', async (reason) => { var params = new URLSearchParams(); params.append('postid', id); params.append('reason', reason); var res = await fetch('/api/v2/admin/deletepost', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params }); var data = await res.json(); if (data.success) { if (window.showFlash) window.showFlash('item deleted', 'success'); } else throw new Error(data.msg || 'Unknown error'); }); }; window.modToggleUnavailable = function(id, currentVis) { var willBeUnavailable = currentVis !== 3; var targetVis = willBeUnavailable ? 3 : 0; var actionText = willBeUnavailable ? 'Make Unavailable (HTTP 451)' : 'Make Available (Public)'; window.ModAction.confirm('Item Visibility', actionText + ' for item #' + id + '?', async () => { var params = new URLSearchParams(); params.append('postid', id); params.append('id', id); params.append('visibility', targetVis); var csrfToken = window.f0ckSession && window.f0ckSession.csrf_token; var res = await fetch('/api/v2/item/visibility', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': csrfToken }, body: params }); var data = await res.json(); if (data.success) { if (window.showFlash) window.showFlash(willBeUnavailable ? 'Item marked unavailable (451)' : 'Item restored to public', 'success'); var item = window.currentReports.find(function(x) { return x.resolved_item_id === id; }); if (item) item.resolved_item_visibility = targetVis; window.loadReports(window.currentPage); } else throw new Error(data.msg || 'Failed to update visibility'); }); }; window.modWarnUser = function(userId) { window.ModAction.confirm('Warn User ID ' + userId, '', async (reason) => { var params = new URLSearchParams(); params.append('user_id', userId); params.append('reason', reason); var res = await fetch('/api/v2/mod/warnings/issue', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params }); var data = await res.json(); if (data.success) { if (window.showFlash) window.showFlash('user has been warned', 'success'); } else throw new Error(data.msg || 'Unknown error'); }); }; window.adminBanUser = function(userId) { var isAdmin = window.f0ckSession && window.f0ckSession.admin; var promptHtml = '

This will restrict the user from accessing their account and performing most actions.

' + '
' + '
'; window.ModAction.confirm('Ban User ID ' + userId, promptHtml, async (reason) => { var duration = document.getElementById('ban-duration-select').value; var params = new URLSearchParams(); params.append('user_id', userId); params.append('reason', reason); params.append('duration', duration); var res = await fetch('/api/v2/admin/ban', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params }); var data = await res.json(); if (data.success) { if (window.showFlash) window.showFlash('User banned cleanly.', 'success'); } else throw new Error(data.msg || 'Unknown error'); }); }; (function() { var filter = document.getElementById('report-status-filter'); if (filter) filter.onchange = function() { window.loadReports(1); }; window.loadReports(1); })();