/* Enhanced Local Chat Statistics */ class LocalChatAnalyzer { constructor() { this.currentChatData = null; this.isAnalyzing = false; } async analyzeCurrentChat() { if (this.isAnalyzing) return; this.isAnalyzing = true; try { console.log('Starting local chat analysis...'); // Try multiple methods to get chat messages const messages = this.extractChatMessages(); if (messages.length === 0) { console.log('No messages found for analysis'); this.showNoDataMessage(); return; } console.log(`Found ${messages.length} messages for analysis`); // Analyze the messages const analysis = this.performDetailedAnalysis(messages); // Store the results this.currentChatData = analysis; // Show the statistics this.displayLocalStatistics(analysis); } catch (error) { console.error('Error analyzing chat:', error); this.showErrorMessage(error.message); } finally { this.isAnalyzing = false; } } extractChatMessages() { const messages = []; // Method 1: Try to get from existing chat viewer if (window.chatViewer && window.chatViewer.currentMessages) { console.log('Using chatViewer.currentMessages'); return window.chatViewer.currentMessages; } // Method 2: Parse from DOM elements const messageElements = document.querySelectorAll('.message, .chat-message, [data-message]'); console.log(`Found ${messageElements.length} message elements in DOM`); messageElements.forEach((element, index) => { const parsedMessage = this.parseMessageElement(element, index); if (parsedMessage) { messages.push(parsedMessage); } }); // Method 3: Fallback - parse from chat content text if (messages.length === 0) { const chatContent = document.getElementById('chatContent'); if (chatContent) { const textMessages = this.parseFromText(chatContent.textContent); messages.push(...textMessages); } } return messages; } parseMessageElement(element, index) { const text = element.textContent || element.innerText; if (!text.trim()) return null; // Extract components const timestamp = element.querySelector('.timestamp')?.textContent || text.match(/\[(\d{1,2}:\d{2}:\d{2})\]/)?.[1] || ''; const roleElement = element.querySelector('.role-badge'); const role = roleElement ? roleElement.textContent.toLowerCase() : 'member'; const playerElement = element.querySelector('.player-name'); const messageElement = element.querySelector('.message-text'); if (playerElement && messageElement) { return { type: 'chat', timestamp: timestamp.replace(/[\[\]]/g, ''), role: role, player: playerElement.textContent.trim(), message: messageElement.textContent.trim(), index: index }; } // Try to parse join/leave messages if (element.classList.contains('join-leave') || text.includes('joined') || text.includes('left')) { const playerMatch = text.match(/(\w+)\s+(joined|left|connected|disconnected)/i); if (playerMatch) { return { type: playerMatch[2].toLowerCase().includes('join') || playerMatch[2].toLowerCase().includes('connect') ? 'join' : 'leave', timestamp: timestamp.replace(/[\[\]]/g, ''), player: playerMatch[1], message: text.trim(), index: index }; } } // Generic message parsing const messageMatch = text.match(/\[(\d{1,2}:\d{2}:\d{2})\]\s*<([^>]+)>\s*(.+)/); if (messageMatch) { return { type: 'chat', timestamp: messageMatch[1], player: messageMatch[2].trim(), message: messageMatch[3].trim(), role: this.detectRole(text), index: index }; } return null; } parseFromText(text) { const messages = []; const lines = text.split('\n').filter(line => line.trim()); lines.forEach((line, index) => { // Chat message patterns const patterns = [ /\[(\d{1,2}:\d{2}:\d{2})\]\s*<([^>]+)>\s*(.+)/, /\[(\d{1,2}:\d{2}:\d{2})\]\s*([^:]+):\s*(.+)/, /(\d{1,2}:\d{2}:\d{2})\s*<([^>]+)>\s*(.+)/ ]; for (const pattern of patterns) { const match = line.match(pattern); if (match) { messages.push({ type: 'chat', timestamp: match[1], player: match[2].trim(), message: match[3].trim(), role: this.detectRole(line), index: index }); break; } } // Join/leave patterns const joinLeaveMatch = line.match(/\[(\d{1,2}:\d{2}:\d{2})\]\s*([^\s]+)\s+(joined|left|connected|disconnected)/i); if (joinLeaveMatch) { messages.push({ type: joinLeaveMatch[3].toLowerCase().includes('join') || joinLeaveMatch[3].toLowerCase().includes('connect') ? 'join' : 'leave', timestamp: joinLeaveMatch[1], player: joinLeaveMatch[2], message: line.trim(), index: index }); } }); return messages; } detectRole(text) { const rolePatterns = { 'admin': /admin|administrator/i, 'moderator': /mod|moderator/i, 'm-builder': /m-builder|master.builder/i, 'praetorian': /praetorian/i, 'builder': /builder/i, 'member': /member/i }; for (const [role, pattern] of Object.entries(rolePatterns)) { if (pattern.test(text)) { return role; } } return 'member'; } performDetailedAnalysis(messages) { const analysis = { totalMessages: messages.length, chatMessages: 0, systemMessages: 0, joinMessages: 0, leaveMessages: 0, players: new Map(), timeDistribution: new Map(), wordFrequency: new Map(), roleDistribution: new Map(), messageLengths: [], sessionInfo: { startTime: null, endTime: null, duration: null }, topWords: [], topPlayers: [], conversationFlow: [] }; // Process each message messages.forEach((message, index) => { // Session timing if (message.timestamp) { if (!analysis.sessionInfo.startTime || message.timestamp < analysis.sessionInfo.startTime) { analysis.sessionInfo.startTime = message.timestamp; } if (!analysis.sessionInfo.endTime || message.timestamp > analysis.sessionInfo.endTime) { analysis.sessionInfo.endTime = message.timestamp; } } // Message type counting switch (message.type) { case 'chat': analysis.chatMessages++; this.processChatMessage(message, analysis); break; case 'join': analysis.joinMessages++; break; case 'leave': analysis.leaveMessages++; break; default: analysis.systemMessages++; } // Time distribution (hourly) if (message.timestamp) { const hour = message.timestamp.split(':')[0]; analysis.timeDistribution.set(hour, (analysis.timeDistribution.get(hour) || 0) + 1); } }); // Calculate session duration if (analysis.sessionInfo.startTime && analysis.sessionInfo.endTime) { const start = this.parseTime(analysis.sessionInfo.startTime); const end = this.parseTime(analysis.sessionInfo.endTime); if (start && end) { analysis.sessionInfo.duration = Math.max(0, (end - start) / 1000 / 60); // minutes } } // Generate top lists analysis.topWords = Array.from(analysis.wordFrequency.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 20); analysis.topPlayers = Array.from(analysis.players.values()) .sort((a, b) => b.messageCount - a.messageCount) .slice(0, 10); return analysis; } processChatMessage(message, analysis) { const player = message.player; // Player statistics if (!analysis.players.has(player)) { analysis.players.set(player, { name: player, role: message.role, messageCount: 0, totalChars: 0, averageLength: 0, firstMessage: message.timestamp, lastMessage: message.timestamp, wordCount: 0 }); } const playerStats = analysis.players.get(player); playerStats.messageCount++; playerStats.totalChars += message.message.length; playerStats.lastMessage = message.timestamp; playerStats.averageLength = Math.round(playerStats.totalChars / playerStats.messageCount); // Role distribution analysis.roleDistribution.set(message.role, (analysis.roleDistribution.get(message.role) || 0) + 1); // Message length analysis.messageLengths.push(message.message.length); // Word frequency analysis const words = message.message.toLowerCase() .replace(/[^\w\s]/g, ' ') .split(/\s+/) .filter(word => word.length > 2); words.forEach(word => { analysis.wordFrequency.set(word, (analysis.wordFrequency.get(word) || 0) + 1); playerStats.wordCount++; }); } parseTime(timeString) { const match = timeString.match(/(\d{1,2}):(\d{2}):(\d{2})/); if (match) { const [, hours, minutes, seconds] = match; const date = new Date(); date.setHours(parseInt(hours), parseInt(minutes), parseInt(seconds), 0); return date; } return null; } displayLocalStatistics(analysis) { this.createLocalStatsModal(analysis); } createLocalStatsModal(analysis) { // Remove existing modal if any const existingModal = document.getElementById('localStatsModal'); if (existingModal) { existingModal.remove(); } const modal = document.createElement('div'); modal.id = 'localStatsModal'; modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 3000; display: flex; align-items: center; justify-content: center; `; modal.innerHTML = `

📊 Chat Session Statistics

${this.generateStatsHTML(analysis)}
`; // Event listeners modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); }); const closeBtn = modal.querySelector('#closeLocalStats'); closeBtn.addEventListener('click', () => modal.remove()); closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = '#444'; }); closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'none'; }); document.body.appendChild(modal); } generateStatsHTML(analysis) { const avgMessageLength = analysis.messageLengths.length > 0 ? Math.round(analysis.messageLengths.reduce((a, b) => a + b, 0) / analysis.messageLengths.length) : 0; return `
${analysis.totalMessages}
Total Messages
${analysis.players.size}
Active Players
${Math.round(analysis.sessionInfo.duration || 0)}m
Session Length
${avgMessageLength}
Avg Length

👥 Top Players

${analysis.topPlayers.map((player, index) => `
${index + 1}
${player.role} ${player.name}
${player.messageCount}
${player.averageLength} chars avg
`).join('')}

💬 Popular Words

${analysis.topWords.slice(0, 15).map(([word, count]) => { const size = Math.max(0.8, Math.min(1.4, count / Math.max(...analysis.topWords.map(w => w[1])) * 0.8 + 0.6)); return ` ${word} ${count} `; }).join('')}

⏰ Activity Timeline

${this.generateTimelineHTML(analysis.timeDistribution)}

📋 Session Details

Start Time: ${analysis.sessionInfo.startTime || 'N/A'}
End Time: ${analysis.sessionInfo.endTime || 'N/A'}
Chat Messages: ${analysis.chatMessages}
Join/Leave: ${analysis.joinMessages + analysis.leaveMessages}
System Messages: ${analysis.systemMessages}

🏷️ Role Distribution

${Array.from(analysis.roleDistribution.entries()) .sort((a, b) => b[1] - a[1]) .map(([role, count]) => { const percentage = Math.round((count / analysis.chatMessages) * 100); return `
${role}
${count} (${percentage}%)
`; }).join('')}
`; } generateTimelineHTML(timeDistribution) { const hours = Array.from({ length: 24 }, (_, i) => { const hour = i.toString().padStart(2, '0'); const count = timeDistribution.get(hour) || 0; return { hour, count }; }); const maxCount = Math.max(...hours.map(h => h.count), 1); return hours.map(({ hour, count }) => { const height = (count / maxCount) * 100; return `
${hour}
${count > 0 ? `
${count}
` : ''}
`; }).join(''); } getPlayerColor(index) { const colors = ['#007acc', '#52c41a', '#ffc53d', '#ff7875', '#9254de', '#13c2c2', '#fa8c16', '#eb2f96']; return colors[index % colors.length]; } showNoDataMessage() { alert('Keine Chat-Daten für die Analyse gefunden. Bitte wähle zuerst einen Chat aus.'); } showErrorMessage(message) { alert(`Fehler bei der Chat-Analyse: ${message}`); } } // Create global instance window.localChatAnalyzer = new LocalChatAnalyzer();