import { moderationCaseStatusLabels, moderationCaseRelationLabels, type ModerationCaseRelation, type ModerationCaseStatus, type ModerationGame } from '$lib/moderation.js'; import { serverTitle } from './server-labels.js'; export function moderationCaseHref(id: string): string { return `/moderation/cases/${encodeURIComponent(id)}`; } export function moderationHref(context: { game?: string; guid?: string; q?: string; server?: string; matchId?: number; message?: string; }): string { const params = new URLSearchParams(); if (context.game === 't5' || context.game === 't6') params.set('game', context.game); if (context.guid) params.set('guid', context.guid); if (context.q) params.set('q', context.q); if (context.server) params.set('server', context.server); if (context.matchId !== undefined && context.message) { params.set('matchId', String(context.matchId)); params.set('message', context.message); } return `/moderation?${params}`; } export function moderationGameName(game: ModerationGame): string { return game === 't5' ? 'Black Ops I' : 'Black Ops II'; } export const moderationActionLabels: Record = { warn: 'Warning', message: 'Private message', kick: 'Kick', announce: 'Announcement', 'create-sanction': 'Restriction created', 'revoke-sanction': 'Restriction revoked', 'create-case': 'Case opened', 'case-note': 'Case note added', 'resolve-case': 'Case reviewed', 'reopen-case': 'Case reopened', 'update-case': 'Case updated', 'attach-evidence': 'Case evidence attached', note: 'Private note', 'restriction.created': 'Restriction created', 'restriction.revoked': 'Restriction revoked', 'action.queued': 'Live action requested', 'action.confirmed': 'Live action confirmed', 'action.failed': 'Live action failed', 'action.expired': 'Live action expired', 'action.unknown': 'Live action outcome unknown', 'case.opened': 'Case opened', 'case.note': 'Case note added', 'case.resolved': 'Case resolved', 'case.dismissed': 'Case dismissed', 'case.in_review': 'Case under review', 'case.escalated': 'Case escalated', 'case.warning_issued': 'Warning issued', 'case.restriction_applied': 'Restriction applied', 'case.no_action': 'Closed without action', 'case.duplicate': 'Closed as duplicate', 'case.appeal_upheld': 'Appeal upheld', 'case.appeal_denied': 'Appeal denied', 'case.reopened': 'Case reopened', 'case.updated': 'Case updated', 'case.assessed': 'Assessment revised', 'case.review_scheduled': 'Follow-up updated', 'schedule-case-review': 'Follow-up updated', 'case.linked': 'Case relationship added', 'case.unlinked': 'Case relationship removed', 'link-case': 'Case relationship added', 'unlink-case': 'Case relationship removed', 'assess-case': 'Assessment revised', 'case.evidence_attached': 'Evidence attached', 'case.restriction': 'Restriction linked to case', 'case.action_linked': 'Existing action linked to case', 'link-case-action': 'Existing action linked to case', 'player.note': 'Private player note', 'runtime.configure': 'Integration configuration requested' }; export function moderationActionName(kind: string): string { return ( moderationActionLabels[kind] ?? (kind === 'ban' ? 'Ban' : kind === 'mute' ? 'Mute' : kind) ); } export function moderationEventDetail(detail: string, action: string): string { if (action.startsWith('case.')) { try { const record: unknown = JSON.parse(detail); if (!record || typeof record !== 'object') return detail; const value = record as Record; const display = (item: unknown, status = false): string => item === null ? 'Unassigned' : typeof item === 'string' ? status && Object.hasOwn(moderationCaseStatusLabels, item) ? moderationCaseStatusLabels[item as ModerationCaseStatus] : item : ''; if ( (action === 'case.linked' || action === 'case.unlinked') && typeof value.relation === 'string' && Object.hasOwn(moderationCaseRelationLabels, value.relation) && typeof value.relatedCaseId === 'string' && typeof value.reason === 'string' ) { const label = value.direction === 'incoming' && value.relation !== 'same_incident' ? value.relation === 'appeal_of' ? 'Appealed by' : 'Has duplicate' : moderationCaseRelationLabels[value.relation as ModerationCaseRelation]; return `${label}: ${value.relatedCaseId}\n${value.reason}`; } if (action === 'case.review_scheduled' && typeof value.reason === 'string') return `${typeof value.reviewAt === 'string' ? `Review scheduled for ${value.reviewAt}` : 'Follow-up cleared'}\n${value.reason}`; if (action === 'case.assessed' && typeof value.summary === 'string') return `Assessment revision ${value.assessmentRevision}: ${value.summary}`; if (action === 'case.updated') { const changes = ['title', 'priority', 'assignee', 'status'].flatMap((key) => { const change = value[key]; if (!change || typeof change !== 'object') return []; const { from, to } = change as { from?: unknown; to?: unknown }; return [ `${key === 'assignee' ? 'Assigned to' : key.charAt(0).toUpperCase() + key.slice(1)}: ${display(from, key === 'status')} → ${display(to, key === 'status')}` ]; }); if (typeof value.reason === 'string') changes.push(value.reason); return changes.join('\n') || detail; } if (action === 'case.reopened' && typeof value.reason === 'string') return `Previous outcome: ${display(value.from, true)}\n${value.reason}`; if ( action === 'case.evidence_attached' && typeof value.count === 'number' && typeof value.matchId === 'number' ) return `${value.count} archived chat ${value.count === 1 ? 'entry' : 'entries'} attached from match #${value.matchId}.`; if (action === 'case.restriction' && typeof value.reason === 'string') return `${value.kind === 'ban' ? 'Ban' : 'Mute'} · ${display(value.scope)} scope\n${value.reason}`; if ( typeof value.reason === 'string' && value.actionReference && typeof value.actionReference === 'object' ) { const reference = value.actionReference as { type?: unknown; id?: unknown }; if ( (reference.type === 'restriction' || reference.type === 'action') && typeof reference.id === 'string' ) return `${value.reason}\nReferenced ${reference.type === 'restriction' ? 'restriction' : 'action'}: ${reference.id}`; } } catch { // Older case events and decision reasons are plain staff-authored text. } return detail; } if (!['restriction.created', 'action.queued'].includes(action)) return detail; try { const value: unknown = JSON.parse(detail); if ( !value || typeof value !== 'object' || !('reason' in value) || typeof value.reason !== 'string' ) return detail; const record = value as Record; const context = [ typeof record.kind === 'string' ? (moderationActionLabels[record.kind] ?? (record.kind === 'ban' ? 'Ban' : record.kind === 'mute' ? 'Mute' : '')) : '', typeof record.server === 'string' ? serverTitle(record.server) : record.scope === 'fleet' ? 'Entire fleet' : record.scope === 'game' ? 'Game scope' : '' ] .filter(Boolean) .join(' · '); return [ context, record.reason, typeof record.note === 'string' && record.note ? `Private note: ${record.note}` : '' ] .filter(Boolean) .join('\n'); } catch { return detail; } }