Bitcointalk Weekly post & Merit Tracker

Conteggio post settimanali + Merit ricevuti + obiettivi personalizzati

// ==UserScript==
// @name        Bitcointalk Weekly post & Merit Tracker
// @namespace   https://bitcointalk.org
// @version     1.3.7
// @description Conteggio post settimanali + Merit ricevuti + obiettivi personalizzati 
// @author      Ace
// @match       https://bitcointalk.org/*
// @grant       none
// ==/UserScript==

(function () {
  'use strict';

  const usernames = ['yourUsername']; // inserisci il tuo username Bitcointalk
  let selectedUser = localStorage.getItem('btwk_user') || usernames[0];
  let startDayIndex = parseInt(localStorage.getItem('btwk_dayIndex')) || 5; // venerdì
  let timezoneOffset = parseInt(localStorage.getItem('btwk_tzOffset')) || 0;
  let currentWeekOffset = 0;
  let collapsed = localStorage.getItem('btwk_collapsed') === 'true';

  const defaultGoals = { minGambling: 10, maxLocal: 5 };

  function getUserGoals(user) {
    const str = localStorage.getItem(`btwk_goals_${user}`);
    if (!str) return { ...defaultGoals };
    try {
      return JSON.parse(str);
    } catch {
      return { ...defaultGoals };
    }
  }

  function saveUserGoals(user, goals) {
    localStorage.setItem(`btwk_goals_${user}`, JSON.stringify(goals));
  }

  function getWeekRange(offset = 0) {
    const now = new Date();
    const utc = new Date(now.getTime() + timezoneOffset * 60 * 60 * 1000);
    const day = utc.getUTCDay();
    const daysSinceStart = (day + 7 - startDayIndex) % 7;

    const start = new Date(utc);
    start.setUTCDate(utc.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, Math.floor(Math.random() * 1000));
    
    return {
      from: start.toISOString().split('.')[0],
      to: end.toISOString(),
      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}`;

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

        const goals = getUserGoals(selectedUser);
        const validLocal = Math.min(local, goals.maxLocal);
        const validTotal = totalAll - (local - validLocal);

        const gamblingCheck = gambling >= goals.minGambling ? '✅' : '❌';
        const localCheck = local >= goals.maxLocal ? '✅' : '❌'; 

        let html = `<b>👤 Account:</b> ${selectedUser} <span id="btwk_settings_btn" style="cursor:pointer;">⚙️</span><br>`;
        html += `<b>📆 Settimana:</b><br>${label}<br><br>`;
        html += `🧮 <b>Totale valido:</b> ${validTotal} / Totale: ${totalAll}<br>`;
        html += `🧩 <b>Non classificati:</b> ${unclassified}<br>`;
        html += `🃏 <b>Gambling:</b> ${gambling} / min ${goals.minGambling} ${gamblingCheck}<br>`;
        html += `🌍 <b>Local IT:</b> ${local} / max ${goals.maxLocal} ${localCheck}<br><br>`;

        if (otherBoards.length > 0) {
          html += `<b>📌 Altre board:</b><br>`;
          otherBoards.forEach(b => {
            html += `• ${b.name}: ${b.count}<br>`;
          });
        }

        renderStats(html);
        addSettingsListener();
      })
      .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: ${total}</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 addSettingsListener() {
    const btn = document.getElementById('btwk_settings_btn');
    if (!btn) return;
    btn.onclick = () => toggleSettingsBox();
  }

  function toggleSettingsBox() {
    let box = document.getElementById('btwk_settings_box');
    if (box) return box.remove();

    const goals = getUserGoals(selectedUser);
    box = document.createElement('div');
    box.id = 'btwk_settings_box';
    box.style.marginTop = '8px';
    box.style.padding = '8px';
    box.style.background = '#333';
    box.style.color = '#eee';
    box.style.borderRadius = '10px';

    box.innerHTML = `
      <b>⚙️ Impostazioni obiettivi per <i>${selectedUser}</i></b><br><br>
      <label>Minimo Gambling:
        <input type="number" id="goalMinGambling" min="0" value="${goals.minGambling}" style="width:60px; margin-left:8px;">
      </label><br><br>
      <label>Massimo Local IT:
        <input type="number" id="goalMaxLocal" min="0" value="${goals.maxLocal}" style="width:60px; margin-left:18px;">
      </label><br><br>
      <button id="saveGoalsBtn" style="padding:4px 10px; cursor:pointer;">💾 Salva</button>
    `;

    const container = document.getElementById('btwk_content');
    container.appendChild(box);

    document.getElementById('saveGoalsBtn').onclick = () => {
      const newMin = parseInt(document.getElementById('goalMinGambling').value) || 0;
      const newMax = parseInt(document.getElementById('goalMaxLocal').value) || 0;
      saveUserGoals(selectedUser, { minGambling: newMin, maxLocal: newMax });
      box.remove();
      update();
    };
  }

  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.width = '280px';
    box.style.zIndex = '9999';
    box.style.boxShadow = '0 0 8px rgba(0,0,0,0.6)';

    const toggleBtn = document.createElement('button');
    toggleBtn.innerText = collapsed ? '➕' : '➖';
    toggleBtn.style.position = 'absolute';
    toggleBtn.style.top = '5px';
    toggleBtn.style.right = '5px';
    toggleBtn.style.background = '#444';
    toggleBtn.style.color = '#fff';
    toggleBtn.style.border = 'none';
    toggleBtn.style.cursor = 'pointer';
    toggleBtn.style.fontSize = '14px';
    toggleBtn.style.padding = '2px 6px';
    toggleBtn.style.borderRadius = '4px';

    toggleBtn.onclick = () => {
      collapsed = !collapsed;
      localStorage.setItem('btwk_collapsed', collapsed);
      updateBoxContent();
      toggleBtn.innerText = collapsed ? '➕' : '➖';
    };

    box.appendChild(toggleBtn);

    const content = document.createElement('div');
    content.id = 'btwk_content';
    box.appendChild(content);

    document.body.appendChild(box);
    updateBoxContent();
  }

  function updateBoxContent() {
    const container = document.getElementById('btwk_content');
    if (!container) return;

    if (collapsed) {
      container.innerHTML = '<i style="opacity:0.7;">Tracker ridotto</i>';
    } else {
      const dayOptions = ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato']
        .map((day, i) => `<option value="${i}" ${i === startDayIndex ? 'selected' : ''}>${day}</option>`).join('');

      container.innerHTML = `
        <div style="margin-bottom:8px;">
          <label>👤
            <select id="btwk_user_select">${usernames.map(u => `<option value="${u}"${u === selectedUser ? ' selected' : ''}>${u}</option>`).join('')}</select>
          </label>
          <label style="margin-left:8px;">🕓 UTC
            <input type="number" id="btwk_tz_input" value="${timezoneOffset}" style="width:40px;">
          </label>
        </div>
        <div style="margin-bottom:8px;">
          <label>📅 Inizio settimana:
            <select id="btwk_day_select" style="margin-left:4px;">${dayOptions}</select>
          </label>
        </div>
        <div style="margin-bottom:8px;">
          <button id="btwk_prev">⏪</button>
          <button id="btwk_next">⏩</button>
        </div>
        <div id="btwk_stats">⏳ Caricamento...</div>
        <hr>
        <div id="btwk_merits">⏳ Caricamento Merit...</div>
      `;

      document.getElementById('btwk_user_select').onchange = (e) => {
        selectedUser = e.target.value;
        localStorage.setItem('btwk_user', selectedUser);
        update();
      };
      document.getElementById('btwk_tz_input').onchange = (e) => {
        timezoneOffset = parseInt(e.target.value) || 0;
        localStorage.setItem('btwk_tzOffset', timezoneOffset);
        update();
      };
      document.getElementById('btwk_day_select').onchange = (e) => {
        startDayIndex = parseInt(e.target.value);
        localStorage.setItem('btwk_dayIndex', startDayIndex);
        update();
      };
      document.getElementById('btwk_prev').onclick = () => {
        currentWeekOffset--;
        update();
      };
      document.getElementById('btwk_next').onclick = () => {
        currentWeekOffset++;
        update();
      };

      update();
    }
  }

  function update() {
    if (!collapsed) {
      fetchBoardStats();
      fetchMerits();
    }
  }

  renderBox();
  update();
})();