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 += ' Resolve ';
actions += ' Reject ';
actions += ' ';
}
if (isItem && !isComment) {
var isUnav = r.resolved_item_visibility === 3;
actions += ' Delete ';
actions += '' + (isUnav ? ' Restore' : ' 451') + ' ';
}
if (isComment) {
actions += ' Del Comment ';
}
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 += ' Warn ' + who + ' ';
actions += ' Ban ' + who + ' ';
} else if (!r.reported_user_id) {
actions += 'Anonymous reporter ';
}
if (r.reporter_id && r.reporter_name) {
actions += 'Warn Reporter ';
}
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 += ' Prev ';
pag.innerHTML += 'Page ' + data.page + ' of ' + data.pages + ' ';
if (data.page < data.pages)
pag.innerHTML += 'Next ';
}
} 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.
' +
'Ban Duration: ' +
'' +
(isAdmin ? 'Permanent ' : '') +
'1 Hour 6 Hours 24 Hours (1 Day) ' +
(!isAdmin ? '48 Hours (2 Days) ' : '') +
(isAdmin ? '168 Hours (1 Week) ' : '') +
(isAdmin ? '720 Hours (1 Month) ' : '') +
'
';
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);
})();