Photon Ultra

Integrates Trading Bot, Bundle Analysis, Pump & Fun Dupe Checker, X Tweet Hover Preview, and URL Highlighting scripts

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
/*
//////////////////////////////////////////////////
//                                              //
//        Photon Ultra by nocommas              //
//        Twitter: twitter.com/n0commas         //
//                                              //
//  START TRADING ON PHOTON NOW                 //
//                                              //
//  https://photon-sol.tinyastro.io/@nocommas   //
//                                              //
//////////////////////////////////////////////////
*/
// ==UserScript==
// @name         Photon Ultra
// @namespace    https://violentmonkey.github.io/
// @version      1.0
// @description  Integrates Trading Bot, Bundle Analysis, Pump & Fun Dupe Checker, X Tweet Hover Preview, and URL Highlighting scripts
// @author       nocommas
// @match        https://photon-sol.tinyastro.io/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      twitter.com
// @connect      x.com
// ==/UserScript==

(function () {
  'use strict';

  /****************************************************************
   *   Helpers for loading/saving toggles and building the UI
   ****************************************************************/
  const DEFAULT_SETTINGS = {
    enableTradingBot: true,
    enableBundleAnalysis: true,
    enablePumpFunDupeChecker: true,
    enableTweetHoverPreview: true,
    enableUrlHighlighting: true, // Added for URL highlighting
    panelCollapsed: false,
    debugMode: false
  };

  function loadSettings() {
    try {
      const stored = JSON.parse(localStorage.getItem('photonAllInOneSettings'));
      return stored ? Object.assign({}, DEFAULT_SETTINGS, stored) : { ...DEFAULT_SETTINGS };
    } catch (e) {
      return { ...DEFAULT_SETTINGS };
    }
  }

  function saveSettings(settings) {
    localStorage.setItem('photonAllInOneSettings', JSON.stringify(settings));
  }

  const scriptSettings = loadSettings();

  function logDebug(message) {
    if (scriptSettings.debugMode) {
      console.log(`[Debug]: ${message}`);
    }
  }

  function createTogglesUI() {
    if (document.getElementById('photon-all-in-one-toggles')) return;

    const container = document.createElement('div');
    container.id = 'photon-all-in-one-toggles';

    const collapseBtn = document.createElement('button');
    collapseBtn.id = 'photonPanelCollapseBtn';
    collapseBtn.textContent = scriptSettings.panelCollapsed ? '+' : '−';
    container.appendChild(collapseBtn);

    const body = document.createElement('div');
    body.id = 'photon-all-in-one-toggles-body';
    body.innerHTML = `
      <label>
        <input type="checkbox" id="toggleTradingBot" />
        Calculate Trading Bot % as of Total Holders
      </label>
      <label>
        <input type="checkbox" id="toggleBundleAnalysis" />
        Analyze Bundles
      </label>
      <label>
        <input type="checkbox" id="togglePumpFunDupeChecker" />
        Check Dupes on PumpFun
      </label>
      <label>
        <input type="checkbox" id="toggleTweetHoverPreview" />
        Enable Tweet Hover Preview
      </label>
      <label>
        <input type="checkbox" id="toggleUrlHighlighting" />
        Enable URL Highlighting
      </label>
      <label>
        <input type="checkbox" id="toggleDebugMode" />
        Enable Debug Mode
      </label>
      <small>Changes take effect on next page load/navigation.</small>
    `;
    container.appendChild(body);

    document.body.appendChild(container);

    body.querySelector('#toggleTradingBot').checked = scriptSettings.enableTradingBot;
    body.querySelector('#toggleBundleAnalysis').checked = scriptSettings.enableBundleAnalysis;
    body.querySelector('#togglePumpFunDupeChecker').checked = scriptSettings.enablePumpFunDupeChecker;
    body.querySelector('#toggleTweetHoverPreview').checked = scriptSettings.enableTweetHoverPreview;
    body.querySelector('#toggleUrlHighlighting').checked = scriptSettings.enableUrlHighlighting;
    body.querySelector('#toggleDebugMode').checked = scriptSettings.debugMode;

    body.querySelector('#toggleTradingBot').addEventListener('change', (e) => {
      scriptSettings.enableTradingBot = e.target.checked;
      saveSettings(scriptSettings);
    });
    body.querySelector('#toggleBundleAnalysis').addEventListener('change', (e) => {
      scriptSettings.enableBundleAnalysis = e.target.checked;
      saveSettings(scriptSettings);
    });
    body.querySelector('#togglePumpFunDupeChecker').addEventListener('change', (e) => {
      scriptSettings.enablePumpFunDupeChecker = e.target.checked;
      saveSettings(scriptSettings);
    });
    body.querySelector('#toggleTweetHoverPreview').addEventListener('change', (e) => {
      scriptSettings.enableTweetHoverPreview = e.target.checked;
      saveSettings(scriptSettings);
    });
    body.querySelector('#toggleUrlHighlighting').addEventListener('change', (e) => {
      scriptSettings.enableUrlHighlighting = e.target.checked;
      saveSettings(scriptSettings);
    });
    body.querySelector('#toggleDebugMode').addEventListener('change', (e) => {
      scriptSettings.debugMode = e.target.checked;
      saveSettings(scriptSettings);
    });

    if (scriptSettings.panelCollapsed) {
      body.style.display = 'none';
    } else {
      body.style.display = 'block';
    }

    collapseBtn.addEventListener('click', () => {
      scriptSettings.panelCollapsed = !scriptSettings.panelCollapsed;
      saveSettings(scriptSettings);
      body.style.display = scriptSettings.panelCollapsed ? 'none' : 'block';
      collapseBtn.textContent = scriptSettings.panelCollapsed ? '+' : '−';
    });

    GM_addStyle(`
      #photon-all-in-one-toggles {
        position: fixed;
        top: 20px;
        right: 10px;
        background: rgba(0, 0, 0, 0.85);
        color: #fff;
        font-family: Arial, sans-serif;
        font-size: 12px;
        border-radius: 5px;
        z-index: 99999;
        padding: 5px;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
      }
      #photonPanelCollapseBtn {
        background: #333;
        color: #fff;
        border: none;
        font-weight: bold;
        font-size: 14px;
        line-height: 1;
        width: 24px;
        height: 24px;
        cursor: pointer;
        border-radius: 3px;
        margin: 0;
        padding: 0;
      }
      #photonPanelCollapseBtn:hover {
        background: #444;
      }
      #photon-all-in-one-toggles-body {
        margin-top: 8px;
      }
      #photon-all-in-one-toggles-body label {
        display: block;
        margin-bottom: 6px;
        cursor: pointer;
      }
      #photon-all-in-one-toggles-body label input[type="checkbox"] {
        vertical-align: middle;
        margin-right: 4px;
      }
      #photon-all-in-one-toggles-body small {
        display: block;
        margin-top: 8px;
        opacity: 0.7;
      }
    `);
  }

  /****************************************************************
   *    New Feature: URL Highlighting (extracted from Feature #1)
   *    (Runs only on /en/memescope if enabled)
   ****************************************************************/
  function initUrlHighlighting() {
    if (!window.location.href.includes('/en/memescope')) return;

    function startObserver() {
      const parentSelector = '.IkXVawB0ALMCnMdJvOFY.sZGsa8iyXO7CgxH57jwf.l-col-12.l-col-xl-4';
      const parent = document.querySelector(parentSelector);
      if (!parent) {
        setTimeout(startObserver, 100);
        return;
      }
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.type === 'childList' && mutation.addedNodes.length) {
            mutation.addedNodes.forEach((node) => {
              if (node.nodeType === 1 && node.classList.contains('sBVBv2HePq7qYTpGDmRM')) {
                processCard(node);
              }
            });
          }
        });
      });
      observer.observe(parent, { childList: true, subtree: true });
      logDebug('MutationObserver for URL Highlighting started.');
    }

    async function processCard(card) {
      if (card.dataset.processed) return;
      card.dataset.processed = 'true';

      await new Promise((resolve) => setTimeout(resolve, 1));

      const urlContainer = card.querySelector('div.D05u1bw1k0YiV6GK94gQ');
      if (!urlContainer) return;
      const urls = Array.from(urlContainer.querySelectorAll('a[href]'))
        .map((a) => {
          const dataIcon = a.querySelector('span[data-icon]')?.getAttribute('data-icon');
          if (dataIcon === 'pump-grey') return null;
          return { type: dataIcon, url: a.getAttribute('href') };
        })
        .filter(Boolean);
      logDebug(`Extracted ${urls.length} URL(s) from card.`);

      for (const { type, url } of urls) {
        if (!url) continue;

        if ((type === 'website' || type === 'twitter') && url.includes('status')) {
          card.style.border = '2px solid lime';
        }
        if (type === 'website' && url.includes('reddit.com')) {
          card.style.border = '2px solid orange';
        }
        if (type === 'website' && (url.includes('vm.tiktok.com') || url.includes('tiktok.com'))) {
          card.style.border = '2px solid cyan';
        }
        if (type === 'website' && (url.includes('kick.tv') || url.includes('twitch.tv'))) {
          card.style.border = '2px solid purple';
        }
      }
    }

    startObserver();
  }

  /****************************************************************
   *    Feature #2: Photon MemeScope Trading Bot Holders Comparison
   ****************************************************************/
  function initTradingBotHoldersComparison() {
    if (!window.location.href.includes('/en/memescope')) return;

    (function () {
      function getTradingBotHolders(div) {
        const el = div.querySelector('.t2JBH0X8tBwr1QlvoIbT.u-color-dark.u-font-light-semibold.u-d-flex.u-align-items-center');
        return el ? parseInt(el.textContent.trim(), 10) : null;
      }

      function getTotalHolders(div) {
        const el = div.querySelector('.t2JBH0X8tBwr1QlvoIbT.u-d-flex.u-align-items-center .u-font-semibold');
        return el ? parseInt(el.textContent.trim(), 10) : null;
      }

      function getNeonColorForPercentage(percentage) {
        if (percentage < 10) return '#FF355E';
        if (percentage >= 75) return '#39FF14';
        const normalized = (percentage - 10) / 65;
        const red = Math.round(255 - (255 - 53) * normalized);
        const green = Math.round(53 + (255 - 53) * normalized);
        return `rgb(${red}, ${green}, 20)`;
      }

      function displayPercentage(div, tradingBotHolders, totalHolders) {
        if (tradingBotHolders !== null && totalHolders !== null && totalHolders > 0) {
          const percentage = ((tradingBotHolders / totalHolders) * 100).toFixed(0);
          let percentageElement = div.querySelector('.trading-bot-percentage');
          if (!percentageElement) {
            percentageElement = document.createElement('div');
            percentageElement.className = 'trading-bot-percentage';
            percentageElement.style.fontWeight = 'bold';
            percentageElement.style.marginLeft = '8px';
            const holderContainer = div.querySelector('.t2JBH0X8tBwr1QlvoIbT.u-d-flex.u-align-items-center');
            if (holderContainer) holderContainer.appendChild(percentageElement);
          }
          percentageElement.textContent = `${percentage}%`;
          percentageElement.style.color = getNeonColorForPercentage(parseFloat(percentage));
        }
      }

      function processCard(card) {
        const tradingBotHolders = getTradingBotHolders(card);
        const totalHolders = getTotalHolders(card);
        displayPercentage(card, tradingBotHolders, totalHolders);
      }

      function observeCards() {
        const cards = document.querySelectorAll('.sBVBv2HePq7qYTpGDmRM.VTmpJ0jdbJuSJQ4HKGlN');
        cards.forEach(processCard);
      }

      const observer = new MutationObserver(() => observeCards());
      observer.observe(document.body, { childList: true, subtree: true });
      observeCards();
      setInterval(observeCards, 1000);
    })();
  }

  function extractTokenDetails() {
    const tokenAddressElement = document.querySelector('div[data-show-token-address]');
    const tokenAddress = tokenAddressElement?.getAttribute('data-show-token-address');
    const tokenNameElement = document.querySelector('.p-show__bar__title span[data-tippy-content]');
    const tokenName = tokenNameElement?.getAttribute('data-tippy-content')?.trim();
    return tokenAddress && tokenName ? { tokenName, tokenAddress } : null;
  }

  /****************************************************************
   *    Feature #3: Photon Bundle Analysis
   ****************************************************************/
  function initBundleAnalysis() {
    if (!window.location.href.includes('/en/lp')) return;
    (function () {
      let currentAddress = null;
      let refreshInterval = null;

      function analyzeBundles(bundles) {
        let countBelow5 = 0, countBetween5And10 = 0, countAbove10 = 0;
        if (!bundles || typeof bundles !== 'object') {
          console.error('Bundles data is invalid or missing:', bundles);
          return { countBelow5, countBetween5And10, countAbove10 };
        }
        Object.values(bundles).forEach(bundle => {
          const holdingPercentage = bundle.holding_percentage || 0;
          if (holdingPercentage < 5) countBelow5++;
          else if (holdingPercentage >= 5 && holdingPercentage < 10) countBetween5And10++;
          else if (holdingPercentage >= 10) countAbove10++;
        });
        return { countBelow5, countBetween5And10, countAbove10 };
      }

      function getColorForPercentage(percentage) {
        if (percentage >= 20) return 'red';
        if (percentage >= 10) return 'yellow';
        return 'green';
      }

      function createOrUpdateUI(data, bundleCounts, timestamp) {
        const { countBelow5, countBetween5And10, countAbove10 } = bundleCounts;
        const holdingPercentageColor = getColorForPercentage(data.total_holding_percentage);
        const fadedFeedDiv = document.querySelector('#faded-feed');
        let container = fadedFeedDiv.querySelector('#custom-bundle-analysis-container');
        if (!container) {
          container = document.createElement('div');
          container.id = 'custom-bundle-analysis-container';
          container.className = 'p-show__widget p-show__info u-p-0 u-mb-xs u-mb-0-lg custom-bundle-analysis';
          fadedFeedDiv.insertBefore(container, fadedFeedDiv.firstChild);
        } else if (fadedFeedDiv.firstChild !== container) {
          fadedFeedDiv.insertBefore(container, fadedFeedDiv.firstChild);
        }

        const content = `
          <div class="c-info js-info">
            <div class="c-info__content js-info__content">
              <div class="l-row l-row-gap--l u-mt-s">
                <div class="l-col-auto c-info__col">
                  <div class="c-info__left">
                    <div class="c-info__cell u-font-size-zh-3xs">
                      Held
                      <div class="c-info__cell__value" style="color: ${holdingPercentageColor};">${data.total_holding_percentage.toFixed(2)}%</div>
                    </div>
                  </div>
                </div>
                <div class="l-col">
                  <div class="l-row u-justify-content-between">
                    <div class="l-col-auto">
                      <div class="c-info__cell u-font-size-zh-3xs">
                        Last Updated
                        <div class="c-info__cell__value">${timestamp}</div>
                      </div>
                    </div>
                    <div class="l-col-auto">
                      <div class="c-info__cell u-font-size-zh-3xs">
                        Total % Bundled
                        <div class="c-info__cell__value">${data.total_percentage_bundled.toFixed(2)}%</div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div class="l-row l-row-gap--l u-mt-s">
                <div class="l-col-auto c-info__col">
                  <div class="c-info__left">
                    <div class="c-info__cell u-font-size-zh-3xs">
                      Total Bundles
                      <div class="c-info__cell__value">${data.total_bundles}</div>
                    </div>
                  </div>
                </div>
                <div class="l-col">
                  <div class="l-row u-justify-content-between">
                    <div class="l-col-auto">
                      <div class="c-info__cell u-font-size-zh-3xs">
                        < 5%
                        <div class="c-info__cell__value">${countBelow5}</div>
                      </div>
                    </div>
                    <div class="l-col-auto">
                      <div class="c-info__cell u-font-size-zh-3xs ">
                        5%-10%
                        <div class="c-info__cell__value">${countBetween5And10}</div>
                      </div>
                    </div>
                    <div class="l-col-auto">
                      <div class="c-info__cell u-font-size-zh-3xs">
                        > 10%
                        <div class="c-info__cell__value">${countAbove10}</div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div class="l-row l-row-gap--l u-mt-s">
                <div class="l-col-auto c-info__col">
                  <div class="c-info__left">
                    <div class="c-info__cell u-font-size-zh-3xs">
                      Dev Holding
                      <div class="c-info__cell__value">${data.creator_analysis.holding_percentage.toFixed(2)}%</div>
                    </div>
                  </div>
                </div>
                <div class="l-col">
                  <div class="l-row u-justify-content-between">
                    <div class="l-col-auto">
                      <div class="c-info__cell u-font-size-zh-3xs">
                        Dev Risk
                        <div class="c-info__cell__value">${data.creator_analysis.high_risk ? 'Yes' : 'No'}</div>
                      </div>
                    </div>
                    <div class="l-col-auto">
                      <div class="c-info__cell u-font-size-zh-3xs">
                        Tokens Created
                        <div class="c-info__cell__value">${data.creator_analysis.history.total_coins_created}</div>
                      </div>
                    </div>
                    <div class="l-col-auto">
                      <div class="c-info__cell u-font-size-zh-3xs">
                        Rug Count
                        <div class="c-info__cell__value">${data.creator_analysis.history.rug_count}</div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        `;

        if (container.innerHTML !== content) container.innerHTML = content;
      }

      function fetchBundleData(address) {
        if (address) {
          GM_xmlhttpRequest({
            method: "GET",
            url: `https://trench.bot/api/bundle/bundle_advanced/${address}`,
            onload: function (response) {
              try {
                const data = JSON.parse(response.responseText);
                if (!data || typeof data !== 'object') throw new Error('Invalid response data');
                const bundleCounts = analyzeBundles(data.bundles);
                const timestamp = new Date().toLocaleTimeString();
                createOrUpdateUI(data, bundleCounts, timestamp);
              } catch (error) {
                console.error('Error processing bundle data:', error);
              }
            },
            onerror: function (error) {
              console.error('Failed to fetch bundle data:', error);
            }
          });
        }
      }

      function handleNavigation() {
        const tokenDetails = extractTokenDetails();
        const address = tokenDetails?.tokenAddress;
        if (address && address !== currentAddress) {
          currentAddress = address;
          if (refreshInterval) clearInterval(refreshInterval);
          fetchBundleData(address);
          refreshInterval = setInterval(() => fetchBundleData(address), 15000);
        } else if (!address) {
          currentAddress = null;
          if (refreshInterval) {
            clearInterval(refreshInterval);
            refreshInterval = null;
          }
        }
      }

      function observeDOMChanges() {
        const observer = new MutationObserver(() => handleNavigation());
        observer.observe(document.body, { childList: true, subtree: true });
      }

      observeDOMChanges();
      handleNavigation();
    })();
  }

  /****************************************************************
   *    Feature #4: Photon PumpFun Dupe Checker
   ****************************************************************/
  function initPumpFunDupeChecker() {
    if (!window.location.href.includes('/en/lp')) return;
    (function () {
      const API_URL = 'https://frontend-api-v3.pump.fun/coins?offset=0&limit=50&sort=market_cap&includeNsfw=false&order=DESC&searchTerm=';
      let fetchInterval = null;
      let currentTokenName = null;
      let currentTokenAddress = null;

      function getFormattedTimestamp() {
        return new Date().toLocaleTimeString();
      }

      function formatTimestamp(timestamp) {
        const secondsAgo = Math.floor(Date.now() / 1000) - Math.floor(timestamp / 1000);
        if (secondsAgo < 60) return `${secondsAgo}s ago`;
        const minutesAgo = Math.floor(secondsAgo / 60);
        if (minutesAgo < 60) return `${minutesAgo}m ago`;
        const hoursAgo = Math.floor(minutesAgo / 60);
        if (hoursAgo < 24) return `${hoursAgo}h ago`;
        return `${Math.floor(hoursAgo / 24)}d ago`;
      }

      function fetchSecondData(searchTerm, tokenAddress) {
        GM_xmlhttpRequest({
          method: 'GET',
          url: `${API_URL}${encodeURIComponent(searchTerm)}`,
          onload: function (response) {
            try {
              const coins = JSON.parse(response.responseText);
              if (coins && coins.length > 0) {
                const timestamp = getFormattedTimestamp();
                createDupeCheckUI(coins, timestamp, tokenAddress);
              } else {
                logDebug('No tokens found in the PumpFun API response.');
              }
            } catch (error) {
              console.error('Failed to parse PumpFun API response:', error);
            }
          },
          onerror: function (error) {
            console.error('PumpFun API request failed:', error);
          },
        });
      }

      function createDupeCheckUI(data, timestamp, tokenAddress) {
        const fadedFeedDiv = document.querySelector('#faded-feed');
        let viewer = document.getElementById('pumpfun-token-viewer');
        if (!viewer) {
          viewer = document.createElement('div');
          viewer.id = 'pumpfun-token-viewer';
          viewer.className = 'p-show__widget p-show__pair u-py-s-lg';
          fadedFeedDiv.appendChild(viewer);
        }

        const counts = data.reduce((acc, item) => {
          acc[item.symbol] = (acc[item.symbol] || 0) + 1;
          return acc;
        }, {});
        const duplicates = Object.values(counts).filter(count => count > 1).length;

        const summary = `
          <div class="u-d-flex u-flex-lg-column u-align-items-start">
            <div class="l-row u-justify-content-between">
              <div class="l-col-auto">
                <div class="c-info__cell u-font-size-zh-3xs">
                    Last Updated
                    <div class="c-info__cell__value">${timestamp}</div>
                </div>
              </div>
              <div class="l-col-auto">
                <div class="c-info__cell u-font-size-zh-3xs">
                    Tokens
                    <div class="c-info__cell__value">${data.length}</div>
                </div>
              </div>
              <div class="l-col-auto">
                <div class="c-info__cell u-font-size-zh-3xs">
                    Dupes
                    <div class="c-info__cell__value">${duplicates}</div>
                </div>
              </div>
            </div>
          </div>
          <hr class="u-mb-xs-lg u-mb-xs u-mt-xs-lg u-mt-xxs"></hr>
        `;

        viewer.innerHTML =
          summary +
          data
            .map((item) => {
              const color = item.mint === tokenAddress ? '#4caf50' : '#ffffff';
              const shortMint = item.mint.substring(0, 7);
              return `
                <div class="token-item p-show__widget__list__item">
                  <strong>${item.name}</strong> (${item.symbol})<br>
                  <a href="https://photon-sol.tinyastro.io/en/lp/${item.mint}"
                     target="_blank" style="color: ${color};">
                    <span style="word-wrap: break-word;">${shortMint}</span>
                  </a><br>
                  $${Math.round(item.usd_market_cap).toLocaleString()}<br>
                  ${formatTimestamp(item.created_timestamp)}<br>
                </div>`;
            })
            .join('');

        GM_addStyle(`
          #pumpfun-token-viewer {
            color: white;
            font-family: Arial, sans-serif;
            font-size: 12px;
            line-height: 1.5;
            padding: 15px;
            border-radius: 10px;
            overflow-y: auto;
            z-index: 10002;
            max-height: 600px;
          }
          .token-item {
            margin-bottom: 10px;
          }
          .token-item a {
            text-decoration: none;
          }
          .token-item a:hover {
            text-decoration: underline;
          }
        `);
      }

      function observePageChanges() {
        const observer = new MutationObserver(() => {
          const tokenDetails = extractTokenDetails();
          if (tokenDetails) {
            const { tokenName, tokenAddress } = tokenDetails;
            if (currentTokenName !== tokenName || currentTokenAddress !== tokenAddress) {
              currentTokenName = tokenName;
              currentTokenAddress = tokenAddress;
              clearInterval(fetchInterval);
              fetchSecondData(currentTokenName, currentTokenAddress);
              fetchInterval = setInterval(() => fetchSecondData(currentTokenName, currentTokenAddress), 15000);
            }
          } else {
            clearInterval(fetchInterval);
            currentTokenName = null;
            currentTokenAddress = null;
          }
        });
        observer.observe(document.body, { childList: true, subtree: true });
      }

      observePageChanges();
    })();
  }

  /****************************************************************
   *    Feature #5: X Tweet Hover Preview
   ****************************************************************/
  function initTweetHoverPreview() {
    if (!window.location.href.includes('/en/memescope')) return;

    GM_addStyle(`
      .tweet-preview {
        position: absolute;
        z-index: 9999;
      }
    `);

    let preview;

    document.body.addEventListener('mouseover', function(event) {
      let target = event.target.closest('a[href*="twitter.com"], a[href*="x.com"]');
      if (target) fetchTweet(target.href, target);
    });

    function fetchTweet(url, anchor) {
      let match = url.match(/status\/(\d+)/);
      if (!match) return;
      let tweetId = match[1];
      showPreview(anchor, tweetId);
    }

    function showPreview(anchor, tweetId) {
      if (preview) preview.remove();
      preview = document.createElement('div');
      preview.className = 'tweet-preview';
      preview.innerHTML = `<iframe src="https://platform.twitter.com/embed/Tweet.html?dnt=false&embedId=twitter-widget-0&frame=false&hideCard=false&hideThread=false&id=${tweetId}&lang=en&theme=dark&widgetsVersion=2615f7e52b7e0%3A1702314776716" style="width: 300px; height: 1000px;" frameborder="0" scrolling="no"></iframe>`;
      document.body.appendChild(preview);

      let rect = anchor.getBoundingClientRect();
      preview.style.top = `${window.scrollY + rect.bottom + 5}px`;
      preview.style.left = `${window.scrollX + rect.left}px`;

      anchor.addEventListener('mouseout', () => {
        if (preview) preview.remove();
      }, { once: true });
    }
  }

  /****************************************************************
   *    Create Faded Feed
   ****************************************************************/
  function ensureFadedFeedDiv() {
    if (!window.location.href.includes('/en/lp')) return;
    let outerWrapper = document.querySelector('.l-col-12.l-col-lg-auto#faded-feed-wrapper');
    if (!outerWrapper) {
      outerWrapper = document.createElement('div');
      outerWrapper.className = 'l-col-12 l-col-lg-auto';
      outerWrapper.id = 'faded-feed-wrapper';
      const parentRow = document.querySelector('.l-row.l-row-gap--xxs.u-flex-lg-row-reverse');
      if (parentRow) parentRow.appendChild(outerWrapper);
    }

    const footer = `
      <div class="c-w-form__footer u-mt-xs js-show__snipe__el u-font-size-zh-xxs" style="text-align: right;">
        Coded by
        <b>nocommas</b>
      </div>
    `;

    let fadedFeedDiv = outerWrapper.querySelector('#faded-feed');
    if (!fadedFeedDiv) {
      fadedFeedDiv = document.createElement('div');
      fadedFeedDiv.id = 'faded-feed';
      fadedFeedDiv.className = 'p-show__sb u-px-0-lg u-px-s';
      outerWrapper.appendChild(fadedFeedDiv);
    }

    fadedFeedDiv.insertAdjacentHTML('afterend', footer);
  }

  /****************************************************************
   *   Running only what is enabled & building the Toggle UI
   ****************************************************************/
  function runAllFeatures() {
    createTogglesUI();

    if (scriptSettings.enableBundleAnalysis || scriptSettings.enablePumpFunDupeChecker) {
      ensureFadedFeedDiv();
    }

    if (scriptSettings.enableTradingBot) {
      initTradingBotHoldersComparison();
    }

    if (scriptSettings.enableBundleAnalysis) {
      initBundleAnalysis();
    }

    if (scriptSettings.enablePumpFunDupeChecker) {
      initPumpFunDupeChecker();
    }

    if (scriptSettings.enableTweetHoverPreview) {
      initTweetHoverPreview();
    }

    if (scriptSettings.enableUrlHighlighting) {
      initUrlHighlighting();
    }
  }

  runAllFeatures();
})();