Dopo che avevo creato il tool mensile il nostro grande fillippone mi ha chiesto di farne uno settimanale per la gestione delle campagne firme
Quindi ho creato adhoc un tool sperando sia utile, comunque modificabile all'occasione

Cosa fa questo tool?
Utilizzo i dati provenienti da ninjastic e bip
Ogni qualvolta che scrivete un post verrà conteggiato da questo tool in base alla board dove e stato scritto
È possibile settare più utenti
È possibile scegliere il giorno di inizio della settimana, esempio mercoledì ore 00:00 -> martedì ore 23:59:59
Nella stessa settimana verranno visualizzati i Merit ricevuti
Come installare il tool:
Versione mobileInstallare Firefox nightly (solo Android)
Installare greasemonkey Add-ons su Firefox
Cliccare su create new script, cancellate le poche righe che trovate scritte di default sul nuovo script
Incollate il codice dello script che trovate in questo thread
Versione desktop Potete usare sia Firefox che Chrome
Installare greasemonkey o tampermonkey oppure violentmonkey
Seguire i stessi passaggi della versione mobile
NB: non conosco browser che diano la possibilità di installare Add-ons su iPhone
In attesa di vostri feedback vi lascio il codice da inserire su tampermonkey o greasemonkey oppure violentmonkey
// ==UserScript==
// @name Bitcointalk Weekly Tracker + Merit
// @namespace https://bitcointalk.org
// @version 1.0
// @description Post settimanali e Merit ricevuti (intervallo personalizzabile), compatibile mobile, con amore per fillippone
// @author *Ace*
// @match https://bitcointalk.org/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const usernames = ['*Ace*', 'fillippone', 'lillominato89'];
let selectedUser = localStorage.getItem('btwk_user') || usernames[0];
let startDayIndex = parseInt(localStorage.getItem('btwk_dayIndex')) || 5; // Venerdì
let currentWeekOffset = 0;
const timezoneOffset = 0; // UTC
function getWeekRange(offset = 0) {
const now = new Date();
const local = new Date(now.getTime() + timezoneOffset * 60 * 60 * 1000);
const day = local.getUTCDay();
const daysSinceStart = (day + 7 - startDayIndex) % 7;
const start = new Date(local);
start.setUTCDate(local.getUTCDate() - daysSinceStart + offset * 7);
start.setUTCHours(0 - timezoneOffset, 0, 0, 0);
const end = new Date(start);
end.setUTCDate(start.getUTCDate() + 7);
end.setTime(end.getTime() + Math.floor(Math.random() * 9000) + 1000); // jitter
return {
from: start.toISOString().split('.')[0],
to: end.toISOString().split('.')[0],
label: `${start.toISOString().slice(0, 10)} → ${new Date(end - 1).toISOString().slice(0, 10)} (UTC${timezoneOffset >= 0 ? '+' : ''}${timezoneOffset})`
};
}
function fetchBoardStats() {
const { from, to, label } = getWeekRange(currentWeekOffset);
const url = `https://api.ninjastic.space/users/${selectedUser}/boards?from=${from}&to=${to}&_=${Date.now()}_${Math.random().toString(36).slice(2)}`;
fetch(url, { cache: 'no-store' })
.then(res => res.json())
.then(json => {
if (json.result !== 'success') {
renderStats(`❌ Errore nel recupero dati`);
return;
}
const boards = json.data.boards || [];
const totalWithBoard = json.data.total_results_with_board || 0;
const totalAll = json.data.total_results || 0;
const unclassified = totalAll - totalWithBoard;
let gambling = 0, local = 0;
const otherBoards = [];
boards.forEach(b => {
if ([228, 56].includes(b.key)) gambling += b.count;
else if ([28, 153].includes(b.key)) local += b.count;
else otherBoards.push({ name: b.name, count: b.count });
});
let html = `<b>👤 Account:</b> ${selectedUser}<br>`;
html += `<b>📆 Settimana:</b><br>${label}<br><br>`;
html += `🧮 <b>Totale:</b> ${totalAll}<br>`;
html += `🧩 <b>Non classificati:</b> ${unclassified}<br>`;
html += `🃏 <b>Gambling:</b> ${gambling}<br>`;
html += `🌍 <b>Local IT:</b> ${local}<br><br>`;
if (otherBoards.length > 0) {
html += `<b>📌 Altre board:</b><br>`;
otherBoards.forEach(b => {
html += `• ${b.name}: ${b.count}<br>`;
});
}
renderStats(html);
})
.catch(err => renderStats(`⚠️ Errore rete: ${err.message}`));
}
function fetchMerits() {
const { from, to } = getWeekRange(currentWeekOffset);
const url = `https://api.allorigins.win/get?url=${encodeURIComponent(`https://bpip.org/smerit.aspx?to=${selectedUser}&start=${from}&end=${to}`)}`;
fetch(url)
.then(res => res.json())
.then(data => {
const html = data.contents;
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const table = doc.querySelector('table');
if (!table) throw new Error("Nessuna tabella trovata");
const rows = Array.from(table.querySelectorAll('tbody tr'));
const fromMap = {};
let total = 0;
rows.forEach(row => {
const tds = row.querySelectorAll('td');
if (tds.length >= 4) {
const from = tds[1].innerText.replace('(Summary)', '').trim();
const amount = parseInt(tds[3].innerText.trim());
fromMap[from] = (fromMap[from] || 0) + amount;
total += amount;
}
});
let htmlOut = `<b>⭐ Merit ricevuti:</b><br>`;
if (total === 0) {
htmlOut += `Nessun Merit ricevuto in questa settimana.`;
} else {
Object.entries(fromMap)
.sort((a, b) => b[1] - a[1])
.forEach(([from, count]) => {
htmlOut += `• ${from}: ${count}<br>`;
});
}
renderMerits(htmlOut);
})
.catch(err => renderMerits(`❌ Errore caricamento Merit: ${err.message}`));
}
function renderStats(html) {
const div = document.getElementById('btwk_stats');
if (div) div.innerHTML = html;
}
function renderMerits(html) {
const div = document.getElementById('btwk_merits');
if (div) div.innerHTML = html;
}
function updateTimestamp() {
const ts = document.getElementById('btwk_timestamp');
if (ts) {
const now = new Date();
ts.textContent = `🔄 Ultimo aggiornamento: ${now.toLocaleString('it-IT', { timeZone: 'UTC' })} UTC`;
}
}
function renderBox() {
if (document.getElementById('btwk_box')) return;
const box = document.createElement('div');
box.id = 'btwk_box';
Object.assign(box.style, {
position: 'fixed', bottom: '10px', right: '10px',
background: '#222', color: '#fff', padding: '12px',
borderRadius: '12px', fontSize: '13px', maxWidth: '350px',
zIndex: '9999', boxShadow: '0 0 8px rgba(0,0,0,0.6)',
fontFamily: 'Arial, sans-serif'
});
const header = document.createElement('div');
header.style.display = 'flex';
header.style.justifyContent = 'space-between';
header.style.alignItems = 'center';
const title = document.createElement('b');
title.textContent = '📊 Tracker Settimanale';
const toggleBtn = document.createElement('span');
toggleBtn.textContent = '➖';
toggleBtn.style.cursor = 'pointer';
toggleBtn.onclick = () => {
const content = document.getElementById('btwk_content');
content.style.display = content.style.display === 'none' ? 'block' : 'none';
toggleBtn.textContent = toggleBtn.textContent === '➖' ? '➕' : '➖';
};
header.appendChild(title);
header.appendChild(toggleBtn);
box.appendChild(header);
const content = document.createElement('div');
content.id = 'btwk_content';
content.style.marginTop = '10px';
const sel = document.createElement('select');
sel.style.width = '100%';
usernames.forEach(name => {
const opt = document.createElement('option');
opt.value = name;
opt.textContent = name;
if (name === selectedUser) opt.selected = true;
sel.appendChild(opt);
});
sel.onchange = () => {
selectedUser = sel.value;
localStorage.setItem('btwk_user', selectedUser);
update();
};
content.appendChild(sel);
const daySel = document.createElement('select');
daySel.style.width = '100%';
['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'].forEach((d, i) => {
const opt = document.createElement('option');
opt.value = i;
opt.textContent = `Inizia da ${d}`;
if (i === startDayIndex) opt.selected = true;
daySel.appendChild(opt);
});
daySel.onchange = () => {
startDayIndex = parseInt(daySel.value);
localStorage.setItem('btwk_dayIndex', startDayIndex);
update();
};
content.appendChild(daySel);
const nav = document.createElement('div');
nav.style.margin = '5px 0';
nav.innerHTML = `
<button id="prevW">⬅️</button>
<button id="thisW">📅</button>
<button id="nextW">➡️</button>
`;
content.appendChild(nav);
const stats = document.createElement('div');
stats.id = 'btwk_stats';
stats.style.marginTop = '8px';
content.appendChild(stats);
const merits = document.createElement('div');
merits.id = 'btwk_merits';
merits.style.marginTop = '12px';
merits.style.borderTop = '1px solid #666';
merits.style.paddingTop = '8px';
content.appendChild(merits);
const timestamp = document.createElement('div');
timestamp.id = 'btwk_timestamp';
timestamp.style.marginTop = '10px';
timestamp.style.fontSize = '11px';
timestamp.style.color = '#ccc';
content.appendChild(timestamp);
box.appendChild(content);
document.body.appendChild(box);
document.getElementById('prevW').onclick = () => { currentWeekOffset--; update(); };
document.getElementById('nextW').onclick = () => { currentWeekOffset++; update(); };
document.getElementById('thisW').onclick = () => { currentWeekOffset = 0; update(); };
}
function update() {
fetchBoardStats();
fetchMerits();
updateTimestamp();
}
renderBox();
update();
setInterval(update, 5 * 60 * 1000);
})();
Update: ver.1.1
// ==UserScript==
// @name Bitcointalk Weekly Tracker + Weekly Merit
// @namespace https://bitcointalk.org
// @version 1.1
// @description Conteggio post settimanali + Merit ricevuti (settimana personalizzabile), mobile compatibile, creata con amore per fillippone
// @author *Ace*
// @match https://bitcointalk.org/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const usernames = ['fillippone', '*Ace*']; //inserisci qui altri username per avere piu account da seguire
let selectedUser = localStorage.getItem('btwk_user') || usernames[0];
let startDayIndex = parseInt(localStorage.getItem('btwk_dayIndex')) || 5; // default venerdì
let timezoneOffset = parseInt(localStorage.getItem('btwk_tzOffset')) || 0; // offset in ore rispetto a UTC
let currentWeekOffset = 0;
function getWeekRange(offset = 0) {
const now = new Date();
const local = new Date(now.getTime() + timezoneOffset * 60 * 60 * 1000);
const day = local.getUTCDay();
const daysSinceStart = (day + 7 - startDayIndex) % 7;
const start = new Date(local);
start.setUTCDate(local.getUTCDate() - daysSinceStart + offset * 7);
start.setUTCHours(0 - timezoneOffset, 0, 0, 0);
const end = new Date(start);
end.setUTCDate(start.getUTCDate() + 7);
end.setTime(end.getTime() + Math.floor(Math.random() * 9000) + 1000); // jitter
return {
from: start.toISOString().split('.')[0],
to: end.toISOString().split('.')[0],
label: `${start.toISOString().slice(0, 10)} → ${new Date(end - 1).toISOString().slice(0, 10)} (UTC${timezoneOffset >= 0 ? '+' : ''}${timezoneOffset})`
};
}
function fetchBoardStats() {
const { from, to, label } = getWeekRange(currentWeekOffset);
const url = `https://api.ninjastic.space/users/${selectedUser}/boards?from=${from}&to=${to}&_=${Date.now()}_${Math.random().toString(36).slice(2)}`;
fetch(url, {
cache: 'no-store',
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
}
})
.then(res => res.json())
.then(json => {
if (json.result !== 'success') {
renderStats(`❌ Errore nel recupero dati`);
return;
}
const boards = json.data.boards || [];
const totalWithBoard = json.data.total_results_with_board || 0;
const totalAll = json.data.total_results || 0;
const unclassified = totalAll - totalWithBoard;
let gambling = 0;
let local = 0;
const otherBoards = [];
boards.forEach(b => {
if ([228, 56].includes(b.key)) gambling += b.count;
else if ([28, 153].includes(b.key)) local += b.count;
else otherBoards.push({ name: b.name, count: b.count });
});
let html = `<b>👤 Account:</b> ${selectedUser}<br>`;
html += `<b>📆 Settimana:</b><br>${label}<br><br>`;
html += `🧮 <b>Totale:</b> ${totalAll}<br>`;
html += `🧩 <b>Non classificati:</b> ${unclassified}<br>`;
html += `🃏 <b>Gambling:</b> ${gambling}<br>`;
html += `🌍 <b>Local IT:</b> ${local}<br><br>`;
if (otherBoards.length > 0) {
html += `<b>📌 Altre board:</b><br>`;
otherBoards.forEach(b => {
html += `• ${b.name}: ${b.count}<br>`;
});
}
renderStats(html);
})
.catch(err => {
renderStats(`⚠️ Errore di rete: ${err.message}`);
});
}
function fetchMerits() {
const { from, to } = getWeekRange(currentWeekOffset);
const url = `https://api.allorigins.win/get?url=${encodeURIComponent(`https://bpip.org/smerit.aspx?to=${selectedUser}&start=${from}&end=${to}`)}`;
fetch(url)
.then(res => res.json())
.then(data => {
const html = data.contents;
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const table = doc.querySelector('table');
if (!table) throw new Error("Nessuna tabella trovata");
const rows = Array.from(table.querySelectorAll('tbody tr'));
const fromMap = {};
let total = 0;
rows.forEach(row => {
const tds = row.querySelectorAll('td');
if (tds.length >= 4) {
const from = tds[1].innerText.replace('(Summary)', '').trim();
const amount = parseInt(tds[3].innerText.trim());
fromMap[from] = (fromMap[from] || 0) + amount;
total += amount;
}
});
let htmlOut = `<b>⭐ Merit ricevuti:</b><br>`;
if (total === 0) {
htmlOut += `Nessun Merit ricevuto in questa settimana.`;
} else {
Object.entries(fromMap)
.sort((a, b) => b[1] - a[1])
.forEach(([from, count]) => {
htmlOut += `• ${from}: ${count}<br>`;
});
}
renderMerits(htmlOut);
})
.catch(err => {
renderMerits(`❌ Errore caricamento Merit: ${err.message}`);
});
}
function renderStats(html) {
const div = document.getElementById('btwk_stats');
if (div) div.innerHTML = html;
}
function renderMerits(html) {
const div = document.getElementById('btwk_merits');
if (div) div.innerHTML = html;
}
function renderBox() {
if (document.getElementById('btwk_box')) return;
const box = document.createElement('div');
box.id = 'btwk_box';
box.style.position = 'fixed';
box.style.bottom = '10px';
box.style.right = '10px';
box.style.background = '#222';
box.style.color = '#fff';
box.style.padding = '12px';
box.style.borderRadius = '12px';
box.style.fontSize = '13px';
box.style.maxWidth = '350px';
box.style.zIndex = '9999';
box.style.boxShadow = '0 0 8px rgba(0,0,0,0.6)';
box.style.fontFamily = 'Arial, sans-serif';
const header = document.createElement('div');
header.style.display = 'flex';
header.style.justifyContent = 'space-between';
header.style.alignItems = 'center';
const title = document.createElement('b');
title.textContent = '📊 Tracker Settimanale';
const toggleBtn = document.createElement('span');
toggleBtn.textContent = '➖';
toggleBtn.style.cursor = 'pointer';
toggleBtn.onclick = () => {
const content = document.getElementById('btwk_content');
content.style.display = content.style.display === 'none' ? 'block' : 'none';
toggleBtn.textContent = toggleBtn.textContent === '➖' ? '➕' : '➖';
};
header.appendChild(title);
header.appendChild(toggleBtn);
box.appendChild(header);
const content = document.createElement('div');
content.id = 'btwk_content';
content.style.marginTop = '10px';
// Select user
const sel = document.createElement('select');
sel.style.width = '100%';
usernames.forEach(name => {
const opt = document.createElement('option');
opt.value = name;
opt.textContent = name;
if (name === selectedUser) opt.selected = true;
sel.appendChild(opt);
});
sel.onchange = () => {
selectedUser = sel.value;
localStorage.setItem('btwk_user', selectedUser);
update();
};
content.appendChild(sel);
// Day select
const daySel = document.createElement('select');
daySel.style.width = '100%';
['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'].forEach((d, i) => {
const opt = document.createElement('option');
opt.value = i;
opt.textContent = `Inizia da ${d}`;
if (i === startDayIndex) opt.selected = true;
daySel.appendChild(opt);
});
daySel.onchange = () => {
startDayIndex = parseInt(daySel.value);
localStorage.setItem('btwk_dayIndex', startDayIndex);
update();
};
content.appendChild(daySel);
// Timezone select
const tzSel = document.createElement('select');
tzSel.style.width = '100%';
for (let i = -12; i <= 14; i++) {
const opt = document.createElement('option');
opt.value = i;
opt.textContent = `Fuso orario UTC${i >= 0 ? '+' + i : i}`;
if (i === timezoneOffset) opt.selected = true;
tzSel.appendChild(opt);
}
tzSel.onchange = () => {
timezoneOffset = parseInt(tzSel.value);
localStorage.setItem('btwk_tzOffset', timezoneOffset);
update();
};
content.appendChild(tzSel);
// Navigation
const nav = document.createElement('div');
nav.style.margin = '5px 0';
nav.innerHTML = `
<button id="prevW">⬅️</button>
<button id="thisW">📅</button>
<button id="nextW">➡️</button>
`;
content.appendChild(nav);
document.body.appendChild(box);
box.appendChild(content);
const stats = document.createElement('div');
stats.id = 'btwk_stats';
stats.style.marginTop = '8px';
content.appendChild(stats);
const merits = document.createElement('div');
merits.id = 'btwk_merits';
merits.style.marginTop = '12px';
merits.style.borderTop = '1px solid #666';
merits.style.paddingTop = '8px';
content.appendChild(merits);
document.getElementById('prevW').onclick = () => { currentWeekOffset--; update(); };
document.getElementById('nextW').onclick = () => { currentWeekOffset++; update(); };
document.getElementById('thisW').onclick = () => { currentWeekOffset = 0; update(); };
}
function update() {
fetchBoardStats();
fetchMerits();
}
renderBox();
update();
})();