Next scheduled rescrape ... never
25/07/2025, 10:27:42 UTC CHANGED TITLE [Tool] Bitcointalk Weekly post and merit tracker v.1.3.2
Version 1
Last scraped
Edited on 25/07/2025, 10:27:42 UTC
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

[REMOVED IMAGE]



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
È possibile settare il Time zone UTC
È possibile settare i target (attualmente solo minimo in gambling e massimo in local board Italia) e salvare in locale i dati inseriti (tipo max local 10 e Min gambling 10)
Nella stessa settimana verranno visualizzati i Merit ricevuti


Come installare il tool: versione attuale 1.3.2


Versione mobile
Installare Firefox nightly (solo Android)
Installare greasemonkey Add-ons su Firefox
Andate su greasy fork a questo indirizzo https://greasyfork.org/it/scripts/543499-bitcointalk-weekly-post-merit-tracker

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

Code: (1.0)
// ==UserScript==
// @name          Bitcointalk Weekly Tracker + Weekly Merit
// @namespace    https://bitcointalk.org
// @version      1.0
// @description   Conteggio postPost settimanali +e Merit ricevuti (settimanaintervallo personalizzabile), mobile compatibile mobile, creata con amore per fillippone
// @author        *Ace*
// @match        https://bitcointalk.org/*
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const usernames = ['*Ace*', 'fillippone', '*Ace*lillominato89']; //inserisci qui altri username per avere piu account da seguire, togli gli username tra gli apici per eliminare l'account
  let selectedUser = localStorage.getItem('btwk_user') || usernames[0];
  let startDayIndex = parseInt(localStorage.getItem('btwk_dayIndex')) || 5; // default venerdì Venerdì
  let currentWeekOffset = 0;
  const timezoneOffset = 0; // UTC
  function getWeekRange(offset = 0) {
   const now = new Datefunction getWeekRange(offset = 0); {
    const todaynow = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
    const daylocal = todaynew Date(now.getUTCDaygetTime() + timezoneOffset * 60 * 60 * 1000);
    const daysSinceStartday = local.getUTCDay(day + 7 - startDayIndex) % 7;
    const startdaysSinceStart = new Date(todayday + 7 - startDayIndex) % 7;
    start.setUTCDate(today.getUTCDate() - daysSinceStart + offset * 7);
    const start.setUTCHours = new Date(0, 0, 0, 0local);
   const end = new Date( start.setUTCDate(local.getUTCDate() - daysSinceStart + offset * 7);
   end.setUTCDate( start.getUTCDatesetUTCHours(0 - timezoneOffset, 0, 0, 0) + 7);
    end.setUTCHours(0, 0, 0, 0);
   return { const end = new Date(start);
     from: startend.toISOStringsetUTCDate()start.splitgetUTCDate('.')[0], + 7);
     to: end.toISOStringsetTime(end.getTime() + Math.splitfloor('Math.'random()[0], * 9000) + 1000); // jitter
      label: `${start.toISOString().slice(0, 10)} → ${new Date(end - 1).toISOString().slice(0, 10)}`
   } ;return {
  }     from: start.toISOString().split('.')[0],
      to: end.toISOString().split('.')[0],
  function fetchBoardStats     label: `${start.toISOString().slice(0, 10)} → ${new Date(end - 1).toISOString().slice(0, 10)} (UTC${timezoneOffset >= 0 ? '+' : ''}${timezoneOffset})`
   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)}`;

   fetchfunction fetchBoardStats(url,) {
     cache: 'no-store'const { from, to, label } = getWeekRange(currentWeekOffset);
     headersconst url = `https://api.ninjastic.space/users/${selectedUser}/boards?from=${from}&to=${to}&_=${Date.now()}_${Math.random().toString(36).slice(2)}`;
        'Cache-Control': 'no-cache, no-store, must-revalidate',
       'Pragma'fetch(url, { cache: 'no-cachestore', })
       'Expires': '0',.then(res => res.json())
     } .then(json => {
   }     if (json.result !== 'success') {
     .then(res =&gtnbsp; res.json   renderStats(`❌ Errore nel recupero dati`));
     .then(json =&gtnbsp; {   return;
       if (json.result !== 'success') { }
          renderStats(`❌ Errore nel recupero dati`);
         returnconst boards = json.data.boards || [];
       } const totalWithBoard = json.data.total_results_with_board || 0;
        const totalAll = json.data.total_results || 0;
        const boardsunclassified = json.data.boards || []totalAll - totalWithBoard;
        const totalWithBoard = json.data.total_results_with_board || 0;
       const totalAll let gambling = json.data.total_results || 0, local = 0;
        const unclassifiedotherBoards = totalAll - totalWithBoard[];

       let gambling boards.forEach(b = 0>; {
       let local    if ([228, 56].includes(b.key)) gambling += 0b.count;
       const otherBoards =    else if ([28, 153].includes(b.key)) local += b.count;
          else otherBoards.push({ name: b.name, count: b.count });
       boards.forEach(b =&gtnbsp; {});
          if ([228, 56].includes(b.key)) gambling += b.count;
         else if ([28, 153].includes(b.key)) local +let html = `<b.count>;👤 Account:</b> ${selectedUser}<br>`;
         else otherBoards.push({ name: html += `<b.name, count>📆 Settimana:</b.count ><br>${label})<;br><br>`;
        html += `🧮 <b>Totale:</b> ${totalAll})<;br>`;
        html += `🧩 <b>Non classificati:</b> ${unclassified}<br>`;
       let  html += `🃏 <b>👤 AccountGambling:</b> ${selectedUsergambling}<br>`;
        html += `🌍 <b>📆 SettimanaLocal IT:</b> <br>${labellocal}<br><br>`;
        html += `🧮 <b>Totale:</b> ${totalAll}<br>`;
       html += `🧩 &ltnbsp;bif (otherBoards.length >Non classificati:</b> $ 0) {unclassified}<br>`;
          html += `🃏 <b>Gambling📌 Altre board:</b> ${gambling}<br>`;
       html += `🌍 &ltnbsp; b&gtnbsp;Local IT:</otherBoards.forEach(b => ${local}<br><br>`;
            html += `• ${b.name}: ${b.count}<br>`;
       if (otherBoards.length &gtnbsp; 0 }) {;
         html += `<b>📌 Altre board:</b><br>`;}
          otherBoards.forEach(b => {
           renderStats(html += `• ${b.name}: ${b.count}<);br>`;
         });
      .catch(err => renderStats(`⚠️ Errore rete: ${err.message}`));
  }
        renderStats(html);
     }function fetchMerits() {
     .catch(errconst { from, to } => getWeekRange(currentWeekOffset); {
       renderStats(const url = `⚠️ Errore di retehttps://api.allorigins.win/get?url=${errencodeURIComponent(`https://bpip.messageorg/smerit.aspx?to=${selectedUser}&start=${from}&end=${to}`)}`;
      });
  }   fetch(url)
      .then(res => res.json())
  function fetchMerits     .then()data => {
        const { from, to }html = getWeekRange(currentWeekOffset)data.contents;
   const url = `https://api.allorigins.win/get?url=${encodeURIComponent(`https://bpip.org/smerit.aspx?to=${selectedUser}&ampnbsp; start=${from}&ampnbsp;end  const parser =${to}` new DOMParser()}`;
        const doc = parser.parseFromString(html, 'text/html');
   fetch     const table = doc.querySelector(url'table');
     .then(res =&gtnbsp; res.json if (!table) throw new Error("Nessuna tabella trovata");
      .then(data => {
        const htmlrows = dataArray.contentsfrom(table.querySelectorAll('tbody tr'));
        const parserfromMap = new DOMParser(){};
       const doc let total = parser.parseFromString(html, 'text/html')0;
        const table = doc.querySelector('table');
       if  rows.forEach(!table) throw new Error("Nessuna tabella trovata")row =>; {
          const tds = row.querySelectorAll('td');
       const rows = Array.from   if (tabletds.querySelectorAll('tbody tr'))length >;= 4) {
            const fromMapfrom = {}tds[1].innerText.replace('(Summary)', '').trim();
       let total     const amount = 0parseInt(tds[3].innerText.trim());
            fromMap[from] = (fromMap[from] || 0) + amount;
       rows.forEach(row =&gtnbsp; {   total += amount;
         const tds = row.querySelectorAll('td') ;}
         if (tds.length >= 4}) {;
            const from = tds[1].innerText.replace('(Summary)', '').trim();
        let htmlOut = `&nbsplt; const amount = parseInt(tds[3].innerText.trim())b>;⭐ Merit ricevuti:</b><br>`;
           fromMap[from] =if (fromMap[from] ||total === 0) + amount;{
           totalhtmlOut += amount`Nessun Merit ricevuto in questa settimana.`;
        } else {
       }) ;  Object.entries(fromMap)
            .sort((a, b) => b[1] - a[1])
       let htmlOut = `&ltnbsp; b&gtnbsp; ⭐ Merit ricevuti:&ltnbsp;/b.forEach(([from, count]) =><br>`; {
       if (total        htmlOut +=== 0) `• ${from}: ${count}<br>`;
         htmlOut += `Nessun Merit ricevuto in questa settimana.` ;  });
        } else {
          Object.entries(fromMap).forEach(([from, count]) => {
           renderMerits(htmlOut += `• ${from}: ${count}<);br>`;
         });
      .catch(err => renderMerits(`❌ Errore caricamento Merit: ${err.message}`));
       renderMerits(htmlOut);}
      })
     .catchfunction renderStats(err =>html) {
       renderMerits(`❌ Errore caricamento Merit: ${errconst div = document.message}`getElementById('btwk_stats');
     }if (div) div.innerHTML = html;
  }

  function renderStatsrenderMerits(html) {
    const div = document.getElementById('btwk_statsbtwk_merits');
    if (div) div.innerHTML = html;
  }

  function renderMeritsupdateTimestamp(html) {
    const divts = document.getElementById('btwk_meritsbtwk_timestamp');
    if (divts) div.innerHTML = html;{
  }     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.createElementfunction renderBox('div'); {
   box if (document.id = getElementById('btwk_box')) return;
    box.style.position = 'fixed';
    const box.style.bottom = document.createElement('10pxdiv');
    box.style.rightid = '10pxbtwk_box';
    Object.assign(box.style.background = '#222';, {
   box.style.color =   position: '#ffffixed';, bottom: '10px', right: '10px',
   box.style.   background: '#222', color: '#fff', padding =: '12px';,
   box.style.   borderRadius =: '12px';, fontSize: '13px', maxWidth: '350px',
   box.style.fontSize =   zIndex: '13px9999';, boxShadow: '0 0 8px rgba(0,0,0,0.6)',
   box.style.maxWidth =   fontFamily: '350pxArial, sans-serif';
   box.style.zIndex = '9999' ;});
    box.style.boxShadow = '0 0 8px rgba(0,0,0,0.6)';
   box.style.fontFamily const header = document.createElement('Arial, sans-serifdiv');
    header.style.display = 'flex';
   const  header = document.createElement(style.justifyContent = 'divspace-between');
    header.style.displayalignItems = 'flexcenter';
    header.style.justifyContent = 'space-between';
   header.style.alignItems const title = document.createElement('centerb');
    title.textContent = '📊 Tracker Settimanale';
    const title = document.createElement('b');
   title.textContent const toggleBtn = document.createElement('📊 Tracker Settimanalespan');
    toggleBtn.textContent = '➖';
   const  toggleBtn = document.createElement(style.cursor = 'spanpointer');
    toggleBtn.textContentonclick = '➖'() =>; {
   toggleBtn.style.cursor   const content = document.getElementById('pointerbtwk_content');
   toggleBtn   content.onclickstyle.display = ()content.style.display =>== 'none' ? 'block' : 'none'; {
     const content toggleBtn.textContent = documenttoggleBtn.getElementById(textContent === 'btwk_content') ? '➕' : '➖';
     content.style.display = content.style.display === 'none' ? 'block' : 'none'};
      toggleBtn.textContent = toggleBtn.textContent === '➖' ? '➕' : '➖';
   } ;header.appendChild(title);
    header.appendChild(toggleBtn);
   header box.appendChild(titleheader);
    header.appendChild(toggleBtn);
   box const content = document.appendChildcreateElement(header'div');
    content.id = 'btwk_content';
   const  content = document.createElement(style.marginTop = 'div10px');
    content.id = 'btwk_content';
   content.style.marginTop const sel = document.createElement('10pxselect');
    sel.style.width = '100%';
   // Select user usernames.forEach(name => {
      const selopt = document.createElement('selectoption');
   sel   opt.style.widthvalue = '100%'name;
   usernames   opt.forEach(nametextContent => name; {
     const opt = document.createElement if ('option'name === selectedUser) opt.selected = true;
     opt sel.value = nameappendChild(opt);
     opt.textContent = name});
     if (namesel.onchange === selectedUser () opt.selected = true>; {
      selectedUser = sel.appendChild(opt)value;
   }   localStorage.setItem('btwk_user', selectedUser);
   sel.onchange =    update() =>; {
     selectedUser = sel.value};
     localStoragecontent.setItemappendChild('btwk_user', selectedUsersel);
      update();
   } ;const daySel = document.createElement('select');
   content daySel.appendChild(sel)style.width = '100%';
    ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'].forEach((d, i) => {
   // Day select   const opt = document.createElement('option');
   const daySel = document   opt.createElement('select')value = i;
   daySel   opt.style.widthtextContent = '100%'`Inizia da ${d}`;
   ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'].forEach   if ((d, i === startDayIndex) opt.selected => true; {
     const opt = document daySel.createElementappendChild('option'opt);
     opt.value = i});
     optdaySel.textContentonchange = `Inizia da ${d}`() =>; {
     if (i ===  startDayIndex) opt.selected = trueparseInt(daySel.value);
     daySel localStorage.appendChildsetItem(opt'btwk_dayIndex', startDayIndex);
   }   update();
   daySel.onchange = () =&gtnbsp; {};
     startDayIndex = parseIntcontent.appendChild(daySel.value);
      localStorage.setItem('btwk_dayIndex', startDayIndex);
     updateconst nav = document.createElement('div');
   } ;nav.style.margin = '5px 0';
   content.appendChild(daySel) ;nav.innerHTML = `
      <button id="prevW">⬅️</button>
      <button id="thisW">📅<// Navigationbutton>
   const nav    <button id= document.createElement('div')"nextW">;➡️</button>
   nav.style.margin = '5px 0' ;`;
   nav content.innerHTML = `appendChild(nav);
      <button id="prevW">⬅️</button>
     <button idconst stats ="thisW"> document.createElement('div');📅</button>
     <button stats.id ="nextW"> 'btwk_stats';➡️</button>
   ` ;stats.style.marginTop = '8px';
    content.appendChild(navstats);

    const merits = document.body.appendChildcreateElement(box'div');
   box merits.appendChild(content)id = 'btwk_merits';
    merits.style.marginTop = '12px';
   const stats = document merits.createElement(style.borderTop = 'div1px solid #666');
   stats merits.idstyle.paddingTop = 'btwk_stats8px';
   stats content.style.marginTop = '8px'appendChild(merits);
    content.appendChild(stats);
    const timestamp = document.createElement('div');
   const merits = document timestamp.createElement(id = 'divbtwk_timestamp');
   merits timestamp.idstyle.marginTop = 'btwk_merits10px';
   merits timestamp.style.marginTopfontSize = '12px11px';
   merits timestamp.style.borderTopcolor = '1px solid #666ccc';
   merits content.style.paddingTop = '8px'appendChild(timestamp);
    content.appendChild(merits);
    box.appendChild(content);
    document.getElementById('prevW')body.onclick = appendChild(box) =>; { currentWeekOffset--; update(); };
    document.getElementById('nextW').onclick = () => { currentWeekOffset++; update(); };
    document.getElementById('thisWprevW').onclick = () => { currentWeekOffset = 0--; update(); };
    document.getElementById('nextW').onclick = () => { currentWeekOffset++; update(); };
    document.getElementById('thisW').onclick = () => { currentWeekOffset = 0; update(); };
  function update() { }
    fetchBoardStats();
   fetchMeritsfunction update(); {
  }   fetchBoardStats();
    fetchMerits();
  renderBox   updateTimestamp();
  update() ;}

  renderBox();
  update();
  setInterval(update, 5 * 60 * 1000);
})();

Update: ver.1.1

Code: (1.1 fix cache)
// ==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();
})();
Original archived [Tool] Btctalk weekly stats v1.0
Scraped on 19/07/2025, 22:12:17 UTC
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

In attesa di vostri feedback vi lascio il codice da inserire su tampermonkey o greasemonkey oppure violentmonkey

Code:
// ==UserScript==
// @name         Bitcointalk Weekly Tracker + Weekly Merit
// @namespace    https://bitcointalk.org
// @version      1.0
// @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, togli gli username tra gli apici per eliminare l'account
  let selectedUser = localStorage.getItem('btwk_user') || usernames[0];
  let startDayIndex = parseInt(localStorage.getItem('btwk_dayIndex')) || 5; // default venerdì
  let currentWeekOffset = 0;

  function getWeekRange(offset = 0) {
    const now = new Date();
    const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
    const day = today.getUTCDay();
    const daysSinceStart = (day + 7 - startDayIndex) % 7;
    const start = new Date(today);
    start.setUTCDate(today.getUTCDate() - daysSinceStart + offset * 7);
    start.setUTCHours(0, 0, 0, 0);
    const end = new Date(start);
    end.setUTCDate(start.getUTCDate() + 7);
    end.setUTCHours(0, 0, 0, 0);
    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)}`
    };
  }

  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).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);

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