X/Twitter t.co Stripper

replace t.co links with direct destinations and outline cards on X/Twitter

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         X/Twitter t.co Stripper
// @namespace    https://spin.rip/
// @version      1.1
// @description  replace t.co links with direct destinations and outline cards on X/Twitter
// @author       Spinfal
// @match        https://x.com/*
// @match        https://twitter.com/*
// @grant        none
// @license      gpl-3.0-or-later
// ==/UserScript==

(function() {
  'use strict';

  const processedFlag = 'tcoStripperProcessed';
  const cardFlag = 'tcoCardMarked';

  // handle t.co links
  function processLink(link) {
    if (!link || link.dataset[processedFlag]) return;
    link.dataset[processedFlag] = '1';

    const href = link.getAttribute('href');
    if (!href || !href.includes('t.co/')) return;

    const candidates = [
      link.getAttribute('data-full-url'),
      link.getAttribute('data-expanded-url'),
      link.getAttribute('aria-label'),
      link.title,
      link.textContent
    ];

    for (const raw of candidates) {
      if (!raw) continue;

      const firstToken = raw.trim().split(/\s+/)[0]?.replace('…','');

      if (/^https?:\/\//i.test(firstToken)) {
        link.href = firstToken;
        link.style.color = '#4caf50';
        break;
      }
    }
  }

  // walk node + children for t.co anchors
  function processNode(root) {
    if (!root || root.nodeType !== Node.ELEMENT_NODE) return;

    if (root.matches && root.matches('a[href*="t.co"]')) {
      processLink(root);
    }

    if (root.querySelectorAll) {
      root.querySelectorAll('a[href*="t.co"]').forEach(processLink);
    }
  }

  // outline cards
  function processCardNode(root) {
    if (!root || root.nodeType !== Node.ELEMENT_NODE) return;

    if (root.dataset && root.dataset.testid === 'card.wrapper') {
      if (!root.dataset[cardFlag]) {
        root.style.outline = '1px solid #ff705f66';
        root.dataset[cardFlag] = '1';
      }
    }

    if (root.querySelectorAll) {
      root.querySelectorAll('[data-testid="card.wrapper"]').forEach(card => {
        if (!card.dataset[cardFlag]) {
          card.style.outline = '1px solid #ff705f66';
          card.dataset[cardFlag] = '1';
        }
      });
    }
  }

  function init() {
    processNode(document.body);
    processCardNode(document.body);

    const observer = new MutationObserver(mutations => {
      for (const m of mutations) {
        if (m.type === 'childList') {
          m.addedNodes.forEach(node => {
            processNode(node);
            processCardNode(node);
          });
        } else if (
          m.type === 'attributes' &&
          m.attributeName === 'href' &&
          m.target.tagName === 'A' &&
          m.target.href.includes('t.co/')
        ) {
          processLink(m.target);
        }
      }
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['href']
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();