/* 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 = `