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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);

})();