new file: .gitignore

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
This commit is contained in:
SimolZimol
2025-12-09 15:31:20 +01:00
commit 8ac625a64d
88 changed files with 169343 additions and 0 deletions

580
script.js Normal file
View File

@@ -0,0 +1,580 @@
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');