您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Statistiche board Bitcointalk con grafici interattivi, download immagini, spinner "Dot Wave", sintesi testuale, distribuzione post, e utenti con maggior impatto
// ==UserScript== // @name Bitcointalk Board Stats // @namespace http://tampermonkey.net/ // @version 1.5.8 // @description Statistiche board Bitcointalk con grafici interattivi, download immagini, spinner "Dot Wave", sintesi testuale, distribuzione post, e utenti con maggior impatto // @author Ace // @match https://bitcointalk.org/* // @grant GM_xmlhttpRequest // @grant fetch // @license MIT // ==/UserScript== (function() { 'use strict'; // Funzione per caricare Chart.js dinamicamente async function loadChartJS() { return new Promise((resolve, reject) => { if (window.Chart) { resolve(); return; } const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/chart.js'; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } // Funzione per aprire l'immagine del grafico in una nuova scheda function openChartImage(canvasId, chartTitle) { const canvas = document.getElementById(canvasId); const image = canvas.toDataURL('image/png'); const newWindow = window.open('', '_blank'); newWindow.document.write(` <html> <head> <title>${chartTitle}</title> </head> <body style="text-align: center; padding: 20px;"> <h3>${chartTitle}</h3> <img src="${image}" style="max-width: 100%; border: 1px solid #ccc;" /> <p>Clicca con il tasto destro sull'immagine e seleziona "Salva immagine con nome" per scaricarla.</p> </body> </html> `); newWindow.document.close(); } // Funzione per recuperare il nome di una board async function fetchBoardName(boardId) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: `https://api.ninjastic.space/boards`, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.result !== "success" || !data.data) { console.error("Errore API o dati mancanti:", data); resolve(`Board ${boardId}`); return; } // Cerca la board nell'array principale for (const board of data.data) { if (board.value == boardId) { console.log("Board trovata:", board.title); resolve(board.title || `Board ${boardId}`); return; } // Cerca nelle child boards if (board.children && board.children.length > 0) { for (const child of board.children) { if (child.value == boardId) { console.log("Child board trovata:", child.title); resolve(child.title || `Board ${boardId}`); return; } } } } // Se non trovato, restituisci un valore di default resolve(`Board ${boardId}`); } catch (err) { console.error("Errore parsing board name:", err); resolve(`Board ${boardId}`); } }, onerror: function(error) { console.error("Errore richiesta board name:", error); resolve(`Board ${boardId}`); } }); }); } // Funzione per recuperare l'elenco delle board async function fetchAllBoards() { return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: `https://api.ninjastic.space/boards`, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.result !== "success" || !data.data) { console.error("Errore API o dati mancanti:", data); resolve([]); return; } const boards = []; const processBoards = (boardList, parentName = "") => { boardList.forEach(board => { const boardName = board.title || `Board ${board.value}`; const displayName = parentName ? `${parentName} > ${boardName}` : boardName; boards.push({ id: board.value, name: boardName, displayName: `${board.value} - ${boardName}` }); if (board.children && board.children.length > 0) { processBoards(board.children, boardName); } }); }; processBoards(data.data); resolve(boards); } catch (err) { console.error("Errore parsing boards:", err); resolve([]); } }, onerror: function(error) { console.error("Errore richiesta boards:", error); resolve([]); } }); }); } // Funzione per recuperare le child boards async function fetchChildBoards(boardId) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://api.ninjastic.space/boards`, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.result !== "success") { throw new Error("Errore API: " + (data.message || "Dati non validi")); } const childBoards = []; const findChildren = (boards) => { boards.forEach(board => { if (board.parent === parseInt(boardId)) { const boardTitle = board.title || `Board ${board.value}`; childBoards.push({ id: board.value, title: boardTitle }); } if (board.children && board.children.length > 0) { findChildren(board.children); } }); }; findChildren(data.data); resolve(childBoards); } catch (err) { console.error("Errore parsing child boards:", err); reject(err); } }, onerror: function(error) { console.error("Errore richiesta child boards:", error); reject(error); } }); }); } // Funzione per recuperare i dati Merit da più board async function fetchMeritBatchData(boardIds, startDate, endDate) { let allSenders = {}; let allReceivers = {}; for (const boardId of boardIds) { try { const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://beta.ninjastic.space/trpc/posts.posts_per_day_histogram,merits.merits_per_day_histogram,merits.top_merit_users,posts.top_users_by_post_count,posts.count_unique_users?batch=1&input=%7B%220%22%3A%7B%22date_min%22%3A%22${startDate}%22%2C%22date_max%22%3A%22${endDate}%22%2C%22board_id%22%3A${boardId}%2C%22interval%22%3A%221d%22%7D%2C%221%22%3A%7B%22date_min%22%3A%22${startDate}%22%2C%22date_max%22%3A%22${endDate}%22%2C%22board_id%22%3A${boardId}%2C%22interval%22%3A%221d%22%7D%2C%222%22%3A%7B%22date_min%22%3A%22${startDate}%22%2C%22date_max%22%3A%22${endDate}%22%2C%22board_id%22%3A${boardId}%7D%2C%223%22%3A%7B%22date_min%22%3A%22${startDate}%22%2C%22date_max%22%3A%22${endDate}%22%2C%22board_id%22%3A${boardId}%7D%2C%224%22%3A%7B%22date_min%22%3A%22${startDate}%22%2C%22date_max%22%3A%22${endDate}%22%2C%22board_id%22%3A${boardId}%7D%7D`, onload: function(response) { try { const data = JSON.parse(response.responseText); resolve(data); } catch (err) { console.error("Errore parsing dati Merit per board " + boardId + ":", err); reject(err); } }, onerror: function(error) { console.error("Errore richiesta Merit per board " + boardId + ":", error); reject(error); } }); }); // Estrai i dati corretti dalla risposta batch const topSenders = response[2].result.data.top_senders || []; const topReceivers = response[2].result.data.top_receivers || []; // Aggrega i dati Merit topSenders.forEach(user => { if (!allSenders[user.user_uid]) { allSenders[user.user_uid] = { sum: 0, user: user.user }; } allSenders[user.user_uid].sum += user.sum; }); topReceivers.forEach(user => { if (!allReceivers[user.user_uid]) { allReceivers[user.user_uid] = { sum: 0, user: user.user }; } allReceivers[user.user_uid].sum += user.sum; }); } catch (err) { console.error("Errore durante il recupero dei dati Merit per board " + boardId + ":", err); } } // Ordina e restituisce le top 10 const topSenders = Object.entries(allSenders) .sort((a, b) => b[1].sum - a[1].sum) .slice(0, 10) .map(([uid, data]) => ({ user_uid: uid, user: data.user, sum: data.sum })); const topReceivers = Object.entries(allReceivers) .sort((a, b) => b[1].sum - a[1].sum) .slice(0, 10) .map(([uid, data]) => ({ user_uid: uid, user: data.user, sum: data.sum })); return { sender: topSenders, receiver: topReceivers }; } // Crea la pagina fittizia delle statistiche function createStatPage() { if (document.querySelector('#fake-stat-page')) return; const page = document.createElement('div'); page.id = 'fake-stat-page'; page.style.position = 'fixed'; page.style.top = '0'; page.style.left = '0'; page.style.width = '100%'; page.style.height = '100%'; page.style.backgroundColor = 'rgba(0,0,0,0.5)'; page.style.zIndex = '9999'; page.style.overflowY = 'auto'; page.style.fontFamily = 'Verdana, Arial, sans-serif'; page.style.fontSize = '14px'; page.innerHTML = ` <div style=" max-width: 950px; margin: 20px auto; background: white; padding: 20px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.2); border: 1px solid #ddd; "> <h2 style="text-align: center; margin-bottom: 20px; color: #2e3b4e;">Statistiche Board</h2> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">Board:</label> <select id="board-select" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 3px; margin-bottom: 10px;"> <option value="">Caricamento board in corso...</option> </select> <div style="display: flex; align-items: center; margin-bottom: 10px;"> <span style="margin-right: 10px;">oppure</span> <input type="text" id="board-id" placeholder="Inserisci ID board manualmente" style="flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 3px;"> </div> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">Mese corrente (inizio):</label> <input type="datetime-local" id="current-start" value="2025-08-01T00:00:00" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 3px;"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">Mese corrente (fine):</label> <input type="datetime-local" id="current-end" value="2025-08-31T23:59:59" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 3px;"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">Mese precedente (inizio):</label> <input type="datetime-local" id="previous-start" value="2025-07-01T00:00:00" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 3px;"> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;">Mese precedente (fine):</label> <input type="datetime-local" id="previous-end" value="2025-07-31T23:59:59" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 3px;"> </div> <div style="margin-bottom: 20px;"> <label style="display: inline-flex; align-items: center; cursor: pointer;"> <input type="checkbox" id="child-boards" style="margin-right: 8px;"> Includi child boards </label> </div> <button id="generate-stats" style=" width: 100%; padding: 10px; font-weight: bold; background: #2e3b4e; color: white; border: none; border-radius: 3px; cursor: pointer; ">Genera Statistiche</button> <div id="stats-preview" style="margin-top: 20px; display: none;"> <h3 style="border-bottom: 1px solid #eee; padding-bottom: 5px;">Anteprima Tabella</h3> <div id="preview-table" style="overflow-x: auto;"></div> <div id="charts-container" style="margin-top: 20px;"></div> </div> <div id="stats-output" style=" margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 3px; border: 1px solid #ddd; min-height: 100px; display: none; "> <h3 style="border-bottom: 1px solid #eee; padding-bottom: 5px;">BBCode</h3> <textarea id="bbcode-output" style=" width: 100%; height: 300px; padding: 10px; font-family: monospace; border: 1px solid #ccc; border-radius: 3px; resize: vertical; "></textarea> <button id="copy-bbcode" style=" display: block; margin: 10px auto 0; padding: 8px 16px; background: #2e3b4e; color: white; border: none; border-radius: 3px; cursor: pointer; ">Copia BBCode</button> </div> <button id="close-page" style=" display: block; margin: 20px auto 0; padding: 8px 16px; font-weight: bold; background: #ccc; border: none; border-radius: 3px; cursor: pointer; ">Chiudi</button> </div> `; document.body.appendChild(page); document.querySelector('#close-page').onclick = () => page.remove(); document.querySelector('#generate-stats').onclick = generateStats; document.querySelector('#copy-bbcode').onclick = copyBBCode; // Carica le board nel menu a tendina fetchAllBoards().then(boards => { const select = document.querySelector('#board-select'); select.innerHTML = '<option value="">Seleziona una board</option>'; boards.forEach(board => { const option = document.createElement('option'); option.value = board.id; option.textContent = board.displayName; select.appendChild(option); }); }); } // Copia BBCode negli appunti function copyBBCode() { const textarea = document.querySelector('#bbcode-output'); textarea.select(); try { navigator.clipboard.writeText(textarea.value) .then(() => alert('BBCode copiato negli appunti!')) .catch(() => { document.execCommand('copy'); alert('BBCode copiato negli appunti!'); }); } catch (err) { document.execCommand('copy'); alert('BBCode copiato negli appunti!'); } } // Ottieni il nome del mese da una data function getMonthName(dateString) { const months = ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic']; const date = new Date(dateString); return months[date.getMonth()]; } // Crea un grafico a barre function createBarChart(canvasId, labels, data, title, label) { const ctx = document.getElementById(canvasId).getContext('2d'); new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: label, data: data, backgroundColor: 'rgba(46, 59, 78, 0.7)', borderColor: 'rgba(46, 59, 78, 1)', borderWidth: 1 }] }, options: { responsive: true, plugins: { title: { display: true, text: title, font: { size: 14 } }, legend: { display: false } }, scales: { y: { beginAtZero: true } } } }); } // Crea un grafico a torta function createPieChart(canvasId, labels, data, title) { const ctx = document.getElementById(canvasId).getContext('2d'); new Chart(ctx, { type: 'pie', data: { labels: labels, datasets: [{ data: data, backgroundColor: [ 'rgba(46, 59, 78, 0.7)', 'rgba(70, 130, 180, 0.7)', 'rgba(123, 104, 238, 0.7)', 'rgba(50, 205, 50, 0.7)', 'rgba(255, 165, 0, 0.7)', 'rgba(255, 69, 0, 0.7)' ], borderColor: [ 'rgba(46, 59, 78, 1)', 'rgba(70, 130, 180, 1)', 'rgba(123, 104, 238, 1)', 'rgba(50, 205, 50, 1)', 'rgba(255, 165, 0, 1)', 'rgba(255, 69, 0, 1)' ], borderWidth: 1 }] }, options: { responsive: true, plugins: { title: { display: true, text: title, font: { size: 14 } }, legend: { position: 'right' } } } }); } // Genera le statistiche con intestazione dinamica e Merit stats async function generateStats() { await loadChartJS(); const boardSelect = document.querySelector('#board-select'); const boardIdInput = document.querySelector('#board-id').value.trim(); const boardId = boardSelect.value || boardIdInput; if (!boardId) { alert('Seleziona o inserisci una board!'); return; } const currentStart = document.querySelector('#current-start').value; const currentEnd = document.querySelector('#current-end').value; const previousStart = document.querySelector('#previous-start').value; const previousEnd = document.querySelector('#previous-end').value; const childBoards = document.querySelector('#child-boards').checked; if (!currentStart || !currentEnd || !previousStart || !previousEnd) { alert('Compila tutti i campi!'); return; } // Nomi dei mesi per l'intestazione const currentMonthName = getMonthName(currentStart); const previousMonthName = getMonthName(previousStart); // Recupera il nome della board const boardName = await fetchBoardName(boardId); const previewDiv = document.querySelector('#stats-preview'); const outputDiv = document.querySelector('#stats-output'); const chartsContainer = document.querySelector('#charts-container'); previewDiv.style.display = 'none'; outputDiv.style.display = 'none'; chartsContainer.innerHTML = ''; // Spinner di caricamento "Dot Wave" const loadingMsg = document.createElement('div'); loadingMsg.style.textAlign = 'center'; loadingMsg.style.margin = '20px 0'; loadingMsg.innerHTML = ` <div class="lds-ellipsis"> <div></div> <div></div> <div></div> <div></div> </div> <p style="color: #666; margin-top: 10px;">Caricamento dati in corso...</p> <style> .lds-ellipsis { display: inline-block; position: relative; width: 64px; height: 64px; } .lds-ellipsis div { position: absolute; top: 27px; width: 11px; height: 11px; border-radius: 50%; background: #2e3b4e; animation-timing-function: cubic-bezier(0, 1, 1, 0); } .lds-ellipsis div:nth-child(1) { left: 6px; animation: lds-ellipsis1 0.6s infinite; } .lds-ellipsis div:nth-child(2) { left: 6px; animation: lds-ellipsis2 0.6s infinite; } .lds-ellipsis div:nth-child(3) { left: 26px; animation: lds-ellipsis2 0.6s infinite; } .lds-ellipsis div:nth-child(4) { left: 45px; animation: lds-ellipsis3 0.6s infinite; } @keyframes lds-ellipsis1 { 0% { transform: scale(0); } 100% { transform: scale(1); } } @keyframes lds-ellipsis3 { 0% { transform: scale(1); } 100% { transform: scale(0); } } @keyframes lds-ellipsis2 { 0% { transform: translate(0, 0); } 100% { transform: translate(19px, 0); } } </style> `; previewDiv.parentNode.insertBefore(loadingMsg, previewDiv); try { // Fetch dati mese corrente (post) const currentUrl = `https://api.ninjastic.space/posts/authors?board=${boardId}&child_boards=${childBoards}&after_date=${currentStart}&before_date=${currentEnd}&limit=1000`; const currentRes = await fetch(currentUrl); const currentJson = await currentRes.json(); // Fetch dati mese precedente (post) const previousUrl = `https://api.ninjastic.space/posts/authors?board=${boardId}&child_boards=${childBoards}&after_date=${previousStart}&before_date=${previousEnd}&limit=1000`; const previousRes = await fetch(previousUrl); const previousJson = await previousRes.json(); if (currentJson.result !== "success" || previousJson.result !== "success") { loadingMsg.innerHTML = `<p style="color: red; text-align: center;">Errore API: ${currentJson.message || previousJson.message || "Sconosciuto"}</p>`; return; } const currentAuthors = currentJson.data.authors; const previousAuthors = previousJson.data.authors; // Crea mappa post mese precedente (author_uid -> count) const previousPostsMap = {}; previousAuthors.forEach(a => { previousPostsMap[a.author_uid] = a.count || 0; }); // Unisci e ordina gli utenti per post del mese corrente const allAuthors = [...currentAuthors]; allAuthors.sort((a, b) => (b.count || 0) - (a.count || 0)); // Calcola i totali const totalPostsCurrent = currentAuthors.reduce((sum, a) => sum + (a.count || 0), 0); const boardIds = childBoards ? [boardId, ...(await fetchChildBoards(boardId)).map(b => b.id)] : [boardId]; const meritData = await fetchMeritBatchData( boardIds, currentStart.split('T')[0], currentEnd.split('T')[0] ); const totalMeritSent = meritData.sender.reduce((sum, u) => sum + u.sum, 0); const totalMeritReceived = meritData.receiver.reduce((sum, u) => sum + u.sum, 0); const avgMeritPerPost = totalPostsCurrent > 0 ? (totalMeritReceived / totalPostsCurrent).toFixed(2) : 0; // Distribuzione post per child board (se attivate) let childBoardDistribution = {}; if (childBoards) { const childBoardsList = await fetchChildBoards(boardId); for (const child of childBoardsList) { const childUrl = `https://api.ninjastic.space/posts/authors?board=${child.id}&after_date=${currentStart}&before_date=${currentEnd}&limit=1000`; const childRes = await fetch(childUrl); const childJson = await childRes.json(); if (childJson.result === "success") { const childPosts = childJson.data.authors.reduce((sum, a) => sum + (a.count || 0), 0); childBoardDistribution[child.title] = childPosts; } } } // Anteprima tabella HTML (post) let previewTable = ` <table style="width: 100%; border-collapse: collapse; margin-bottom: 15px; font-size: 13px;"> <tr style="background: #f0f0f0;"> <th style="padding: 8px; text-align: center; border: 1px solid #ddd; width: 5%;">Pos.</th> <th style="padding: 8px; text-align: left; border: 1px solid #ddd; width: 25%;">User</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd; width: 10%;">Post (${currentMonthName})</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd; width: 10%;">Post (${previousMonthName})</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd; width: 10%;">Change</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd; width: 10%;">BPIP</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd; width: 10%;">Ninjastic</th> </tr> `; allAuthors.forEach((a, index) => { const username = a.author || 'Sconosciuto'; const userUid = a.author_uid || ''; const currentCount = a.count || 0; const previousCount = previousPostsMap[userUid] || 0; const diff = currentCount - previousCount; const variationColor = diff >= 0 ? 'green' : 'red'; const variationText = diff >= 0 ? `▲${diff}` : `▼${Math.abs(diff)}`; const userLink = userUid ? `<a href="https://bitcointalk.org/index.php?action=profile;u=${userUid}" target="_blank">${username}</a>` : username; previewTable += ` <tr style="border: 1px solid #ddd;"> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${index + 1}.</td> <td style="padding: 8px; border: 1px solid #ddd;">${userLink}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${currentCount}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${previousCount}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center; color: ${variationColor};">${variationText}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://bpip.org/Profile?p=${username}" target="_blank"> <img src="https://www.talkimg.com/images/2023/08/03/GXlZb.png" width="15"> </a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://ninjastic.space/user/${username}" target="_blank"> <img src="https://talkimg.com/images/2023/08/03/GwdMz.png" width="15"> </a> </td> </tr> `; }); previewTable += `</table>`; // BBCode con intestazione dinamica (post) let bbcode = `[center][b][size=12pt]Statistiche Board[/size][/b][/center] [center][i]Confronto: ${previousMonthName} vs ${currentMonthName}[/i][/center] [table] [tr] [td][b]Pos.[/b][/td] [td][b]User[/b][/td] [td][b]Post (${currentMonthName})[/b][/td] [td][b]Post (${previousMonthName})[/b][/td] [td][b]Change[/b][/td] [td][b]BPIP[/b][/td] [td][b]Ninjastic[/b][/td] [/tr] `; allAuthors.forEach((a, index) => { const username = a.author || 'Sconosciuto'; const userUid = a.author_uid || ''; const currentCount = a.count || 0; const previousCount = previousPostsMap[userUid] || 0; const diff = currentCount - previousCount; const variationColor = diff >= 0 ? 'green' : 'red'; const variationText = diff >= 0 ? `[color=${variationColor}]▲${diff}[/color]` : `[color=${variationColor}]▼${Math.abs(diff)}[/color]`; const userLink = userUid ? `[url=https://bitcointalk.org/index.php?action=profile;u=${userUid}]${username}[/url]` : username; bbcode += `[tr] [td]${index + 1}.[/td] [td]${userLink}[/td] [td]${currentCount}[/td] [td]${previousCount}[/td] [td]${variationText}[/td] [td][url=https://bpip.org/Profile?p=${username}][img width=15]https://www.talkimg.com/images/2023/08/03/GXlZb.png[/img][/url][/td] [td][url=https://ninjastic.space/user/${username}][img width=15]https://talkimg.com/images/2023/08/03/GwdMz.png[/img][/url][/td] [/tr] `; }); bbcode += `[/table]`; // Analizza la distribuzione dei post per utente const postDistribution = { "1 post": 0, "2-5 post": 0, "6-10 post": 0, "11-20 post": 0, "21-50 post": 0, ">50 post": 0 }; allAuthors.forEach(author => { const postCount = author.count || 0; if (postCount === 1) postDistribution["1 post"]++; else if (postCount >= 2 && postCount <= 5) postDistribution["2-5 post"]++; else if (postCount >= 6 && postCount <= 10) postDistribution["6-10 post"]++; else if (postCount >= 11 && postCount <= 20) postDistribution["11-20 post"]++; else if (postCount >= 21 && postCount <= 50) postDistribution["21-50 post"]++; else if (postCount > 50) postDistribution[">50 post"]++; }); // Genera la tabella HTML per la distribuzione dei post let postDistributionPreview = ` <div style="margin-top: 20px;"> <h4 style="text-align: center; margin-bottom: 10px;">Distribuzione Post per Utente (${currentMonthName})</h4> <table style="width: 100%; border-collapse: collapse; font-size: 13px;"> <tr style="background: #f0f0f0;"> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Intervallo Post</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Num. Utenti</th> </tr> `; Object.entries(postDistribution).forEach(([range, count]) => { postDistributionPreview += ` <tr style="border: 1px solid #ddd;"> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${range}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${count}</td> </tr> `; }); postDistributionPreview += `</table></div>`; // Genera il BBCode per la distribuzione dei post let postDistributionBBCode = ` [center][b]Distribuzione Post per Utente (${currentMonthName})[/b][/center] [table] [tr] [td][b]Intervallo Post[/b][/td] [td][b]Num. Utenti[/b][/td] [/tr] `; Object.entries(postDistribution).forEach(([range, count]) => { postDistributionBBCode += ` [tr] [td]${range}[/td] [td]${count}[/td] [/tr] `; }); postDistributionBBCode += `[/table]`; // Anteprima tabelle Merit (affiancate) let meritPreview = ` <div style="display: flex; gap: 20px; margin-top: 20px;"> <div style="flex: 1;"> <h4 style="text-align: center; margin-bottom: 10px;">Top 10 Merit Sender</h4> <table style="width: 100%; border-collapse: collapse; font-size: 13px;"> <tr style="background: #f0f0f0;"> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Pos.</th> <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">User</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Merit Inviati</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">BPIP</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Ninjastic</th> </tr> `; meritData.sender.slice(0, 10).forEach((user, index) => { meritPreview += ` <tr style="border: 1px solid #ddd;"> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${index + 1}.</td> <td style="padding: 8px; border: 1px solid #ddd;"> <a href="https://bitcointalk.org/index.php?action=profile;u=${user.user_uid}" target="_blank">${user.user}</a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${user.sum}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://bpip.org/Profile?p=${user.user}" target="_blank"> <img src="https://www.talkimg.com/images/2023/08/03/GXlZb.png" width="15"> </a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://ninjastic.space/user/${user.user}" target="_blank"> <img src="https://talkimg.com/images/2023/08/03/GwdMz.png" width="15"> </a> </td> </tr> `; }); meritPreview += `</table></div>`; // Tabella Merit Receiver meritPreview += ` <div style="flex: 1;"> <h4 style="text-align: center; margin-bottom: 10px;">Top 10 Merit Receiver</h4> <table style="width: 100%; border-collapse: collapse; font-size: 13px;"> <tr style="background: #f0f0f0;"> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Pos.</th> <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">User</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Merit Ricevuti</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">BPIP</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Ninjastic</th> </tr> `; meritData.receiver.slice(0, 10).forEach((user, index) => { meritPreview += ` <tr style="border: 1px solid #ddd;"> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${index + 1}.</td> <td style="padding: 8px; border: 1px solid #ddd;"> <a href="https://bitcointalk.org/index.php?action=profile;u=${user.user_uid}" target="_blank">${user.user}</a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${user.sum}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://bpip.org/Profile?p=${user.user}" target="_blank"> <img src="https://www.talkimg.com/images/2023/08/03/GXlZb.png" width="15"> </a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://ninjastic.space/user/${user.user}" target="_blank"> <img src="https://talkimg.com/images/2023/08/03/GwdMz.png" width="15"> </a> </td> </tr> `; }); meritPreview += `</table></div></div>`; // BBCode per Merit (affiancate) let meritBBCode = ` [table] [tr] [td][center][b]Top 10 Merit Sender[/b][/center][/td] [td][center][b]Top 10 Merit Receiver[/b][/center][/td] [/tr] [tr] [td] [table] [tr] [td][b]Pos.[/b][/td] [td][b]User[/b][/td] [td][b]Merit Inviati[/b][/td] [td][b]BPIP[/b][/td] [td][b]Ninjastic[/b][/td] [/tr] `; meritData.sender.slice(0, 10).forEach((user, index) => { meritBBCode += ` [tr] [td]${index + 1}.[/td] [td][url=https://bitcointalk.org/index.php?action=profile;u=${user.user_uid}]${user.user}[/url][/td] [td]${user.sum}[/td] [td][url=https://bpip.org/Profile?p=${user.user}][img width=15]https://www.talkimg.com/images/2023/08/03/GXlZb.png[/img][/url][/td] [td][url=https://ninjastic.space/user/${user.user}][img width=15]https://talkimg.com/images/2023/08/03/GwdMz.png[/img][/url][/td] [/tr] `; }); meritBBCode += ` [/table] [/td] [td] [table] [tr] [td][b]Pos.[/b][/td] [td][b]User[/b][/td] [td][b]Merit Ricevuti[/b][/td] [td][b]BPIP[/b][/td] [td][b]Ninjastic[/b][/td] [/tr] `; meritData.receiver.slice(0, 10).forEach((user, index) => { meritBBCode += ` [tr] [td]${index + 1}.[/td] [td][url=https://bitcointalk.org/index.php?action=profile;u=${user.user_uid}]${user.user}[/url][/td] [td]${user.sum}[/td] [td][url=https://bpip.org/Profile?p=${user.user}][img width=15]https://www.talkimg.com/images/2023/08/03/GXlZb.png[/img][/url][/td] [td][url=https://ninjastic.space/user/${user.user}][img width=15]https://talkimg.com/images/2023/08/03/GwdMz.png[/img][/url][/td] [/tr] `; }); meritBBCode += ` [/table] [/td] [/tr] [/table] `; // Unisci i dati dei post e dei merit per calcolare il rateo const userRateoMap = {}; allAuthors.forEach(author => { const userUid = author.author_uid; const postCount = author.count || 0; const meritUser = meritData.receiver.find(u => u.user_uid == userUid); const meritCount = meritUser ? meritUser.sum : 0; const rateo = postCount > 0 ? (meritCount / postCount).toFixed(2) : 0; userRateoMap[userUid] = { user: author.author, userUid, postCount, meritCount, rateo: parseFloat(rateo) }; }); // Ordina per rateo e prendi i primi 10 (filtra utenti con almeno 1 post) const topRateoUsers = Object.values(userRateoMap) .filter(user => user.postCount > 0) .sort((a, b) => b.rateo - a.rateo) .slice(0, 10); // Genera la tabella HTML per il rateo let rateoPreview = ` <div style="margin-top: 20px;"> <h4 style="text-align: center; margin-bottom: 10px;">Top 10 Utenti per Rateo Merit/Post</h4> <table style="width: 100%; border-collapse: collapse; font-size: 13px;"> <tr style="background: #f0f0f0;"> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Pos.</th> <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">User</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Post</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Merit Ricevuti</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Rateo</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">BPIP</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Ninjastic</th> </tr> `; topRateoUsers.forEach((user, index) => { rateoPreview += ` <tr style="border: 1px solid #ddd;"> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${index + 1}.</td> <td style="padding: 8px; border: 1px solid #ddd;"> <a href="https://bitcointalk.org/index.php?action=profile;u=${user.userUid}" target="_blank">${user.user}</a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${user.postCount}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${user.meritCount}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${user.rateo}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://bpip.org/Profile?p=${user.user}" target="_blank"> <img src="https://www.talkimg.com/images/2023/08/03/GXlZb.png" width="15"> </a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://ninjastic.space/user/${user.user}" target="_blank"> <img src="https://talkimg.com/images/2023/08/03/GwdMz.png" width="15"> </a> </td> </tr> `; }); rateoPreview += `</table></div>`; // Genera il BBCode per il rateo let rateoBBCode = ` [center][b]Top 10 Utenti per Rateo Merit/Post[/b][/center] [table] [tr] [td][b]Pos.[/b][/td] [td][b]User[/b][/td] [td][b]Post[/b][/td] [td][b]Merit Ricevuti[/b][/td] [td][b]Rateo[/b][/td] [td][b]BPIP[/b][/td] [td][b]Ninjastic[/b][/td] [/tr] `; topRateoUsers.forEach((user, index) => { rateoBBCode += ` [tr] [td]${index + 1}.[/td] [td][url=https://bitcointalk.org/index.php?action=profile;u=${user.userUid}]${user.user}[/url][/td] [td]${user.postCount}[/td] [td]${user.meritCount}[/td] [td]${user.rateo}[/td] [td][url=https://bpip.org/Profile?p=${user.user}][img width=15]https://www.talkimg.com/images/2023/08/03/GXlZb.png[/img][/url][/td] [td][url=https://ninjastic.space/user/${user.user}][img width=15]https://talkimg.com/images/2023/08/03/GwdMz.png[/img][/url][/td] [/tr] `; }); rateoBBCode += `[/table]`; // Calcola gli utenti con il maggior impatto (Post + Merit) const userImpactMap = {}; allAuthors.forEach(author => { const userUid = author.author_uid; const postCount = author.count || 0; const meritUser = meritData.receiver.find(u => u.user_uid == userUid); const meritCount = meritUser ? meritUser.sum : 0; // Calcola il punteggio di impatto: (post * 0.5) + (merit * 1.5) const impactScore = (postCount * 0.5) + (meritCount * 1.5); userImpactMap[userUid] = { user: author.author, userUid, postCount, meritCount, impactScore: impactScore.toFixed(2) }; }); // Ordina per impatto e prendi i primi 10 const topImpactUsers = Object.values(userImpactMap) .filter(user => user.postCount > 0 || user.meritCount > 0) .sort((a, b) => parseFloat(b.impactScore) - parseFloat(a.impactScore)) .slice(0, 10); // Genera la tabella HTML per l'impatto let impactPreview = ` <div style="margin-top: 20px;"> <h4 style="text-align: center; margin-bottom: 10px;">Top 10 Utenti per Impatto (Post + Merit)</h4> <table style="width: 100%; border-collapse: collapse; font-size: 13px;"> <tr style="background: #f0f0f0;"> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Pos.</th> <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">User</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Post</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Merit Ricevuti</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Impatto</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">BPIP</th> <th style="padding: 8px; text-align: center; border: 1px solid #ddd;">Ninjastic</th> </tr> `; topImpactUsers.forEach((user, index) => { impactPreview += ` <tr style="border: 1px solid #ddd;"> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${index + 1}.</td> <td style="padding: 8px; border: 1px solid #ddd;"> <a href="https://bitcointalk.org/index.php?action=profile;u=${user.userUid}" target="_blank">${user.user}</a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${user.postCount}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${user.meritCount}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;">${user.impactScore}</td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://bpip.org/Profile?p=${user.user}" target="_blank"> <img src="https://www.talkimg.com/images/2023/08/03/GXlZb.png" width="15"> </a> </td> <td style="padding: 8px; border: 1px solid #ddd; text-align: center;"> <a href="https://ninjastic.space/user/${user.user}" target="_blank"> <img src="https://talkimg.com/images/2023/08/03/GwdMz.png" width="15"> </a> </td> </tr> `; }); impactPreview += `</table></div>`; // Genera il BBCode per l'impatto let impactBBCode = ` [center][b]Top 10 Utenti per Impatto (Post + Merit)[/b][/center] [table] [tr] [td][b]Pos.[/b][/td] [td][b]User[/b][/td] [td][b]Post[/b][/td] [td][b]Merit Ricevuti[/b][/td] [td][b]Impatto[/b][/td] [td][b]BPIP[/b][/td] [td][b]Ninjastic[/b][/td] [/tr] `; topImpactUsers.forEach((user, index) => { impactBBCode += ` [tr] [td]${index + 1}.[/td] [td][url=https://bitcointalk.org/index.php?action=profile;u=${user.userUid}]${user.user}[/url][/td] [td]${user.postCount}[/td] [td]${user.meritCount}[/td] [td]${user.impactScore}[/td] [td][url=https://bpip.org/Profile?p=${user.user}][img width=15]https://www.talkimg.com/images/2023/08/03/GXlZb.png[/img][/url][/td] [td][url=https://ninjastic.space/user/${user.user}][img width=15]https://talkimg.com/images/2023/08/03/GwdMz.png[/img][/url][/td] [/tr] `; }); impactBBCode += `[/table]`; // Genera una sintesi testuale dei dati let summaryText = ` <div style="margin-top: 20px; padding: 15px; background: #f9f9f9; border-radius: 5px; border: 1px solid #ddd;"> <h4 style="text-align: center; margin-bottom: 15px; color: #2e3b4e;">Sintesi degli Andamenti</h4> <p style="margin-bottom: 10px;"><strong>Periodo analizzato:</strong> ${currentMonthName} (vs ${previousMonthName})</p> <p style="margin-bottom: 10px;"><strong>Board:</strong> ${boardName} (ID: ${boardId}) ${childBoards ? "(incluse child boards)" : ""}</p> <p style="margin-bottom: 10px;"> <strong>Post totali:</strong> ${totalPostsCurrent} (${currentMonthName})<br> <strong>Merit totali inviati (solo nella board):</strong> ${totalMeritSent}<br> <strong>Merit totali ricevuti (solo nella board):</strong> ${totalMeritReceived}<br> <strong>Media Merit/Post:</strong> ${avgMeritPerPost} </p> ${childBoards ? `<p style="margin-bottom: 10px;"><strong>Distribuzione Post per Child Board:</strong></p> <ul style="margin-bottom: 15px; padding-left: 20px;"> ${Object.entries(childBoardDistribution).map(([title, posts]) => `<li><strong>${title}:</strong> ${posts} post</li>` ).join('')} </ul>` : ''} <p style="margin-bottom: 10px;"><strong>Utenti attivi:</strong> ${allAuthors.length} (di cui ${allAuthors.filter(a => (a.count || 0) >= 2).length} con almeno 2 post in ${currentMonthName})</p> <h5 style="margin: 15px 0 10px 0; color: #2e3b4e;">Top Poster</h5> <ul style="margin-bottom: 15px; padding-left: 20px;"> <li><strong>1°:</strong> ${allAuthors[0]?.author || "Nessuno"} (${allAuthors[0]?.count || 0} post in ${currentMonthName})</li> <li><strong>2°:</strong> ${allAuthors[1]?.author || "Nessuno"} (${allAuthors[1]?.count || 0} post)</li> <li><strong>3°:</strong> ${allAuthors[2]?.author || "Nessuno"} (${allAuthors[2]?.count || 0} post)</li> </ul> <h5 style="margin: 15px 0 10px 0; color: #2e3b4e;">Top Merit Receiver</h5> <ul style="margin-bottom: 15px; padding-left: 20px;"> <li><strong>1°:</strong> ${meritData.receiver[0]?.user || "Nessuno"} (${meritData.receiver[0]?.sum || 0} merit)</li> <li><strong>2°:</strong> ${meritData.receiver[1]?.user || "Nessuno"} (${meritData.receiver[1]?.sum || 0} merit)</li> <li><strong>3°:</strong> ${meritData.receiver[2]?.user || "Nessuno"} (${meritData.receiver[2]?.sum || 0} merit)</li> </ul> <h5 style="margin: 15px 0 10px 0; color: #2e3b4e;">Distribuzione Post</h5> <p style="margin-bottom: 10px;"> ${postDistribution["1 post"]} utenti con 1 post,<br> ${postDistribution["2-5 post"]} utenti con 2-5 post,<br> ${postDistribution["6-10 post"]} utenti con 6-10 post,<br> ${postDistribution[">50 post"]} utenti con più di 50 post. </p> <h5 style="margin: 15px 0 10px 0; color: #2e3b4e;">Utenti con Maggior Impatto</h5> <ul style="margin-bottom: 15px; padding-left: 20px;"> <li><strong>1°:</strong> ${topImpactUsers[0]?.user || "Nessuno"} (Impatto: ${topImpactUsers[0]?.impactScore || 0})</li> <li><strong>2°:</strong> ${topImpactUsers[1]?.user || "Nessuno"} (Impatto: ${topImpactUsers[1]?.impactScore || 0})</li> <li><strong>3°:</strong> ${topImpactUsers[2]?.user || "Nessuno"} (Impatto: ${topImpactUsers[2]?.impactScore || 0})</li> </ul> <h5 style="margin: 15px 0 10px 0; color: #2e3b4e;">Rateo Merit/Post</h5> <p style="margin-bottom: 10px;"> ${topRateoUsers[0]?.user || "Nessuno"} ha il rateo più alto: ${topRateoUsers[0]?.rateo || 0} merit per post. </p> </div> `; // Genera il BBCode per la sintesi let summaryBBCode = ` [center][b][size=12pt]Sintesi degli Andamenti[/size][/b][/center] [b]Periodo analizzato:[/b] ${currentMonthName} (vs ${previousMonthName}) [b]Board:[/b] ${boardName} (ID: ${boardId}) ${childBoards ? "(incluse child boards)" : ""} [b]Post totali:[/b] ${totalPostsCurrent} (${currentMonthName}) [b]Merit totali inviati (solo nella board):[/b] ${totalMeritSent} [b]Merit totali ricevuti (solo nella board):[/b] ${totalMeritReceived} [b]Media Merit/Post:[/b] ${avgMeritPerPost} ${childBoards ? `[center][b]Distribuzione Post per Child Board[/b][/center] [list] ${Object.entries(childBoardDistribution).map(([title, posts]) => `[*]${title}: ${posts} post` ).join('\n')} [/list]` : ''} [b]Utenti attivi:[/b] ${allAuthors.length} (di cui ${allAuthors.filter(a => (a.count || 0) >= 2).length} con almeno 2 post in ${currentMonthName}) [center][b]Top Poster[/b][/center] [list] [*]1°: ${allAuthors[0]?.author || "Nessuno"} (${allAuthors[0]?.count || 0} post in ${currentMonthName}) [*]2°: ${allAuthors[1]?.author || "Nessuno"} (${allAuthors[1]?.count || 0} post) [*]3°: ${allAuthors[2]?.author || "Nessuno"} (${allAuthors[2]?.count || 0} post) [/list] [center][b]Top Merit Receiver[/b][/center] [list] [*]1°: ${meritData.receiver[0]?.user || "Nessuno"} (${meritData.receiver[0]?.sum || 0} merit) [*]2°: ${meritData.receiver[1]?.user || "Nessuno"} (${meritData.receiver[1]?.sum || 0} merit) [*]3°: ${meritData.receiver[2]?.user || "Nessuno"} (${meritData.receiver[2]?.sum || 0} merit) [/list] [center][b]Distribuzione Post[/b][/center] ${postDistribution["1 post"]} utenti con 1 post ${postDistribution["2-5 post"]} utenti con 2-5 post ${postDistribution["6-10 post"]} utenti con 6-10 post ${postDistribution[">50 post"]} utenti con più di 50 post [center][b]Utenti con Maggior Impatto[/b][/center] [list] [*]1°: ${topImpactUsers[0]?.user || "Nessuno"} (Impatto: ${topImpactUsers[0]?.impactScore || 0}) [*]2°: ${topImpactUsers[1]?.user || "Nessuno"} (Impatto: ${topImpactUsers[1]?.impactScore || 0}) [*]3°: ${topImpactUsers[2]?.user || "Nessuno"} (Impatto: ${topImpactUsers[2]?.impactScore || 0}) [/list] [center][b]Rateo Merit/Post[/b][/center] ${topRateoUsers[0]?.user || "Nessuno"} ha il rateo più alto: ${topRateoUsers[0]?.rateo || 0} merit per post `; // Aggiungi i grafici con pulsanti di download chartsContainer.innerHTML = ` <div style="margin-top: 20px;"> <h4 style="text-align: center; margin-bottom: 10px;">Distribuzione Post per Utente</h4> <canvas id="postDistributionChart" style="max-height: 300px;"></canvas> <button id="downloadPostDistributionChart" style="display: block; margin: 10px auto; padding: 5px 10px; background: #2e3b4e; color: white; border: none; border-radius: 3px; cursor: pointer;"> Apri Grafico </button> </div> <div style="display: flex; gap: 20px; margin-top: 20px;"> <div style="flex: 1;"> <h4 style="text-align: center; margin-bottom: 10px;">Top 10 Merit Sender</h4> <canvas id="topSenderChart" style="max-height: 300px;"></canvas> <button id="downloadTopSenderChart" style="display: block; margin: 10px auto; padding: 5px 10px; background: #2e3b4e; color: white; border: none; border-radius: 3px; cursor: pointer;"> Apri Grafico </button> </div> <div style="flex: 1;"> <h4 style="text-align: center; margin-bottom: 10px;">Top 10 Merit Receiver</h4> <canvas id="topReceiverChart" style="max-height: 300px;"></canvas> <button id="downloadTopReceiverChart" style="display: block; margin: 10px auto; padding: 5px 10px; background: #2e3b4e; color: white; border: none; border-radius: 3px; cursor: pointer;"> Apri Grafico </button> </div> </div> <div style="margin-top: 20px;"> <h4 style="text-align: center; margin-bottom: 10px;">Top 10 Utenti per Rateo Merit/Post</h4> <canvas id="topRateoChart" style="max-height: 300px;"></canvas> <button id="downloadTopRateoChart" style="display: block; margin: 10px auto; padding: 5px 10px; background: #2e3b4e; color: white; border: none; border-radius: 3px; cursor: pointer;"> Apri Grafico </button> </div> <div style="margin-top: 20px;"> <h4 style="text-align: center; margin-bottom: 10px;">Top 10 Utenti per Impatto</h4> <canvas id="topImpactChart" style="max-height: 300px;"></canvas> <button id="downloadTopImpactChart" style="display: block; margin: 10px auto; padding: 5px 10px; background: #2e3b4e; color: white; border: none; border-radius: 3px; cursor: pointer;"> Apri Grafico </button> </div> `; // Crea i grafici setTimeout(() => { // Distribuzione post (torta) createPieChart( 'postDistributionChart', Object.keys(postDistribution), Object.values(postDistribution), `Distribuzione Post per Utente (${currentMonthName})` ); // Top Merit Sender (barre) createBarChart( 'topSenderChart', meritData.sender.slice(0, 10).map(u => u.user), meritData.sender.slice(0, 10).map(u => u.sum), 'Top 10 Merit Sender', 'Merit Inviati' ); // Top Merit Receiver (barre) createBarChart( 'topReceiverChart', meritData.receiver.slice(0, 10).map(u => u.user), meritData.receiver.slice(0, 10).map(u => u.sum), 'Top 10 Merit Receiver', 'Merit Ricevuti' ); // Top Rateo (barre) createBarChart( 'topRateoChart', topRateoUsers.map(u => u.user), topRateoUsers.map(u => u.rateo), 'Top 10 Utenti per Rateo Merit/Post', 'Rateo' ); // Top Impatto (barre) createBarChart( 'topImpactChart', topImpactUsers.map(u => u.user), topImpactUsers.map(u => parseFloat(u.impactScore)), 'Top 10 Utenti per Impatto', 'Impatto' ); // Aggiungi gli event listener ai pulsanti di download document.getElementById('downloadPostDistributionChart').addEventListener('click', () => { openChartImage('postDistributionChart', `Distribuzione_Post_per_Utente_${currentMonthName}`); }); document.getElementById('downloadTopSenderChart').addEventListener('click', () => { openChartImage('topSenderChart', 'Top_10_Merit_Sender'); }); document.getElementById('downloadTopReceiverChart').addEventListener('click', () => { openChartImage('topReceiverChart', 'Top_10_Merit_Receiver'); }); document.getElementById('downloadTopRateoChart').addEventListener('click', () => { openChartImage('topRateoChart', 'Top_10_Utenti_per_Rateo_Merit_Post'); }); document.getElementById('downloadTopImpactChart').addEventListener('click', () => { openChartImage('topImpactChart', 'Top_10_Utenti_per_Impatto'); }); }, 500); // Aggiungi la sintesi all'anteprima e al BBCode previewTable = summaryText + previewTable; bbcode = summaryBBCode + `\n\n` + bbcode; // Mostra anteprima e BBCode loadingMsg.remove(); previewDiv.style.display = 'block'; outputDiv.style.display = 'block'; document.querySelector('#preview-table').innerHTML = previewTable + postDistributionPreview + meritPreview + rateoPreview + impactPreview; document.querySelector('#bbcode-output').value = bbcode + `\n\n${postDistributionBBCode}\n\n${meritBBCode}\n\n${rateoBBCode}\n\n${impactBBCode}`; } catch (err) { console.error("Errore generale durante il recupero dei dati:", err); loadingMsg.innerHTML = '<p style="color: red; text-align: center;">Errore durante il caricamento dati. Vedi console per dettagli.</p>'; } } // Aggiunge pulsante Stat nella navbar function addStatButton() { const navbar = document.querySelector('table[style*="margin-left: 10px;"]'); if (!navbar) { setTimeout(addStatButton, 1000); return; } const lastCell = navbar.querySelector('td.maintab_last'); if (lastCell.querySelector('#stat-button')) return; const statButton = document.createElement('td'); statButton.id = 'stat-button'; statButton.className = 'maintab_back'; statButton.innerHTML = `<a href="javascript:void(0)" class="maintab_link" style="padding: 0 10px;">Stats</a>`; statButton.querySelector('a').onclick = createStatPage; navbar.querySelector('tr').insertBefore(statButton, lastCell); } window.addEventListener('load', () => setTimeout(addStatButton, 1000)); window.copyBBCode = copyBBCode; window.createStatPage = createStatPage; })();