Ciao ragazzi, da poco mi sono cimentato a scrivere userscript da utilizzare con Firefox o Chrome. Avevo visto che fillippone ogni mese costava un resoconto abbastanza bello e completo, e mi è venuta l'idea di creare uno script minimalista che restituisce alcuni dati importanti, come post scritti nel mese corrente o a ritroso, dove li avete scritti e quanti Merit avete ricevuto
Vi allego giusto 2 screenshot per avere una idea di quello che ho fatto, non è professionale ma è abbastanza carino


Inserisco anche il codice se qualcuno volesse provarlo e darmi feedback o consigli su cosa inserire, poi eventualmente valuterò di pubblicarlo anche sulla sezione internazionale
// ==UserScript==
// @name Bitcointalk Monthly Stats + Merit Tracker
// @namespace https://bitcointalk.org
// @version 1.0
// @description Monthly post and merit statistics for Bitcointalk profile (mobile friendly)
// @author *Ace*
// @match https://bitcointalk.org/index.php?action=profile*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const uid = 'inserisci_il_tuo_UID';
const username = 'inserisci_il_tuo_username';
const boxId = 'monthlyStatsBox';
const now = new Date();
let currentMonthOffset = 0;
function pad(n) {
return n.toString().padStart(2, '0');
}
function addOneDay(dateString) {
const d = new Date(dateString);
d.setDate(d.getDate() + 1);
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
function getDateRange(monthOffset = 0) {
const date = new Date(now.getFullYear(), now.getMonth() + monthOffset, 1);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const firstDay = `${year}-${pad(month)}-01`;
const lastDay = new Date(year, month, 0).getDate();
const lastDate = `${year}-${pad(month)}-${pad(lastDay)}`;
const label = `${date.toLocaleString('en', { month: 'long' })} ${year}`;
return { from: firstDay, to: lastDate, label, y: year, m: month };
}
async function fetchBoardData(from, to) {
const url = `https://api.ninjastic.space/users/${username}/boards?from=${from}T00:00:00&to=${addOneDay(to)}T00:00:00`;
try {
const res = await fetch(url);
const json = await res.json();
if (json.result !== 'success') return null;
return json.data;
} catch {
return null;
}
}
async function fetchMeritData(y, m) {
const from = `${y}-${pad(m)}-01`;
const toDate = new Date(y, m, 0);
const to = `${y}-${pad(m)}-${pad(toDate.getDate())}`;
const url = `https://bpip.org/smerit.aspx?&to=${username}&start=${from}&end=${to}`;
try {
const res = await fetch(url);
const htmlText = await res.text();
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, 'text/html');
const rows = Array.from(doc.querySelectorAll('table tbody tr'));
if (!rows.length) return null;
const fromData = {};
let total = 0;
rows.forEach(tr => {
const tds = tr.querySelectorAll('td');
if (tds.length >= 4) {
const name = tds[1].innerText.trim().replace(/\s*\(Summary\)$/i, '');
const count = parseInt(tds[3].innerText.trim()) || 0;
total += count;
fromData[name] = (fromData[name] || 0) + count;
}
});
return { total, fromData };
} catch (e) {
console.error(e);
return null;
}
}
function createBox() {
let box = document.getElementById(boxId);
if (box) return box;
box = document.createElement('div');
box.id = boxId;
box.style.position = 'fixed';
box.style.left = '5px';
box.style.top = '460px';
box.style.background = '#222';
box.style.color = '#fff';
box.style.padding = '12px';
box.style.borderRadius = '12px';
box.style.fontSize = '13px';
box.style.maxWidth = '340px';
box.style.zIndex = '9999';
box.style.boxShadow = '0 0 8px rgba(0,0,0,0.6)';
box.style.fontFamily = 'Arial, sans-serif';
const content = document.createElement('div');
content.id = `${boxId}-content`;
content.innerHTML = 'Loading...';
box.appendChild(content);
const nav = document.createElement('div');
nav.style.marginTop = '8px';
nav.style.display = 'flex';
nav.style.justifyContent = 'space-between';
const prevBtn = document.createElement('button');
prevBtn.textContent = '← Previous Month';
prevBtn.style.flex = '1';
prevBtn.style.marginRight = '4px';
prevBtn.style.padding = '6px';
prevBtn.style.border = 'none';
prevBtn.style.borderRadius = '6px';
prevBtn.style.background = '#444';
prevBtn.style.color = '#fff';
prevBtn.style.cursor = 'pointer';
prevBtn.onclick = () => {
currentMonthOffset--;
renderStats();
};
const nextBtn = document.createElement('button');
nextBtn.textContent = 'Next Month →';
nextBtn.style.flex = '1';
nextBtn.style.marginLeft = '4px';
nextBtn.style.padding = '6px';
nextBtn.style.border = 'none';
nextBtn.style.borderRadius = '6px';
nextBtn.style.background = '#444';
nextBtn.style.color = '#fff';
nextBtn.style.cursor = 'pointer';
nextBtn.onclick = () => {
if (currentMonthOffset < 0) {
currentMonthOffset++;
renderStats();
}
};
nav.appendChild(prevBtn);
nav.appendChild(nextBtn);
box.appendChild(nav);
document.body.appendChild(box);
return box;
}
async function renderStats() {
const box = createBox();
const content = document.getElementById(`${boxId}-content`);
content.innerHTML = '📊 Loading monthly data...';
const { from, to, label, y, m } = getDateRange(currentMonthOffset);
const boardData = await fetchBoardData(from, to);
const meritData = await fetchMeritData(y, m);
if (!boardData) {
content.innerHTML = '❌ Error loading posts.';
return;
}
let html = `🧮 <b>Statistics for ${label}</b><br><br>`;
html += `📝 <b>Posts written:</b> ${boardData.total_results_with_board}<br>`;
boardData.boards.forEach(b => {
html += `• ${b.name}: ${b.count}<br>`;
});
if (!meritData) {
html += `<br>⭐ <b>Merits received:</b> Loading error.`;
} else {
html += `<br>⭐ <b>Merits received:</b> ${meritData.total}<br>`;
const sorted = Object.entries(meritData.fromData).sort((a, b) => b[1] - a[1]);
sorted.forEach(([name, count]) => {
html += `• ${name}: ${count}<br>`;
});
}
content.innerHTML = html;
}
if (location.href.includes(`u=${uid}`)) {
renderStats();
}
})();
Basta inserire UserID e UserNAME e lo script si avvia, viene visualizzato soltanto nella nostra pagina profilo
https://bitcointalk.org/index.php?action=profile;u=xxxxxA voi le opinioni
Grazie in anticipo ☺️