Bitcointalk Board Stats

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;
})();