new file: Dockerfile new file: README.md new file: app.py new file: chat-logs/chat-index.json new file: chat-logs/crea-1-10.08.2020-merged.txt new file: chat-logs/crea-1-11.08.2020-merged.txt new file: chat-logs/crea-1-12.08.2020-merged.txt new file: chat-logs/crea-1-13.08.2020-merged.txt new file: chat-logs/crea-1-14.08.2020-merged.txt new file: chat-logs/crea-1-15.08.2020-merged.txt new file: chat-logs/crea-1-18.08.2020-merged.txt new file: chat-logs/crea-1-20.08.2020-merged.txt new file: chat-logs/crea-1-2020-07-27-1-filtered.txt new file: chat-logs/crea-1-2020-07-28-1-filtered.txt new file: chat-logs/crea-1-2020-07-29-1-filtered.txt new file: chat-logs/crea-1-2020-07-30-1-filtered.txt new file: chat-logs/crea-1-2020-08-03-1-filtered.txt new file: chat-logs/crea-1-2020-08-04-1-filtered.txt new file: chat-logs/crea-1-2020-08-08-1-filtered.txt new file: chat-logs/crea-1-2020-08-09-1-filtered.txt new file: chat-logs/crea-1-2020-08-10-1-filtered.txt new file: chat-logs/crea-1-2020-08-11-1-filtered.txt new file: chat-logs/crea-1-2020-08-13-1-filtered.txt new file: chat-logs/crea-1-2020-08-16-1-filtered.txt new file: chat-logs/crea-1-2020-08-17-1-filtered.txt new file: chat-logs/crea-1-2020-08-18-1-filtered.txt new file: chat-logs/crea-1-2020-08-20-1-filtered.txt new file: chat-logs/crea-1-2020-08-24-1-filtered.txt new file: chat-logs/crea-1-2020-08-29-1-filtered.txt new file: chat-logs/crea-1-2020-08-30-1-filtered.txt new file: chat-logs/crea-1-21.08.2020-merged.txt new file: chat-logs/crea-1-22.08.2020-merged.txt new file: chat-logs/crea-1-23.08.2020-merged.txt new file: chat-logs/crea-1-24.07.2020-merged.txt new file: chat-logs/crea-1-25.07.2020-merged.txt new file: chat-logs/crea-1-25.08.2020-merged.txt new file: chat-logs/crea-1-26.07.2020-merged.txt new file: chat-logs/crea-1-26.08.2020-merged.txt new file: chat-logs/crea-1-27.08.2020-merged.txt new file: chat-logs/crea-1-28.08.2020-merged.txt new file: chat-logs/crea-1-crea-1-10.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-11.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-12.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-14.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-15.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-18.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-20.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-21.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-22.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-23.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-24.07.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-25.07.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-25.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-26.07.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-26.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-27.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-28.08.2020-merged-filtered.txt new file: chat-logs/survival-1-15.08.2020-merged.txt new file: chat-logs/survival-1-2020-07-27-1-filtered.txt new file: chat-logs/survival-1-2020-07-28-1-filtered.txt new file: chat-logs/survival-1-2020-08-07-1-filtered.txt new file: chat-logs/survival-1-2020-08-08-1-filtered.txt new file: chat-logs/survival-1-2020-08-11-1-filtered.txt new file: chat-logs/survival-1-2020-08-13-1-filtered.txt new file: chat-logs/survival-1-2020-08-14-1-filtered.txt new file: chat-logs/survival-1-2020-08-17-1-filtered.txt new file: chat-logs/survival-1-2020-08-18-1-filtered.txt new file: chat-logs/survival-1-2020-08-19-1-filtered.txt new file: chat-logs/survival-1-25.07.2020-merged.txt new file: chat-logs/survival-1-survival-1-15.08.2020-merged-filtered.txt new file: chat-logs/survival-1-survival-1-25.07.2020-merged-filtered.txt new file: chat-logs/thesur-1-2020-08-17-1-filtered.txt new file: chat-logs/thesur-1-2020-08-31-1-filtered.txt new file: count_all_sessions.py new file: count_sessions.py new file: index.html new file: local-chat-analyzer.js new file: merge_daily_logs.py new file: process_thesur_logs.py new file: quick_add.py new file: requirements.txt new file: script.js new file: server.py new file: statistics-integration.js new file: statistics.css new file: statistics.js new file: style.css
581 lines
21 KiB
JavaScript
581 lines
21 KiB
JavaScript
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 = `
|
|
<div class="error-message">
|
|
<p>❌ Error loading chat list</p>
|
|
<p>Make sure the server is running and chat files are available.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
setupCategoryFilter() {
|
|
if (Object.keys(this.categories).length === 0) {
|
|
this.categoryFilter.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
this.categoryFilter.style.display = 'block';
|
|
this.categorySelect.innerHTML = '<option value="all">🌟 All Categories</option>';
|
|
|
|
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 = '<p>No chat logs available</p>';
|
|
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 = `
|
|
<div class="category-header ${categoryKey}">
|
|
<div>
|
|
<h3 class="category-title">${category.name}</h3>
|
|
<p class="category-description">${category.description}</p>
|
|
</div>
|
|
<div class="category-stats">
|
|
<div>${chats.length} session${chats.length !== 1 ? 's' : ''}</div>
|
|
<div>${totalMessages.toLocaleString()} messages</div>
|
|
</div>
|
|
</div>
|
|
<div class="chat-list">
|
|
${chats.map(chat => this.createChatItemHTML(chat)).join('')}
|
|
</div>
|
|
`;
|
|
|
|
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 ?
|
|
`<div class="chat-item-category ${chat.category}">${category.name}</div>` : '';
|
|
|
|
return `
|
|
<div class="chat-item" data-chat-id="${chat.id}">
|
|
${categoryBadge}
|
|
<div class="chat-item-header">
|
|
<h4 class="chat-item-title">${chat.title}</h4>
|
|
<span class="chat-item-date">${formatDate(chat.date)}</span>
|
|
</div>
|
|
<p class="chat-item-description">${chat.description}</p>
|
|
<div class="chat-item-stats">
|
|
<span class="chat-item-stat">
|
|
💬 ${chat.messages} messages
|
|
</span>
|
|
<span class="chat-item-stat">
|
|
👥 ${chat.participants.length} players
|
|
</span>
|
|
</div>
|
|
<button class="load-chat-btn" onclick="chatParser.loadChatLog('${chat.id}')">
|
|
Load chat
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// 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 += `<span class="timestamp">[${message.timestamp}]</span>`;
|
|
}
|
|
|
|
// Message content
|
|
html += '<div class="message-content">';
|
|
|
|
if (message.type === 'chat') {
|
|
// Player info with role
|
|
html += '<div class="player-info">';
|
|
if (highlightRoles && message.role) {
|
|
html += `<span class="role-badge ${message.role}">${message.role}</span>`;
|
|
}
|
|
html += `<span class="player-name">${message.player}</span>`;
|
|
html += '</div>';
|
|
html += `<span class="message-separator">:</span>`;
|
|
html += `<span class="message-text">${this.escapeHtml(message.message)}</span>`;
|
|
} else if (['join', 'leave', 'disconnect'].includes(message.type)) {
|
|
// Join/Leave messages
|
|
if (message.role && highlightRoles) {
|
|
html += `<span class="role-badge ${message.role}">${message.role}</span> `;
|
|
}
|
|
if (message.player) {
|
|
html += `<span class="player-name">${message.player}</span> `;
|
|
}
|
|
html += `<span class="message-text">${message.message}</span>`;
|
|
} else {
|
|
// System messages
|
|
html += `<span class="message-text">${this.escapeHtml(message.message)}</span>`;
|
|
}
|
|
|
|
html += '</div>';
|
|
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 = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>${title}</title>
|
|
<style>
|
|
body { font-family: 'Consolas', monospace; background: #1a1a1a; color: #fff; padding: 20px; }
|
|
.header { text-align: center; margin-bottom: 30px; }
|
|
.stats { background: #2d2d2d; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
|
.chat-message { margin-bottom: 8px; padding: 8px; border-radius: 4px; }
|
|
.chat-message:hover { background: #2d2d2d; }
|
|
.timestamp { color: #888; margin-right: 15px; }
|
|
.role-badge { padding: 2px 8px; border-radius: 12px; font-size: 0.8em; margin-right: 8px; }
|
|
.role-badge.admin { background: #ff4444; }
|
|
.role-badge.mod { background: #ffa500; }
|
|
.role-badge.member { background: #4CAF50; }
|
|
.role-badge.m-builder { background: #9C27B0; }
|
|
.role-badge.praetorian { background: #8E24AA; }
|
|
.player-name { font-weight: bold; margin-right: 8px; }
|
|
.message-text { color: #ccc; }
|
|
.system-message { font-style: italic; background: rgba(158, 158, 158, 0.1); }
|
|
.join-leave { background: rgba(76, 175, 80, 0.1); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>🎮 ${title}</h1>
|
|
<p>${description}</p>
|
|
</div>
|
|
<div class="stats">
|
|
<strong>Statistics:</strong> ${totalMessages} messages from ${uniquePlayers} players
|
|
</div>
|
|
<div class="chat-content">`;
|
|
|
|
this.chatData.forEach(message => {
|
|
const messageEl = this.createMessageElement(message, true, true);
|
|
html += messageEl.outerHTML;
|
|
});
|
|
|
|
html += `
|
|
</div>
|
|
</body>
|
|
</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');
|