Greasy Fork

来自缓存

Greasy Fork is available in English.

Twitter X Icon

Change Twitter X Icon

当前为 2023-07-30 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Twitter X Icon
// @namespace   TwitterX
// @match       https://twitter.com/*
// @grant       none
// @version     0.1.9
// @author      CY Fung
// @description Change Twitter X Icon
// @run-at      document-start
// @license     MIT
// ==/UserScript==


(() => {

  let mIconUrl = '';
  let linkCache = new Map();

  let waa = new WeakSet();

  let mDotUrlMap = new Map();

  const op = {
    radius: (canvasSize) => Math.round(canvasSize.width * 0.14),

    x: (canvasSize, radius) => canvasSize.width - radius * 2 + radius * 0.05,

    y: (canvasSize, radius) => 0 + radius * 2 - radius * 0.3,

  };

  function addRedDotToImage(dataUriBase64, op) {
    return new Promise((resolve, reject) => {
      // Create an image element to load the data URI
      const image = new Image();
      image.onload = () => {

        const { width, height } = image;
        const canvasSize = {
          width, height
        }

        const radius = op.radius(canvasSize);
        const dotX = op.x(canvasSize, radius);
        const dotY = op.y(canvasSize, radius);

        // Convert the canvas back to a data URI base64 string
        let revisedDataUriBase64;
        if (dataUriBase64.startsWith('data:image/svg+xml')) {
          // For SVG, create a new SVG element and add the circle element
          const svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
          svgElement.setAttribute('width', width);
          svgElement.setAttribute('height', height);

          // Create a new image element within the SVG
          const svgImageElement = document.createElementNS('http://www.w3.org/2000/svg', 'image');
          svgImageElement.setAttribute('width', width);
          svgImageElement.setAttribute('height', height);
          svgImageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', dataUriBase64);
          svgElement.appendChild(svgImageElement);

          // Create a red dot circle element
          const circleElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
          circleElement.setAttribute('cx', dotX);
          circleElement.setAttribute('cy', dotY);
          circleElement.setAttribute('r', radius);
          circleElement.setAttribute('fill', 'red');
          svgElement.appendChild(circleElement);

          if (typeof btoa !== 'function') return reject();
          try {

            // Convert the modified SVG element back to a data URI base64 string
            const serializer = new XMLSerializer();
            const svgString = serializer.serializeToString(svgElement);
            revisedDataUriBase64 = 'data:image/svg+xml;base64,' + btoa(svgString);
          } catch (e) { }
        } else {

          const canvas = document.createElement('canvas');
          canvas.width = width;
          canvas.height = height;

          const ctx = canvas.getContext('2d');
          ctx.drawImage(image, 0, 0);

          // Draw a red dot on the top right corner
          ctx.beginPath();
          ctx.arc(dotX, dotY, radius, 0, 2 * Math.PI);
          ctx.fillStyle = 'red';
          ctx.fill();
          try {
            revisedDataUriBase64 = canvas.toDataURL();
          } catch (e) { }
        }


        if (!revisedDataUriBase64) {
          return reject();
        }

        // Convert the canvas back to a data URI base64 string
        // const revisedDataUriBase64 = canvas.toDataURL();
        resolve(revisedDataUriBase64);
      };

      // Set the image source to the provided data URI
      image.src = dataUriBase64;
    });
  }


  function myLink(link, dottable) {

    if (waa.has(link)) return;
    waa.add(link);


    let hrefDtor = Object.getOwnPropertyDescriptor(link.constructor.prototype, 'href');

    if (!hrefDtor.set || !hrefDtor.get) {
      return;
    }

    const getHref = () => {
      return hrefDtor.get.call(link)
    }

    let qq = null;


    async function updateURL(hh) {


      console.log('old href', hh, link.getAttribute('has-dot') === 'true')

      let nurl = mIconUrl;

      if (nurl && hh) {

        let href = hh;
        let isDotted = link.getAttribute('has-dot') === 'true'

        if (isDotted && !nurl.startsWith('http')) {
          nurl = await addRedDotToImage(nurl, op);
        }



        if (hh !== nurl && nurl) {


          let rel = link.getAttribute('rel');
          if (rel === 'icon' || rel === 'shortcut icon') {
            const link1 = document.querySelector('link[rel="icon"]');
            const link2 = document.querySelector('link[rel="shortcut icon"]');

            link1.setAttribute('has-dot', isDotted ? 'true' : 'false');
            link2.setAttribute('has-dot', isDotted ? 'true' : 'false');

            link1.href = nurl;
            link2.href = nurl;
          } else {
            link.href = nurl;
          }



        }

      }





    }

    function ckk() {
      const hh = getHref();
      if (qq === hh) return;
      qq = hh;
      updateURL(hh);
    }


    function updateDotState(hh2) {

      if (hh2 && typeof hh2 == 'string' && hh2.startsWith('http')) {
        let href = hh2;
        let isDotted = false;

        if (mDotUrlMap.has(href)) isDotted = mDotUrlMap.get(href);
        else {

          if (href.endsWith('/twitter-pip.3.ico')) isDotted = true;
          else {

            let q = /\?[^?.:\/\\]+/.exec(href);
            q = q ? q[0] : '';

            if (q) {
              isDotted = true;
            }

          }

          mDotUrlMap.set(href, isDotted);


        }


        link.setAttribute('has-dot', isDotted ? 'true' : 'false')
      }

      Promise.resolve().then(ckk)



    }

    let hh2 = null;

    hh2 = getHref();
    updateDotState(hh2);

    Object.defineProperty(link, 'href', {
      get() {
        return hh2;
      },
      set(a) {
        if (!a || a.startsWith('http')) {
          hh2 = a;
          updateDotState(hh2);
        }
        return hrefDtor.set.call(this, a);
      }

    });



    document.addEventListener('my-twitter-icon-has-changed', (evt) => {

      if (!evt) return;
      let detail = evt.detail;

      if (!detail) return;
      let mIconUrl = detail.mIconUrl;
      if (!mIconUrl) return;


      link.href = mIconUrl;
      console.log('icon changed')

      Promise.resolve().then(ckk);



    }, true);

  }

  function mIconFn(iconUrl, rel, dottable) {



    const selector = `link[rel~="${rel}"]`;
    let link = document.querySelector(selector);
    if (!link) {

      /** @type {HTMLLinkElement} */
      link = document.createElement("link");
      link.rel = `${rel}`;
      link.href = iconUrl;
      document.head.appendChild(link);
    }

    for (const link of document.querySelectorAll(selector)) {
      if (waa.has(link)) continue;
      myLink(link, dottable);
    }


  }

  function replacePageIcon(iconUrl) {
    mIconFn(iconUrl, 'icon', 1)
  }

  function replaceAppIcon(iconUrl) {

    mIconFn(iconUrl, 'apple-touch-icon', 0);
  }


  const addCSS = (href) => {
    let p = document.querySelector('style#j8d4f');
    if (!p) {
      p = document.createElement('style');
      p.id = 'j8d4f';
      document.head.appendChild(p);
    }

    let newTextContent = `
        a[href][my-custom-icon] > div::before {

            background-image: url("${href}");
            --my-custom-icon-padding: 6px;
            position: absolute;
            left: var(--my-custom-icon-padding);
            right: var(--my-custom-icon-padding);
            top: var(--my-custom-icon-padding);
            bottom: var(--my-custom-icon-padding);
            content: '';
            color: #fff;
            display: block;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            border-radius: 46%;
        }
        a[href][my-custom-icon] svg::before {
            display: block;
            position: absolute;
            content: "";
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
        }


        a[href][my-custom-icon] svg path {
            visibility: collapse;
        }

        `;
    newTextContent = newTextContent.trim();

    if (p.textContent !== newTextContent) p.textContent = newTextContent;
  }

  let qdd = 0;

  function sendMessageIconChanged(mIconUrl) {
    document.dispatchEvent(new CustomEvent('my-twitter-icon-has-changed', { detail: { mIconUrl } }));
  }

  function changeIconFn() {
    mIconUrl = localStorage.getItem('myCustomTwitterIcon');
    if (!mIconUrl) return;

    let tid = qdd = Date.now();

    if (tid !== qdd) return;

    addCSS(mIconUrl);
    replacePageIcon(mIconUrl);
    replaceAppIcon(mIconUrl);

    sendMessageIconChanged(mIconUrl)


  }


  function onImageLoaded(dataURL) {


    // Save the data URL to localStorage with a specific key
    localStorage.setItem('myCustomTwitterIcon', dataURL);
    console.log('myCustomTwitterIcon - done');
    changeIconFn();



  }


  // Function to handle the image drop event
  function handleDrop(event) {
    if (!event) return;

    if (!(event.target instanceof HTMLElement)) return;

    event.preventDefault();
    // Check if the target element is the desired anchor with href="/home"
    const targetElement = event.target.closest('a[href][my-custom-icon]');
    if (!targetElement) return;

    // Get the dropped file (assuming only one file is dropped)
    const file = event.dataTransfer.files[0];

    // Check if the dropped file is an image
    if (!file || !file.type.startsWith('image/')) return;

    linkCache.clear();

    // Read the image file and convert to base64 data URL
    let reader = new FileReader();
    reader.onload = function () {
      Promise.resolve(reader.result).then(onImageLoaded);
      reader = null;
    };
    reader.readAsDataURL(file);
  }

  // Function to handle the dragover event and allow dropping
  function handleDragOver(event) {
    event.preventDefault();
  }


  if (localStorage.getItem('myCustomTwitterIcon')) {

    changeIconFn();
  }

  let observer = null;

  // Function to check if the target element is available and hook the drag and drop functionality
  function hookDragAndDrop() {
    const targetElement = document.querySelector('a[href="/home"][aria-label="Twitter"]');
    if (targetElement && observer) {
      targetElement.setAttribute('my-custom-icon', '');
      targetElement.addEventListener('dragover', handleDragOver);
      targetElement.addEventListener('drop', handleDrop);
      console.log('Drag and drop functionality hooked.');

      document.head.appendChild(document.createElement('style')).textContent = `
           a[href="/home"][aria-label="Twitter"][my-custom-icon] * {
                pointer-events: none;
           }
     `;


      observer.takeRecords();
      // Stop and disconnect the observer since the targetElement is found
      observer.disconnect();
      observer = null;

      if (localStorage.getItem('myCustomTwitterIcon')) {

        changeIconFn();
      }


    }
  }

  // Use MutationObserver to observe changes in the document
  observer = new MutationObserver(function (mutationsList, observer) {
    let p = false;
    for (const mutation of mutationsList) {
      if (mutation.type === 'childList' || mutation.type === 'subtree') {
        p = true;

      }
    }
    if (p) hookDragAndDrop();
  });

  // Start observing the entire document
  observer.observe(document, { childList: true, subtree: true });

  document.addEventListener('change-my-twitter-icon', () => {
    changeIconFn();
  }, true);

})();