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

550
statistics-integration.js Normal file
View File

@@ -0,0 +1,550 @@
/* 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);
}
});
})();