Files
minecraft-chat-viewer/statistics-integration.js
SimolZimol 8ac625a64d 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
2025-12-09 15:31:20 +01:00

551 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Statistics Integration */
// This file extends the existing functionality without modifying original files
(function() {
let statsButtonAdded = false;
let globalStatsButtonAdded = false;
function addStatsButton() {
if (statsButtonAdded) return;
console.log('Attempting to add stats button...');
// Find the control groups
const controlGroups = document.querySelectorAll('.control-group');
console.log('Found control groups:', controlGroups.length);
if (controlGroups.length >= 2) {
const downloadControlGroup = controlGroups[1]; // Second control group with download buttons
const statsButton = document.createElement('button');
statsButton.id = 'showStats';
statsButton.className = 'btn-secondary';
statsButton.innerHTML = '📊 Statistics';
statsButton.addEventListener('click', () => {
console.log('Stats button clicked');
// Use the new local chat analyzer
if (window.localChatAnalyzer) {
window.localChatAnalyzer.analyzeCurrentChat();
} else {
// Fallback to original method
console.log('window.chatStats available:', !!window.chatStats);
if (window.chatStats) {
console.log('Current data available:', !!window.chatStats.currentData);
if (window.chatStats.currentData) {
window.chatStats.showStats();
} else {
console.log('No statistics data available yet, triggering analysis...');
triggerStatsAnalysis();
setTimeout(() => {
if (window.chatStats.currentData) {
window.chatStats.showStats();
} else {
alert('No chat data available for statistics');
}
}, 500);
}
} else {
console.error('chatStats module not loaded');
alert('Statistics module not loaded');
}
}
});
// Insert before the download map button or at the end
const downloadMapButton = document.getElementById('downloadMap');
if (downloadMapButton) {
downloadControlGroup.insertBefore(statsButton, downloadMapButton);
} else {
downloadControlGroup.appendChild(statsButton);
}
statsButtonAdded = true;
console.log('Stats button added successfully');
} else {
console.log('Control groups not found yet');
}
}
function addGlobalStatsButton() {
if (globalStatsButtonAdded) return;
console.log('Adding global stats button...');
// Add button to header area
const header = document.querySelector('header');
if (header) {
const globalStatsButton = document.createElement('button');
globalStatsButton.id = 'globalStatsButton';
globalStatsButton.className = 'btn-primary global-stats-btn';
globalStatsButton.innerHTML = '📊 Global Chat Statistics';
globalStatsButton.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
padding: 12px 20px;
background: linear-gradient(135deg, #007acc, #40a9ff);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 122, 204, 0.3);
transition: all 0.3s ease;
font-size: 14px;
`;
globalStatsButton.addEventListener('mouseenter', () => {
globalStatsButton.style.transform = 'translateY(-2px)';
globalStatsButton.style.boxShadow = '0 6px 16px rgba(0, 122, 204, 0.4)';
});
globalStatsButton.addEventListener('mouseleave', () => {
globalStatsButton.style.transform = 'translateY(0)';
globalStatsButton.style.boxShadow = '0 4px 12px rgba(0, 122, 204, 0.3)';
});
globalStatsButton.addEventListener('click', () => {
console.log('Global stats button clicked');
showGlobalStatistics();
});
document.body.appendChild(globalStatsButton);
globalStatsButtonAdded = true;
console.log('Global stats button added successfully');
}
}
async function showGlobalStatistics() {
console.log('Loading global statistics...');
try {
// Create modal
const modal = createGlobalStatsModal();
document.body.appendChild(modal);
// Load chat index
const response = await fetch('chat-logs/chat-index.json');
const chatIndex = await response.json();
// Calculate global stats
const globalStats = calculateGlobalStats(chatIndex);
// Display stats
displayGlobalStats(globalStats, chatIndex);
// Show modal
modal.style.display = 'flex';
} catch (error) {
console.error('Error loading global statistics:', error);
alert('Error loading global statistics: ' + error.message);
}
}
function createGlobalStatsModal() {
const modal = document.createElement('div');
modal.id = 'globalStatsModal';
modal.style.cssText = `
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 2000;
align-items: center;
justify-content: center;
`;
modal.innerHTML = `
<div class="global-stats-content" style="
background: #1e1e1e;
color: white;
border-radius: 12px;
width: 90%;
max-width: 1000px;
max-height: 80%;
overflow-y: auto;
padding: 0;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
">
<div class="global-stats-header" style="
padding: 20px;
border-bottom: 1px solid #333;
display: flex;
justify-content: space-between;
align-items: center;
background: #2d2d2d;
border-radius: 12px 12px 0 0;
">
<h2 style="margin: 0; color: #007acc;">📊 Global Chat Statistics</h2>
<button id="closeGlobalStats" style="
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
">×</button>
</div>
<div id="globalStatsBody" style="padding: 20px;">
<div style="text-align: center; padding: 40px;">
<div style="font-size: 48px; margin-bottom: 20px;">📊</div>
<div>Loading global statistics...</div>
</div>
</div>
</div>
`;
// Close button
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.remove();
}
});
setTimeout(() => {
const closeBtn = modal.querySelector('#closeGlobalStats');
if (closeBtn) {
closeBtn.addEventListener('click', () => modal.remove());
}
}, 100);
return modal;
}
function calculateGlobalStats(chatIndex) {
const stats = {
totalChats: chatIndex.chats.length,
totalCategories: Object.keys(chatIndex.categories).length,
categories: {},
dateRange: { earliest: null, latest: null },
totalEstimatedMessages: 0,
averageMessagesPerChat: 0
};
// Calculate category stats
Object.keys(chatIndex.categories).forEach(categoryKey => {
const categoryInfo = chatIndex.categories[categoryKey];
const categoryChats = chatIndex.chats.filter(chat => chat.category === categoryKey);
stats.categories[categoryKey] = {
name: categoryInfo.name,
color: categoryInfo.color,
chatCount: categoryChats.length,
percentage: Math.round((categoryChats.length / stats.totalChats) * 100),
estimatedMessages: categoryChats.reduce((sum, chat) => sum + (chat.messages || 0), 0)
};
});
// Calculate date range and messages
chatIndex.chats.forEach(chat => {
const chatDate = new Date(chat.date);
if (!stats.dateRange.earliest || chatDate < stats.dateRange.earliest) {
stats.dateRange.earliest = chatDate;
}
if (!stats.dateRange.latest || chatDate > stats.dateRange.latest) {
stats.dateRange.latest = chatDate;
}
stats.totalEstimatedMessages += chat.messages || 0;
});
stats.averageMessagesPerChat = Math.round(stats.totalEstimatedMessages / stats.totalChats);
return stats;
}
function displayGlobalStats(stats, chatIndex) {
const body = document.getElementById('globalStatsBody');
if (!body) return;
const categoriesArray = Object.entries(stats.categories)
.sort((a, b) => b[1].chatCount - a[1].chatCount);
body.innerHTML = `
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px;">
<div class="stat-card" style="
background: #2d2d2d;
padding: 20px;
border-radius: 8px;
border: 1px solid #444;
">
<h3 style="color: #007acc; margin: 0 0 15px 0;">📈 Overview</h3>
<div style="display: grid; gap: 10px;">
<div style="display: flex; justify-content: space-between;">
<span>Total Chats:</span>
<strong style="color: #40a9ff;">${stats.totalChats.toLocaleString()}</strong>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Categories:</span>
<strong style="color: #40a9ff;">${stats.totalCategories}</strong>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Est. Messages:</span>
<strong style="color: #40a9ff;">${stats.totalEstimatedMessages.toLocaleString()}</strong>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Avg/Chat:</span>
<strong style="color: #40a9ff;">${stats.averageMessagesPerChat}</strong>
</div>
</div>
</div>
<div class="stat-card" style="
background: #2d2d2d;
padding: 20px;
border-radius: 8px;
border: 1px solid #444;
">
<h3 style="color: #007acc; margin: 0 0 15px 0;">📅 Timeline</h3>
<div style="display: grid; gap: 10px;">
<div style="display: flex; justify-content: space-between;">
<span>First Chat:</span>
<strong style="color: #40a9ff;">${stats.dateRange.earliest?.toLocaleDateString() || 'N/A'}</strong>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Latest Chat:</span>
<strong style="color: #40a9ff;">${stats.dateRange.latest?.toLocaleDateString() || 'N/A'}</strong>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Time Span:</span>
<strong style="color: #40a9ff;">${calculateTimeSpan(stats.dateRange)}</strong>
</div>
</div>
</div>
</div>
<div class="stat-card" style="
background: #2d2d2d;
padding: 20px;
border-radius: 8px;
border: 1px solid #444;
margin-bottom: 20px;
">
<h3 style="color: #007acc; margin: 0 0 20px 0;">📊 Category Breakdown</h3>
<div style="display: grid; gap: 15px;">
${categoriesArray.map(([key, category]) => `
<div style="display: flex; align-items: center; gap: 15px;">
<div style="
width: 16px;
height: 16px;
background: ${category.color};
border-radius: 3px;
flex-shrink: 0;
"></div>
<div style="min-width: 120px; font-weight: 500;">${category.name}</div>
<div style="
flex: 1;
height: 20px;
background: #1a1a1a;
border-radius: 10px;
overflow: hidden;
position: relative;
">
<div style="
height: 100%;
background: linear-gradient(90deg, ${category.color}, ${category.color}aa);
width: ${category.percentage}%;
transition: width 0.5s ease;
"></div>
</div>
<div style="min-width: 100px; text-align: right;">
<strong style="color: #40a9ff;">${category.chatCount}</strong>
<span style="color: #888; margin-left: 8px;">(${category.percentage}%)</span>
</div>
</div>
`).join('')}
</div>
</div>
<div style="
background: #2d2d2d;
padding: 15px;
border-radius: 8px;
border: 1px solid #444;
text-align: center;
color: #888;
font-style: italic;
">
💡 Tip: Click on individual chat logs to see detailed statistics for that session
</div>
`;
}
function calculateTimeSpan(dateRange) {
if (!dateRange.earliest || !dateRange.latest) return 'N/A';
const diffTime = Math.abs(dateRange.latest - dateRange.earliest);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays < 30) {
return `${diffDays} days`;
} else if (diffDays < 365) {
const months = Math.round(diffDays / 30);
return `${months} months`;
} else {
const years = Math.round(diffDays / 365);
return `${years} years`;
}
}
function triggerStatsAnalysis() {
if (!window.chatStats) {
console.log('chatStats not available yet');
return;
}
// Try to get messages from the current chat viewer instance
if (window.chatViewer && window.chatViewer.currentMessages) {
console.log('Using chatViewer.currentMessages:', window.chatViewer.currentMessages.length);
window.chatStats.analyzeChat(window.chatViewer.currentMessages);
return;
}
// Fallback: Parse messages from DOM
const chatMessages = document.querySelectorAll('.message');
console.log('Found DOM messages:', chatMessages.length);
if (chatMessages.length > 0) {
const messages = Array.from(chatMessages).map(msg => {
const timestamp = msg.querySelector('.timestamp')?.textContent || '';
const roleElement = msg.querySelector('.role-badge');
const role = roleElement ? roleElement.textContent.toLowerCase() : 'member';
const playerElement = msg.querySelector('.player-name');
const messageElement = msg.querySelector('.message-text');
if (playerElement && messageElement) {
return {
type: 'chat',
timestamp: timestamp.replace(/[\[\]]/g, ''),
role: role,
player: playerElement.textContent,
message: messageElement.textContent
};
} else if (msg.classList.contains('join-leave')) {
const text = msg.textContent;
return {
type: text.includes('joined') ? 'join' : 'leave',
timestamp: timestamp.replace(/[\[\]]/g, ''),
player: text.match(/(\w+) (joined|left)/)?.[1] || '',
message: text
};
}
return null;
}).filter(msg => msg !== null);
if (messages.length > 0) {
console.log('Analyzing', messages.length, 'messages');
window.chatStats.analyzeChat(messages);
}
}
}
// Wait for the page to load
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM loaded, initializing statistics...');
// Add statistics CSS
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'statistics.css';
document.head.appendChild(link);
// Load local chat analyzer
const analyzerScript = document.createElement('script');
analyzerScript.src = 'local-chat-analyzer.js';
analyzerScript.onload = () => {
console.log('Local chat analyzer loaded');
};
document.head.appendChild(analyzerScript);
// Try to add button immediately
setTimeout(addStatsButton, 100);
// Add global stats button (always available)
setTimeout(addGlobalStatsButton, 200);
// Monitor for controls becoming visible
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// Check if controls became visible
if (mutation.target.id === 'controls' && mutation.attributeName === 'style') {
const controls = document.getElementById('controls');
if (controls && controls.style.display !== 'none') {
console.log('Controls became visible');
setTimeout(addStatsButton, 100);
}
}
// Check if chat content changed
if (mutation.target.id === 'chatContent' && mutation.type === 'childList') {
console.log('Chat content changed');
setTimeout(() => {
addStatsButton();
triggerStatsAnalysis();
}, 200);
}
});
});
// Start observing
const controls = document.getElementById('controls');
const chatContainer = document.getElementById('chatContent');
if (controls) {
observer.observe(controls, {
attributes: true,
attributeFilter: ['style']
});
}
if (chatContainer) {
observer.observe(chatContainer, {
childList: true,
subtree: true
});
}
// Also check periodically for the first few seconds
let checkCount = 0;
const intervalCheck = setInterval(() => {
checkCount++;
addStatsButton();
// Check if there's already chat content
const messages = document.querySelectorAll('.message');
if (messages.length > 0) {
triggerStatsAnalysis();
}
if (checkCount >= 10 || statsButtonAdded) {
clearInterval(intervalCheck);
}
}, 1000);
// Ensure chatStats is available
if (!window.chatStats) {
console.log('Waiting for chatStats to be available...');
const waitForStats = setInterval(() => {
if (window.chatStats) {
console.log('chatStats is now available');
clearInterval(waitForStats);
// Trigger analysis if there are already messages
const messages = document.querySelectorAll('.message');
if (messages.length > 0) {
console.log('Found existing messages, triggering analysis');
triggerStatsAnalysis();
}
}
}, 100);
}
});
})();