Statistiche board Bitcointalk con spinner "Dot Wave", sintesi testuale, distribuzione post, e utenti con maggior impatto
当前为
// ==UserScript==
// @name Bitcointalk Board Stats
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description Statistiche board Bitcointalk con 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 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)) {
childBoards.push(board.value);
}
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 ID o nome:</label>
<input type="text" id="board-id" value="28" 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 (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>
<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;
}
// 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()];
}
// Genera le statistiche con intestazione dinamica e Merit stats
async function generateStats() {
const boardId = document.querySelector('#board-id').value.trim();
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 (!boardId || !currentStart || !currentEnd || !previousStart || !previousEnd) {
alert('Compila tutti i campi!');
return;
}
// Nomi dei mesi per l'intestazione
const currentMonthName = getMonthName(currentStart);
const previousMonthName = getMonthName(previousStart);
const previewDiv = document.querySelector('#stats-preview');
const outputDiv = document.querySelector('#stats-output');
previewDiv.style.display = 'none';
outputDiv.style.display = 'none';
// 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));
// 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]`;
// Recupera i dati Merit
const boardIds = childBoards ? [boardId, ...await fetchChildBoards(boardId)] : [boardId];
const meritData = await fetchMeritBatchData(
boardIds,
currentStart.split('T')[0],
currentEnd.split('T')[0]
);
// 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> ${boardId} ${childBoards ? "(incluse child boards)" : ""}</p>
<p style="margin-bottom: 10px;"><strong>Utenti attivi:</strong> ${allAuthors.length} (di cui ${allAuthors.filter(a => a.count > 0).length} con almeno 1 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] ${boardId} ${childBoards ? "(incluse child boards)" : ""}
[b]Utenti attivi:[/b] ${allAuthors.length} (di cui ${allAuthors.filter(a => a.count > 0).length} con almeno 1 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 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;
})();