您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Monthly post and merit stats (incl. sent/received) with mobile support via CORS proxy
// ==UserScript== // @name Bitcointalk Monthly Stats + Merit Tracker (Mobile fix) // @namespace https://bitcointalk.org // @version 1.4 // @description Monthly post and merit stats (incl. sent/received) with mobile support via CORS proxy // @author *Ace* // @match https://bitcointalk.org/index.php?action=profile* // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const uid = 'userid'; const username = '*username'; // Username esatto const boxId = 'monthlyStatsBox'; const now = new Date(); let currentMonthOffset = 0; function pad(n) { return n.toString().padStart(2, '0'); } function addOneDayWithRandomSeconds(dateString) { const d = new Date(dateString); d.setDate(d.getDate() + 1); const hours = '00'; const minutes = '00'; const seconds = pad(Math.floor(Math.random() * 59) + 1); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${hours}:${minutes}:${seconds}`; } 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)}-${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=${addOneDayWithRandomSeconds(to)}`; 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 proxyFetchHTML(url) { try { const proxied = `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`; const res = await fetch(proxied); return await res.text(); } catch { return null; } } async function fetchMeritReceived(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=${encodeURIComponent(username)}&start=${from}&end=${to}`; const htmlText = await proxyFetchHTML(url); if (!htmlText) return null; 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 }; } async function fetchMeritSent(y, m) { const from = `${y}-${pad(m)}-01`; const to = `${y}-${pad(m)}-${pad(new Date(y, m, 0).getDate())}`; const url = `https://bpip.org/smerit.aspx?&from=${encodeURIComponent(username)}&start=${from}&end=${to}`; const htmlText = await proxyFetchHTML(url); if (!htmlText) return null; const doc = new DOMParser().parseFromString(htmlText, 'text/html'); const rows = Array.from(doc.querySelectorAll('table tbody tr')); if (!rows.length) return null; const toData = {}; let total = 0; rows.forEach(tr => { const tds = tr.querySelectorAll('td'); if (tds.length >= 4) { const name = tds[2].innerText.trim().replace(/\s*\(Summary\)$/i, ''); const count = parseInt(tds[3].innerText.trim()) || 0; total += count; toData[name] = (toData[name] || 0) + count; } }); return { total, toData }; } 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'; 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 →'; 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`); const { from, to, label, y, m } = getDateRange(currentMonthOffset); content.innerHTML = `📊 Loading for ${label}...<br><br>👁️ URLs:<br>` + `https://api.ninjastic.space/users/${username}/boards?from=${from}T00:00:00&to=${to}T23:59:59<br>` + `https://bpip.org/smerit.aspx?&to=${username}&start=${from}&end=${to}<br>` + `https://bpip.org/smerit.aspx?&from=${username}&start=${from}&end=${to}`; const boardData = await fetchBoardData(from, to); const meritReceived = await fetchMeritReceived(y, m); const meritSent = await fetchMeritSent(y, m); let html = `🧮 <b>Statistics for ${label}</b><br><br>`; if (!boardData) { content.innerHTML = '❌ Error loading posts.'; return; } html += `📝 <b>Posts written:</b> ${boardData.total_results_with_board}<br>`; boardData.boards.forEach(b => { html += `• ${b.name}: ${b.count}<br>`; }); if (!meritReceived) { html += `<br>⭐ <b>Merits received:</b> error`; } else { html += `<br>⭐ <b>Merits received:</b> ${meritReceived.total}<br>`; const sorted = Object.entries(meritReceived.fromData).sort((a, b) => b[1] - a[1]); sorted.forEach(([name, count]) => html += `• ${name}: ${count}<br>`); } if (!meritSent) { html += `<br>🎁 <b>Merits sent:</b> error`; } else { html += `<br>🎁 <b>Merits sent:</b> ${meritSent.total}<br>`; const sorted = Object.entries(meritSent.toData).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(); } })();