Bitcointalk Mobile Enhancer v1.2.1 (Custom Ranks)

Tema moderno AMOLED, quote leggibili, pulsanti, sMerit via AJAX, toggle dark/light, barra progresso + nome rank (supporta custom ranks dopo Legendary)

当前为 2025-08-24 提交的版本,查看 最新版本

// ==UserScript==
// @name         Bitcointalk Mobile Enhancer v1.2.1 (Custom Ranks)
// @namespace    Violentmonkey Scripts
// @version      1.2.1
// @description  Tema moderno AMOLED, quote leggibili, pulsanti, sMerit via AJAX, toggle dark/light, barra progresso + nome rank (supporta custom ranks dopo Legendary)
// @match        https://bitcointalk.org/*
// @grant        none
// @author       *ace*
// @license      MIT
// ==/UserScript==

(function () {
'use strict';

let sMerit = null;

// 🔹 Tabella rank (ufficiali + custom)
const rankTable = [
    { name: "Brand New", merit: 0, activity: 0 },
    { name: "Newbie", merit: 0, activity: 1 },
    { name: "Jr. Member", merit: 1, activity: 30 },
    { name: "Member", merit: 10, activity: 60 },
    { name: "Full Member", merit: 100, activity: 120 },
    { name: "Sr. Member", merit: 250, activity: 240 },
    { name: "Hero Member", merit: 500, activity: 480 },
    { name: "Legendary", merit: 1000, activity: 775 },
    // 👇 custom ranks
    { name: "Mythical", merit: 2000, activity: 1200 },
    { name: "Padawan", merit: 5000, activity: 2000 },
    { name: "Jedi", merit: 10000, activity: 3000 },
    { name: "Immortal", merit: 20000, activity: 4000 }
];

// Carica sMerit via AJAX senza redirect visibili
function fetchSmerit() {
    const meritPage = 'https://bitcointalk.org/index.php?action=merit';
    fetch(meritPage, { credentials: 'include', redirect: 'manual' })
        .then(res => {
            if (res.type === 'opaqueredirect' || res.status === 0 || res.status === 302) {
                sMerit = '?';
                updateSmeritIndicator();
                return null;
            }
            return res.text();
        })
        .then(html => {
            if (!html) return;
            const match = html.match(/You\s+have\s+(?:<b>)?(\d+)(?:<\/b>)?\s+sendable/i);
            sMerit = match ? match[1] : '?';
            updateSmeritIndicator();
        })
        .catch(() => {
            sMerit = 'x';
            updateSmeritIndicator();
        });
}

// Indicatore sMerit
function updateSmeritIndicator() {
    let indicator = document.getElementById('smerit-indicator');
    if (!indicator) {
        indicator = document.createElement('div');
        indicator.id = 'smerit-indicator';
        document.body.appendChild(indicator);
    }
    indicator.textContent = `🪙 ${sMerit ?? '...'}`;
}

// Quote espandibili
function fixQuotes() {
    document.querySelectorAll('.quote').forEach(quote => {
        if (quote.classList.contains('enhanced')) return;
        quote.classList.add('enhanced');

        if (quote.scrollHeight > 140) {
            const button = document.createElement('div');
            button.className = 'show-more';
            button.textContent = 'Mostra tutto';
            button.onclick = () => {
                quote.classList.add('expanded');
                button.remove();
            };
            quote.appendChild(button);
        }
    });
}

// Pulsanti mobile
function addButtons() {
    document.querySelectorAll('td[class^="windowbg"] > div:nth-child(2)').forEach(post => {
        if (post.closest('.windowbg:first-child')) return;
        if (post.querySelector('.mobile-buttons')) return;

        const links = post.querySelectorAll('a');
        let quote, report, merit;

        links.forEach(a => {
            const href = a.getAttribute('href') || '';
            if (href.includes('quote')) quote = a;
            if (href.includes('report')) report = a;
            if (href.includes('merit')) merit = a;
        });

        const box = document.createElement('div');
        box.className = 'mobile-buttons';

        if (quote) {
            const q = quote.cloneNode(true);
            q.textContent = 'Quote';
            box.appendChild(q);
        }
        if (merit) {
            const m = merit.cloneNode(true);
            m.textContent = '+Merit';
            box.appendChild(m);
        }
        if (report) {
            const r = report.cloneNode(true);
            r.textContent = 'Report';
            box.appendChild(r);
        }

        post.appendChild(box);
    });
}

// Barra progresso + nome rank sotto avatar (aggiorna senza duplicati)
function addRankBarsInThreads() {
    document.querySelectorAll('td.poster_info').forEach(avatarCell => {
        const meritMatch = avatarCell.textContent.match(/Merit:\s*(\d+)/i);
        const activityMatch = avatarCell.textContent.match(/Activity:\s*(\d+)/i);
        if (!meritMatch || !activityMatch) return;

        const merit = parseInt(meritMatch[1], 10);
        const activity = parseInt(activityMatch[1], 10);

        let currentRankIndex = 0;
        for (let i = 0; i < rankTable.length; i++) {
            if (merit >= rankTable[i].merit && activity >= rankTable[i].activity) {
                currentRankIndex = i;
            }
        }

        const currentRank = rankTable[currentRankIndex];
        const nextRank = rankTable[Math.min(currentRankIndex + 1, rankTable.length - 1)];

        let totalProgress;
        if (currentRankIndex === rankTable.length - 1) {
            totalProgress = 100;
        } else {
            const meritProgress = Math.min(
                (merit - currentRank.merit) / (nextRank.merit - currentRank.merit || 1),
                1
            );
            const activityProgress = Math.min(
                (activity - currentRank.activity) / (nextRank.activity - currentRank.activity || 1),
                1
            );
            totalProgress = Math.min(meritProgress, activityProgress) * 100;
        }

        // 🔹 Controlla se esiste già il container
        let container = avatarCell.querySelector('.rank-container');
        if (!container) {
            container = document.createElement('div');
            container.className = 'rank-container';
        }

        // Nome rank
        let rankName = container.querySelector('.rank-name');
        if (!rankName) {
            rankName = document.createElement('div');
            rankName.className = 'rank-name';
            container.appendChild(rankName);
        }
        rankName.textContent = currentRank.name;

        // Barra progresso
        let fill = container.querySelector('.rank-progress-fill');
        if (!fill) {
            const bar = document.createElement('div');
            bar.className = 'rank-progress-bar';
            fill = document.createElement('div');
            fill.className = 'rank-progress-fill';
            bar.appendChild(fill);
            container.appendChild(bar);
        }
        fill.style.width = totalProgress + '%';

        // Sostituisci rank ufficiale testuale
        const officialRank = avatarCell.querySelector('.smalltext:nth-of-type(2)');
        if (officialRank) {
            officialRank.replaceWith(container);
        } else if (!avatarCell.contains(container)) {
            avatarCell.appendChild(container);
        }
    });
}

// 🔹 Temi
const lightTheme = `
  body {
  font-family: "Segoe UI", sans-serif !important;
  font-size: 16px;
  background: #f9fafb !important;
  color: #222;
  }

  table, .windowbg, .windowbg2 {
  background: #fff !important;
  border-radius: 10px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  }

  td.poster_info {
  width: 70px !important;
  max-width: 70px !important;
  background: #fff !important;
  text-align: center;
  padding: 4px !important;
  }

  td.poster_info img {
  max-width: 48px !important;
  height: auto;
  border-radius: 6px;
  }

  .quote {
  background: #e0f2fe;
  border-left: 4px solid #3b82f6;
  }

  .mobile-buttons a {
  background: #3b82f6;
  color: white !important;
  }

  .mobile-buttons a:hover {
  background: #1d4ed8;
  }

  #smerit-indicator {
  background: #10b981;
  color: white;
  }

  .rank-progress-bar {
  background: #ddd;
  }

  .rank-progress-fill {
  background: #3b82f6;
  height: 6px;
  border-radius: 3px;
  }
`;

const darkTheme = `
  body, td, tr, th {
    font-family: "Segoe UI", sans-serif !important;
    font-size: 16px;
    background: #000000 !important;
    color: #e5e7eb !important;
  }

  /* Contenitori principali */
  table, .windowbg, .windowbg2, td.td_headerandpost, table.bordercolor {
    background: #0a0a0a !important;
    color: #f1f1f1 !important;
    border-radius: 10px;
    box-shadow: 0 0 8px rgba(0,0,0,0.8);
    border: 1px solid #222 !important;
  }

  /* Info utente a sinistra */
  td.poster_info {
    width: 70px !important;
    max-width: 70px !important;
    background: #0a0a0a !important;
    text-align: center;
    padding: 4px !important;
    color: #f1f1f1 !important;
  }
  td.poster_info small {
    color: #a0a0a0 !important;
    font-size: 11px;
  }
  td.poster_info img {
    max-width: 48px !important;
    border-radius: 6px;
    box-shadow: 0 0 6px rgba(0,0,0,0.8);
  }

  /* Quote */
  .quote {
    background: #111111 !important;
    border-left: 4px solid #00d4ff !important;
    color: #f8f8f8 !important;
  }

  /* Bottoni mobile */
  .mobile-buttons a {
    background: linear-gradient(135deg, #00d4ff, #0077ff);
    color: white !important;
    box-shadow: 0 0 6px rgba(0, 122, 255, 0.6);
  }

  /* Indicatore sMerit */
  #smerit-indicator {
    background: linear-gradient(135deg, #10b981, #065f46);
    color: white !important;
    box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
  }

  /* Progress bar rank */
  .rank-progress-bar {
    background: #1f1f1f;
  }
  .rank-progress-fill {
    background: linear-gradient(90deg, #00d4ff, #0077ff);
    height: 6px;
    border-radius: 3px;
  }
`;

const commonStyles = `
/* Nasconde il rank ufficiale testuale */
  .poster_info > .smalltext:nth-of-type(2) {
  display: none !important;
}
  .quote { max-height: 120px; overflow: hidden; padding: 8px; border-radius: 6px; position: relative; margin: 6px 0; }
  .quote.expanded { max-height: none !important; }
  .quote .show-more { position: absolute; bottom: 4px; right: 6px; font-size: 12px; background: rgba(0,0,0,0.3); color: #fff; padding: 2px 6px; border-radius: 4px; cursor: pointer; }
  .mobile-buttons { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; font-size: 14px; }
  .mobile-buttons a { padding: 6px 10px; border-radius: 20px; text-decoration: none; transition: background 0.2s; }
  #smerit-indicator { position: fixed; top: 14px; left: 14px; background: #6b7280; color: white; padding: 6px 10px; font-size: 13px; border-radius: 10px; z-index: 9999; font-weight: bold; }
  #theme-toggle { position: fixed; top: 14px; right: 14px; background: #6b7280; color: white; padding: 6px 10px; font-size: 13px; border-radius: 10px; z-index: 9999; cursor: pointer; }
  .rank-container { margin-top: 6px; text-align: center; font-size: 12px; font-weight: bold; }
  .rank-name { margin-bottom: 2px; }
  .rank-progress-bar { width: 100%; margin: 0 auto; height: 6px; border-radius: 3px; }
  .rank-progress-fill { height: 6px; border-radius: 3px; }
`;

const style = document.createElement("style");
document.head.appendChild(style);

function applyTheme(theme) {
    const css = (theme === 'dark' ? darkTheme : lightTheme) + commonStyles;
    style.textContent = css;
    localStorage.setItem('bitcointalk-theme', theme);
}

// Toggle tema
const toggle = document.createElement("div");
toggle.id = "theme-toggle";
toggle.textContent = "🔆🌘";
toggle.onclick = () => {
    const current = localStorage.getItem('bitcointalk-theme') === 'dark' ? 'light' : 'dark';
    applyTheme(current);
};
document.body.appendChild(toggle);

// Avvio
applyTheme(localStorage.getItem('bitcointalk-theme') || 'light');
updateSmeritIndicator();
if (document.querySelector('a[href*="action=profile"]')) fetchSmerit();

addButtons();
fixQuotes();
addRankBarsInThreads();

// Rilancia ogni 1.5s
setInterval(() => {
    addButtons();
    fixQuotes();
    addRankBarsInThreads();
}, 1500);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址