class MinecraftChatParser { constructor() { this.chatCategories = document.getElementById('chatCategories'); this.categoryFilter = document.getElementById('categoryFilter'); this.categorySelect = document.getElementById('categorySelect'); this.controls = document.getElementById('controls'); this.chatContainer = document.getElementById('chatContainer'); this.chatContent = document.getElementById('chatContent'); this.legend = document.getElementById('legend'); this.messageCountEl = document.getElementById('messageCount'); this.playerCountEl = document.getElementById('playerCount'); this.showTimestamps = document.getElementById('showTimestamps'); this.showJoinLeave = document.getElementById('showJoinLeave'); this.highlightRoles = document.getElementById('highlightRoles'); this.downloadFormatted = document.getElementById('downloadFormatted'); this.downloadMap = document.getElementById('downloadMap'); this.chatData = []; this.players = new Set(); this.availableChats = []; this.categories = {}; this.currentChatInfo = null; this.selectedCategory = 'all'; this.initEventListeners(); this.loadAvailableChats(); } initEventListeners() { // Control events this.showTimestamps.addEventListener('change', () => this.renderChat()); this.showJoinLeave.addEventListener('change', () => this.renderChat()); this.highlightRoles.addEventListener('change', () => this.renderChat()); this.downloadFormatted.addEventListener('click', () => this.downloadAsHTML()); this.downloadMap.addEventListener('click', () => this.downloadMapFile()); // Category filter this.categorySelect.addEventListener('change', (e) => { this.selectedCategory = e.target.value; this.renderChatCategories(); }); } async loadAvailableChats() { try { const response = await fetch('./chat-logs/chat-index.json'); const data = await response.json(); // Handle both old and new format if (Array.isArray(data)) { // Old format - convert to new format this.availableChats = data; this.categories = {}; } else { // New format with categories this.categories = data.categories || {}; this.availableChats = data.chats || []; } this.setupCategoryFilter(); this.renderChatCategories(); } catch (error) { console.error('Error loading chat index:', error); this.chatCategories.innerHTML = `

❌ Error loading chat list

Make sure the server is running and chat files are available.

`; } } setupCategoryFilter() { if (Object.keys(this.categories).length === 0) { this.categoryFilter.style.display = 'none'; return; } this.categoryFilter.style.display = 'block'; this.categorySelect.innerHTML = ''; Object.entries(this.categories).forEach(([key, category]) => { const option = document.createElement('option'); option.value = key; option.textContent = category.name; this.categorySelect.appendChild(option); }); } renderChatCategories() { if (this.availableChats.length === 0) { this.chatCategories.innerHTML = '

No chat logs available

'; return; } // Group chats by category const chatsByCategory = {}; this.availableChats.forEach(chat => { const category = chat.category || 'uncategorized'; if (!chatsByCategory[category]) { chatsByCategory[category] = []; } chatsByCategory[category].push(chat); }); this.chatCategories.innerHTML = ''; // Render categories Object.entries(chatsByCategory).forEach(([categoryKey, chats]) => { if (this.selectedCategory !== 'all' && this.selectedCategory !== categoryKey) { return; } const categorySection = this.createCategorySection(categoryKey, chats); this.chatCategories.appendChild(categorySection); }); } createCategorySection(categoryKey, chats) { const section = document.createElement('div'); section.className = 'category-section'; const category = this.categories[categoryKey] || { name: '📁 Uncategorized', description: 'Chats without a specific category', color: '#666' }; const totalMessages = chats.reduce((sum, chat) => sum + chat.messages, 0); section.innerHTML = `

${category.name}

${category.description}

${chats.length} session${chats.length !== 1 ? 's' : ''}
${totalMessages.toLocaleString()} messages
${chats.map(chat => this.createChatItemHTML(chat)).join('')}
`; return section; } createChatItemHTML(chat) { const formatDate = (dateStr) => { const date = new Date(dateStr); return date.toLocaleDateString('de-DE', { year: 'numeric', month: 'short', day: 'numeric' }); }; const category = this.categories[chat.category]; const categoryBadge = category ? `
${category.name}
` : ''; return `
${categoryBadge}

${chat.title}

${formatDate(chat.date)}

${chat.description}

💬 ${chat.messages} messages 👥 ${chat.participants.length} players
`; } // Keep old function for compatibility renderChatList() { this.renderChatCategories(); } createChatListItem(chat) { const item = document.createElement('div'); item.innerHTML = this.createChatItemHTML(chat); return item.firstElementChild; } async loadChatLog(chatId) { const chat = this.availableChats.find(c => c.id === chatId); if (!chat) { alert('Chat not found!'); return; } try { // Visual feedback const chatItem = document.querySelector(`[data-chat-id="${chatId}"]`); const loadBtn = chatItem.querySelector('.load-chat-btn'); const originalText = loadBtn.textContent; loadBtn.textContent = 'Loading...'; loadBtn.disabled = true; // Load chat file const response = await fetch(`./chat-logs/${chat.filename}`); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const text = await response.text(); this.currentChatInfo = chat; this.parseChat(text); // Update UI this.highlightSelectedChat(chatId); this.updateMapDownloadButton(chat); loadBtn.textContent = '✅ Loaded'; // Reset button after delay setTimeout(() => { loadBtn.textContent = originalText; loadBtn.disabled = false; }, 2000); } catch (error) { console.error('Error loading chat log:', error); alert('Error loading chat log. Is the server running?'); // Reset button const chatItem = document.querySelector(`[data-chat-id="${chatId}"]`); const loadBtn = chatItem.querySelector('.load-chat-btn'); loadBtn.textContent = 'Load chat'; loadBtn.disabled = false; } } highlightSelectedChat(chatId) { // Remove previous selection document.querySelectorAll('.chat-item').forEach(item => { item.classList.remove('selected'); }); // Highlight selected const selectedItem = document.querySelector(`[data-chat-id="${chatId}"]`); if (selectedItem) { selectedItem.classList.add('selected'); } } parseChat(text) { this.chatData = []; this.players.clear(); const lines = text.split('\n').filter(line => line.trim()); lines.forEach(line => { const message = this.parseLine(line.trim()); if (message) { this.chatData.push(message); if (message.player) { this.players.add(message.player); } } }); this.showChatInterface(); this.renderChat(); this.updateStats(); } parseLine(line) { // Match timestamp pattern [HH:MM:SS] const timestampMatch = line.match(/^\[(\d{2}:\d{2}:\d{2})\]/); if (!timestampMatch) return null; const timestamp = timestampMatch[1]; const content = line.substring(timestampMatch[0].length).trim(); // System messages (join/leave/disconnect) if (content.includes('joined the game')) { const joinMatch = content.match(/:\s*(\w+)\s*▎\s*(\w+)\s+joined the game/); if (joinMatch) { return { timestamp, type: 'join', role: joinMatch[1].toLowerCase(), player: joinMatch[2], message: 'joined the game' }; } } if (content.includes('left the game')) { const leaveMatch = content.match(/:\s*(\w+)\s+left the game/); if (leaveMatch) { return { timestamp, type: 'leave', player: leaveMatch[1], message: 'left the game' }; } } if (content.includes('lost connection') || content.includes('Disconnected')) { const disconnectMatch = content.match(/:\s*(\w+)\s+lost connection/); if (disconnectMatch) { return { timestamp, type: 'disconnect', player: disconnectMatch[1], message: 'lost connection: Disconnected' }; } } // Chat messages const chatMatch = content.match(/:\s*(\w+)\s*▎\s*(\w+)\s*:\s*(.+)/); if (chatMatch) { return { timestamp, type: 'chat', role: chatMatch[1].toLowerCase(), player: chatMatch[2], message: chatMatch[3] }; } // Fallback for other system messages if (content.startsWith(':')) { return { timestamp, type: 'system', message: content.substring(1).trim() }; } return null; } showChatInterface() { this.controls.style.display = 'flex'; this.chatContainer.style.display = 'block'; this.legend.style.display = 'block'; } renderChat() { this.chatContent.innerHTML = ''; const showTimestamps = this.showTimestamps.checked; const showJoinLeave = this.showJoinLeave.checked; const highlightRoles = this.highlightRoles.checked; this.chatData.forEach(message => { if (!showJoinLeave && ['join', 'leave', 'disconnect'].includes(message.type)) { return; } const messageEl = this.createMessageElement(message, showTimestamps, highlightRoles); this.chatContent.appendChild(messageEl); }); // Scroll to top instead of bottom to start with oldest messages this.chatContent.scrollTop = 0; } createMessageElement(message, showTimestamps, highlightRoles) { const messageEl = document.createElement('div'); messageEl.className = 'chat-message'; if (message.type === 'system' || ['join', 'leave', 'disconnect'].includes(message.type)) { messageEl.classList.add('system-message'); } if (['join', 'leave', 'disconnect'].includes(message.type)) { messageEl.classList.add('join-leave'); } let html = ''; // Timestamp if (showTimestamps) { html += `[${message.timestamp}]`; } // Message content html += '
'; if (message.type === 'chat') { // Player info with role html += '
'; if (highlightRoles && message.role) { html += `${message.role}`; } html += `${message.player}`; html += '
'; html += `:`; html += `${this.escapeHtml(message.message)}`; } else if (['join', 'leave', 'disconnect'].includes(message.type)) { // Join/Leave messages if (message.role && highlightRoles) { html += `${message.role} `; } if (message.player) { html += `${message.player} `; } html += `${message.message}`; } else { // System messages html += `${this.escapeHtml(message.message)}`; } html += '
'; messageEl.innerHTML = html; return messageEl; } updateStats() { const totalMessages = this.chatData.length; const chatMessages = this.chatData.filter(m => m.type === 'chat').length; const uniquePlayers = this.players.size; // Include chat info if available let displayTitle = 'Chat Log'; if (this.currentChatInfo) { displayTitle = this.currentChatInfo.title; document.querySelector('.chat-header h2').textContent = `📜 ${displayTitle}`; } this.messageCountEl.textContent = `${totalMessages} messages (${chatMessages} chat)`; this.playerCountEl.textContent = `${uniquePlayers} players`; } downloadAsHTML() { const html = this.generateFormattedHTML(); const blob = new Blob([html], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; // Use chat info for filename if available let filename = `minecraft-chat-${new Date().toISOString().split('T')[0]}.html`; if (this.currentChatInfo) { filename = `${this.currentChatInfo.id}-${this.currentChatInfo.date}.html`; } a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } generateFormattedHTML() { const date = new Date().toLocaleDateString('de-DE'); const totalMessages = this.chatData.length; const uniquePlayers = this.players.size; let title = 'Minecraft Chat'; let description = `Exported on ${date}`; if (this.currentChatInfo) { title = this.currentChatInfo.title; description = `${this.currentChatInfo.description} | ${this.currentChatInfo.date}`; } let html = ` ${title}

🎮 ${title}

${description}

Statistics: ${totalMessages} messages from ${uniquePlayers} players
`; this.chatData.forEach(message => { const messageEl = this.createMessageElement(message, true, true); html += messageEl.outerHTML; }); html += `
`; return html; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } updateMapDownloadButton(chat) { const mapButton = this.downloadMap; if (chat.mapDownload && chat.mapDownload.available) { mapButton.style.display = 'inline-block'; mapButton.innerHTML = `🗺️ Download World (${chat.mapDownload.size || 'ZIP'})`; mapButton.title = chat.mapDownload.description || 'Download Minecraft world file'; } else { mapButton.style.display = 'none'; } } downloadMapFile() { if (!this.currentChatInfo || !this.currentChatInfo.mapDownload) { alert('No map download available for this chat session'); return; } const mapInfo = this.currentChatInfo.mapDownload; const mapUrl = `./maps/${mapInfo.filename}`; // Create temporary download link const downloadLink = document.createElement('a'); downloadLink.href = mapUrl; downloadLink.download = mapInfo.filename; downloadLink.style.display = 'none'; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); // Show feedback const originalText = this.downloadMap.innerHTML; this.downloadMap.innerHTML = '⬇️ Downloading...'; setTimeout(() => { this.downloadMap.innerHTML = originalText; }, 2000); } } // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.chatParser = new MinecraftChatParser(); }); // Add some nice console messages console.log('🎮 Minecraft Chat Viewer loaded!'); console.log('Made with ❤️ for SimolZimol\'s Minecraft Chat Collection');