Bitcointalk Mobile Enhancer v1.4.1 (Popup Styling)

Tema moderno AMOLED, quote leggibili, pulsanti, sMerit via AJAX, toggle dark/light, barra progresso + nome rank, popup per invio merit con stile personalizzato

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Bitcointalk Mobile Enhancer v1.4.1 (Popup Styling)
// @namespace    Violentmonkey Scripts
// @version      1.4.1
// @description  Tema moderno AMOLED, quote leggibili, pulsanti, sMerit via AJAX, toggle dark/light, barra progresso + nome rank, popup per invio merit con stile personalizzato
// @match        https://bitcointalk.org/*
// @grant        none
// @author       *ace*
// @license      MIT
// @require      https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.3.1.min.js
// ==/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 },
    { name: "🌀 Mythical", merit: 1500, activity: 1200 },
    { name: "🔺 Ascendant", merit: 2500, activity: 2000 },
    { name: "🌌 Celestial", merit: 5000, activity: 3000 },
    { name: "♾️ Immortal", merit: 10000, activity: 4000 }
  ];

  // Recupera il token CSRF
  function getCsrfToken() {
    const logoutLink = document.querySelector('td.maintab_back a[href*="index.php?action=logout;sesc="]');
    if (!logoutLink) return null;
    const match = /;sesc=(.*)/.exec(logoutLink.href);
    return match ? match[1] : null;
  }

  // Invia merit via POST
  function sendMerit(msgId, merits, sc) {
    const formData = new FormData();
    formData.append('merits', merits);
    formData.append('msgID', msgId);
    formData.append('sc', sc);

    fetch('https://bitcointalk.org/index.php?action=merit', {
      method: 'POST',
      credentials: 'include',
      body: formData,
    })
    .then(response => response.text())
    .then(data => {
      if (data.includes('<title>An Error Has Occurred!</title>')) {
        alert('Errore nell\'invio del merit.');
      } else if (data.includes(`#msg${msgId}`)) {
        alert('Merit inviato con successo!');
        fetchSmerit();
      } else {
        alert('Risposta del server indeterminata.');
      }
    })
    .catch(() => alert('Errore di rete.'));
  }

  // Crea il popup per l'invio dei merit
  function openMeritPopup(msgId, sc) {
    let popup = document.getElementById(`merit-popup-${msgId}`);
    if (popup) {
      popup.remove();
    }

    popup = document.createElement('div');
    popup.id = `merit-popup-${msgId}`;
    popup.className = 'merit-popup';
    popup.style.position = 'absolute';
    popup.style.right = '40px';
    popup.style.zIndex = '10000';
    popup.style.display = 'none';

    popup.innerHTML = `
      <form>
        <div style="margin-bottom: 8px;">
          Merit points: <input size="4" name="merits" value="1" type="text" style="text-align: center;" />
        </div>
        <div style="text-align: right;">
          <input value="Invia" type="submit" style="margin-left: 8px;" />
        </div>
      </form>
    `;

    popup.querySelector('form').onsubmit = (e) => {
      e.preventDefault();
      const merits = e.target.elements['merits'].value;
      const submitBtn = e.target.querySelector('input[type="submit"]');
      submitBtn.disabled = true;
      submitBtn.value = 'Invio...';

      sendMerit(msgId, merits, sc)
        .finally(() => {
          submitBtn.disabled = false;
          submitBtn.value = 'Invia';
          popup.style.display = 'none';
        });
    };

    return popup;
  }

  // Aggiungi i popup ai link "+Merit"
  function addMeritPopups() {
    const sc = getCsrfToken();
    if (!sc) return;

    document.querySelectorAll('a[href*="index.php?action=merit;msg="]').forEach(link => {
      const msgId = /msg=([0-9]+)/.exec(link.href)[1];
      const popup = openMeritPopup(msgId, sc);
      link.parentNode.insertBefore(popup, link.nextSibling);

      link.onclick = (e) => {
        e.preventDefault();
        popup.style.display = popup.style.display === 'none' ? 'block' : 'none';
      };
    });
  }

  // Carica sMerit via AJAX
  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 (report) {
        const r = report.cloneNode(true);
        r.textContent = 'Report';
        box.appendChild(r);
      }

      post.appendChild(box);
    });
  }

  // Barra progresso + nome rank sotto avatar
  function addRankBarsInThreads() {
    document.querySelectorAll('td.poster_info').forEach(avatarCell => {
      if (avatarCell.querySelector('.rank-container')) return;

      const text = avatarCell.textContent;
      const meritMatch = text.match(/Merit:\s*(\d+)/i);
      const activityMatch = text.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;
      }

      const container = document.createElement('div');
      container.className = 'rank-container';

      const rankName = document.createElement('div');
      rankName.className = 'rank-name';
      rankName.textContent = currentRank.name;
      container.appendChild(rankName);

      const bar = document.createElement('div');
      bar.className = 'rank-progress-bar';
      const fill = document.createElement('div');
      fill.className = 'rank-progress-fill';
      fill.style.width = totalProgress + '%';
      bar.appendChild(fill);
      container.appendChild(bar);

      avatarCell.appendChild(container);
    });
  }

  function removeOfficialRank() {
    document.querySelectorAll('td.poster_info div.smalltext').forEach(div => {
      const rankTexts = [
        "Brand New", "Newbie", "Jr. Member", "Member",
        "Full Member", "Sr. Member", "Hero Member", "Legendary"
      ];
      let lines = div.innerHTML.split('<br>');
      lines = lines.filter(line => {
        return !rankTexts.some(rank => line.includes(rank));
      });
      div.innerHTML = lines.join('<br>');
    });
  }

  // 🔹 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;
    }
    .merit-popup {
      background: #f0f0f0 !important;
      color: #333 !important;
      border: 1px solid #ccc !important;
      border-radius: 6px !important;
      padding: 10px !important;
      font-size: 14px !important;
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1) !important;
    }
    .merit-popup input[type="text"] {
      background: #fff !important;
      color: #333 !important;
      border: 1px solid #ccc !important;
      padding: 4px 8px !important;
      border-radius: 4px !important;
    }
    .merit-popup input[type="submit"] {
      background: #3b82f6 !important;
      color: white !important;
      border: none !important;
      padding: 6px 12px !important;
      border-radius: 4px !important;
      cursor: pointer !important;
    }
    .merit-popup input[type="submit"]:hover {
      background: #1d4ed8 !important;
    }
  `;

  const darkTheme = `
    body, td, tr, th {
      font-family: "Segoe UI", sans-serif !important;
      font-size: 16px;
      background: #000000 !important;
      color: #e5e7eb !important;
    }
    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;
    }
    td.titlebg, td.catbg {
      background: #111 !important;
      color: #00d4ff !important;
      font-weight: bold;
    }
    td.titlebg a, td.catbg a {
      color: #00d4ff !important;
      font-weight: bold;
      text-decoration: none !important;
    }
    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 {
      background: #111111 !important;
      border-left: 4px solid #00d4ff !important;
      color: #f8f8f8 !important;
    }
    .quote cite, .quote .quoteheader {
      color: #00d4ff !important;
      font-weight: bold;
    }
    .quote .quote {
      background: #1a1a1a !important;
      border-left: 3px solid #0077ff !important;
    }
    .mobile-buttons a {
      background: linear-gradient(135deg, #00d4ff, #0077ff);
      color: white !important;
      box-shadow: 0 0 6px rgba(0, 122, 255, 0.6);
    }
    .mobile-buttons a:hover {
      background: linear-gradient(135deg, #00aaff, #0055cc);
    }
    #smerit-indicator {
      background: linear-gradient(135deg, #10b981, #065f46);
      color: white !important;
      box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
    }
    .rank-progress-bar {
      background: #1f1f1f;
    }
    .rank-progress-fill {
      background: linear-gradient(90deg, #00d4ff, #0077ff);
      height: 6px;
      border-radius: 3px;
    }
    a.board, a:link, a:visited {
      color: #00d4ff !important;
      font-weight: bold;
      text-decoration: none !important;
    }
    a:hover {
      color: #ffffff !important;
      text-decoration: underline !important;
    }
    .smerit_received, .smerit_given, .activity {
      color: #00d4ff !important;
      font-weight: bold;
      background: #111111 !important;
      padding: 2px 4px;
      border-radius: 4px;
      display: inline-block;
    }
    .smerit_received a, .smerit_given a, .activity a {
      color: #00d4ff !important;
      text-decoration: none !important;
    }
    .smerit_received:hover, .smerit_given:hover, .activity:hover {
      color: #ffffff !important;
      background: #0077ff !important;
    }
    .smalltext, .smalltext a {
      color: #c0c0c0 !important;
    }
    .smalltext a:hover {
      color: #00d4ff !important;
    }
    input, select, textarea {
      background: #111 !important;
      color: #f1f1f1 !important;
      border: 1px solid #333 !important;
      border-radius: 6px;
      padding: 4px 6px;
    }
    input[type="submit"], input[type="button"], button {
      background: linear-gradient(135deg, #00d4ff, #0077ff) !important;
      color: #fff !important;
      border: none !important;
      border-radius: 6px !important;
      padding: 6px 12px !important;
      cursor: pointer;
    }
    input[type="submit"]:hover, input[type="button"]:hover, button:hover {
      background: linear-gradient(135deg, #00aaff, #0055cc) !important;
    }
    tr td:nth-child(1) .trust img {
      filter: brightness(2) contrast(2) !important;
    }
    .merit-popup {
      background: #1e1e1e !important;
      color: #e0e0e0 !important;
      border: 1px solid #444 !important;
      border-radius: 6px !important;
      padding: 10px !important;
      font-size: 14px !important;
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important;
    }
    .merit-popup input[type="text"] {
      background: #2d2d2d !important;
      color: #e0e0e0 !important;
      border: 1px solid #444 !important;
      padding: 4px 8px !important;
      border-radius: 4px !important;
    }
    .merit-popup input[type="submit"] {
      background: linear-gradient(135deg, #00d4ff, #0077ff) !important;
      color: white !important;
      border: none !important;
      padding: 6px 12px !important;
      border-radius: 4px !important;
      cursor: pointer !important;
    }
    .merit-popup input[type="submit"]:hover {
      background: linear-gradient(135deg, #00aaff, #0055cc) !important;
    }
  `;

  const commonStyles = `
    .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();
  addMeritPopups();
  addButtons();
  fixQuotes();
  addRankBarsInThreads();
  removeOfficialRank();
  if (document.querySelector('a[href*="action=profile"]')) fetchSmerit();

  // Rilancia ogni 11s
  setInterval(() => {
    addButtons();
    fixQuotes();
    addRankBarsInThreads();
    removeOfficialRank();
    addMeritPopups();
  }, 11000);

})();