Bitcointalk Banned Users Checker

Checks if a user is banned on Bitcointalk.

// ==UserScript==
// @name         Bitcointalk Banned Users Checker
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Checks if a user is banned on Bitcointalk.
// @author       promise444c5
// @match        https://bitcointalk.org/index.php?action=profile*
// @match        https://bitcointalk.org/index.php?topic=*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      loyce.club
// ==/UserScript==

(function () {
    "use strict";
  
    const BANNED_LIST_URL = "https://loyce.club/bans/usernames.txt";
    const CACHE_KEY_DATA = "bannedUsersData";
    const CACHE_KEY_TIMESTAMP = "bannedUsersTimestamp";
    const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // cache for 1 day  (24 hours)
  
    let bannedUsers = [];
    let scriptError = false;
  
    function parseBannedUsers(text) {
      const users = [];
      if (!text) return users;
  
      text.split("\n").forEach((line) => {
        const trimmedLine = line.trim();
        if (trimmedLine) {
          const parts = trimmedLine.split(":");
          if (parts.length === 2 && parts[0] && parts[1]) {
            users.push({ username: parts[1].trim(), userId: parts[0].trim() });
          } else {
            console.warn(
              `Bitcointalk Ban Checker: Skipping malformed line: ${trimmedLine}`
            );
          }
        }
      });
      return users;
    }
  
    function applyBan() {
      if (scriptError || bannedUsers.length === 0) {
        console.log(
          "Bitcointalk Ban Checker: Skipping application due to error or empty list."
        );
        return;
      }
  
      console.log(
        `Bitcointalk Ban Checker: Applying Ban on ${bannedUsers.length} banned users.`
      );
      const currentUrl = window.location.href;
      const isProfilePage = currentUrl.includes("action=profile");
      const isThreadPage = currentUrl.includes("topic=");
  
      // Add CSS style to the page
      const style = document.createElement("style");
      style.textContent = `
            .banned-user-text {
              text-decoration: line-through;
              color: red;
            }
      
            .banned-label {
              background-color: red;
              color: white;
              font-size: 10px;
              padding: 1px 3px;
              border-radius: 3px;
              margin-left: 5px;
              font-weight: bold;
              display: inline-block;
              vertical-align: middle;
            }
          `;
      document.head.appendChild(style);
  
      if (isProfilePage) {
        console.log("Bitcointalk Ban Checker: Running on profile page.");
        let profileUsernameElement = null;
        // Find the 'Name:' cell and get the next sibling cell which contains the username
        document.querySelectorAll("td.windowbg table td").forEach((td) => {
          const boldElement = td.querySelector("b");
          if (boldElement && boldElement.textContent.trim() === "Name:") {
            profileUsernameElement = td.nextElementSibling;
            console.log(
              "Bitcointalk Ban Checker: Found potential username TD element."
            );
          }
        });
  
        if (profileUsernameElement) {
          let profileUsername = profileUsernameElement.textContent.trim();
          console.log(
            `Bitcointalk Ban Checker: Checking profile username: ${profileUsername}`
          );
  
          // Check if the username exists in the bannedUsers array
          const isBanned = bannedUsers.find(
            (user) => user.username === profileUsername
          );
  
          if (isBanned) {
            let banMessage = `User ${profileUsername} is listed as banned.`;
  
            // Create and insert the ban status message below the username row
            let banStatusElement = document.createElement("div");
            banStatusElement.style.color = "red";
            banStatusElement.style.fontWeight = "bold";
            banStatusElement.style.marginTop = "5px";
            banStatusElement.textContent = banMessage;
  
            let usernameRow = profileUsernameElement.closest("tr");
            if (usernameRow && usernameRow.parentNode) {
              let newRow = document.createElement("tr");
              let newCell = document.createElement("td");
              newCell.colSpan = 2;
              newCell.appendChild(banStatusElement);
              newRow.appendChild(newCell);
              usernameRow.parentNode.insertBefore(
                newRow,
                usernameRow.nextSibling
              );
              console.log(
                "Bitcointalk Ban Checker: Inserted ban status message row."
              );
            } else {
              console.warn(
                "Bitcointalk Ban Checker: Could not find username row to insert message."
              );
            }
  
            // Check if label already exists to prevent duplicates on potential re-runs (though unlikely on profile)
            if (!profileUsernameElement.querySelector(".banned-label")) {
              const bannedLabel = document.createElement("span");
              bannedLabel.textContent = "BANNED";
              bannedLabel.classList.add("banned-label");
              // Append the label to the TD containing the username
              profileUsernameElement.appendChild(bannedLabel);
              console.log(
                "Bitcointalk Ban Checker: Appended BANNED label to username TD."
              );
              // The following lines attempt to wrap the text and re-append the label,
              profileUsernameElement.childNodes[0].nodeValue =
                profileUsernameElement.childNodes[0].nodeValue + " "; // Add space before label - This assumes first child is text node
              profileUsernameElement.innerHTML = `<span class="banned-user-text">${profileUsername}</span>`; // Wrap text - This overwrites existing content including the label just added
              profileUsernameElement.appendChild(bannedLabel);
            }
          } else {
            console.log(
              `Bitcointalk Ban Checker: User ${profileUsername} is not listed as banned.`
            );
          }
        } else {
          console.log(
            "Bitcointalk Ban Checker: Could not find username element on profile page using the 'Name:' label."
          );
        }
      } else if (isThreadPage) {
        console.log("Bitcointalk Ban Checker: Running on thread page.");
        function checkBannedUsersOnThread() {
          const postElements = document.querySelectorAll(".windowbg, .windowbg2");
          let bannedFound = 0;
  
          postElements.forEach((post) => {
            const usernameElement = post.querySelector(".poster_info b a");
            if (!usernameElement) return;
  
            // Check if style already applied to prevent redundant checks/updates
            if (usernameElement.style.textDecoration === "line-through") return;
  
            const username = usernameElement.textContent.trim();
  
            // Check if the username exists in the bannedUsers array
            const isBanned = bannedUsers.find(
              (user) => user.username === username
            );
  
            if (isBanned) {
              // Apply styles directly to the <a> tag
              usernameElement.style.textDecoration = "line-through";
              usernameElement.style.color = "red";
  
              // create a label next to the username link
              if (!usernameElement.parentNode.querySelector(".banned-label")) {
                const bannedLabel = document.createElement("span");
                bannedLabel.textContent = "BANNED";
                bannedLabel.classList.add("banned-label");
                usernameElement.parentNode.insertBefore(
                  bannedLabel,
                  usernameElement.nextSibling
                );
                bannedFound++;
                console.log(
                  `Bitcointalk Ban Checker: Added BANNED label for ${username}.`
                );
              }
            }
          });
          if (bannedFound > 0) {
            console.log(
              `Bitcointalk Ban Checker: Found ${bannedFound} banned occurence`
            );
          }
        }
  
        // Initial check
        checkBannedUsersOnThread();
      }
    }
  
    /*
     ** Fetch and cache list,
     ** Used in the main script execution
     */
    const fetchAndCacheList = () => {
      console.log("Bitcointalk Ban Checker: Fetching fresh banned users list...");
  
      //Modified header
      const reqHeader = {
        "User-Agent": "UserScript/1.0",
        Accept: "text/plain",
        "Accept-Language": "en",
        Referer: "",
      };
  
      GM_xmlhttpRequest({
        method: "GET",
        url: BANNED_LIST_URL,
        headers: reqHeader,
        timeout: 5000, // 5 seconds timeout
        onload: (response) => {
          if (response.status >= 200 && response.status < 300) {
            const fetchedText = response.responseText;
            bannedUsers = parseBannedUsers(fetchedText);
            GM_setValue(CACHE_KEY_DATA, fetchedText); // Store raw text
            GM_setValue(CACHE_KEY_TIMESTAMP, Date.now());
            console.log(
              `Bitcointalk Ban Checker: Successfully fetched and cached ${bannedUsers.length} users.`
            );
            applyBan();
          } else {
            console.error(
              `Bitcointalk Ban Checker: Failed to fetch list. Status: ${response.status}`
            );
            scriptError = true;
            // use stale cache if available?
            applyBan();
          }
        },
        onerror: function (error) {
          console.error(
            "Bitcointalk Ban Checker: Network error fetching list.",
            error
          );
          scriptError = true;
        },
      });
    };
  
    /* --- Main Script Execution ---*/
    const cachedTimestamp = GM_getValue(CACHE_KEY_TIMESTAMP, 0);
    const cachedData = GM_getValue(CACHE_KEY_DATA, null);
    const now = Date.now();
  
    if (cachedData && now - cachedTimestamp < CACHE_DURATION_MS) {
      console.log("Bitcointalk Ban Checker: Using cached banned users list.");
      bannedUsers = parseBannedUsers(cachedData);
      applyBan();
    } else {
      if (cachedData) {
        console.log("Bitcointalk Ban Checker: Cache expired, fetching new list.");
        // Use stale data immediately while fetching in background
        bannedUsers = parseBannedUsers(cachedData);
        applyBan();
        fetchAndCacheList(); // Fetch new list in background
      } else {
        console.log("Bitcointalk Ban Checker: No cache found, fetching list.");
        fetchAndCacheList(); // Fetch list
      }
    }
  })();