Conteggio post settimanali + Merit ricevuti + obiettivi personalizzati
目前為
// ==UserScript==
// @name Bitcointalk Weekly post & Merit Tracker
// @namespace https://bitcointalk.org
// @version 1.3.6
// @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().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)
.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();
})();