Twitter X Icon

Change Twitter X Icon

目前為 2023-07-30 提交的版本,檢視 最新版本

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Twitter X Icon
// @namespace   TwitterX
// @match       https://twitter.com/*
// @grant       none
// @version     0.1.2
// @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: (canvas)=>Math.round(canvas.width * 0.14),

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

    y: (canvas, 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 canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;

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

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

        // 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();

        // 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) 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="/home"][aria-label="Twitter"].my-custom-icon > div::before {

            background-image: url("${href}");

            position: absolute;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            content: '';
            color: #fff;
            display: block;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            border-radius: 50% / 50%;
        }
        a[href="/home"][aria-label="Twitter"].my-custom-icon svg::before {
            display: block;
            position: absolute;
            content: "";
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
        }


        a[href="/home"][aria-label="Twitter"].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(withPageElement) {
    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(1);



  }


  // 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('.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(0);
  }

  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.classList.add('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(1);
      }


    }
  }

  // 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 });




})();