Twitter X Icon

Change Twitter X Icon

目前为 2023-07-30 提交的版本。查看 最新版本

// ==UserScript==
// @name        Twitter X Icon
// @namespace   TwitterX
// @match       https://twitter.com/*
// @grant       none
// @version     0.1.0
// @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"] * {
     pointer-events: none;
     }
        a[href="/home"][aria-label="Twitter"] > 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"] svg::before {
            display: block;
            position: absolute;
            content: "";
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
        }


        a[href="/home"] 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('a[href="/home"][aria-label="Twitter"]');
    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"]');
    if (targetElement && observer) {
      targetElement.addEventListener('dragover', handleDragOver);
      targetElement.addEventListener('drop', handleDrop);
      console.log('Drag and drop functionality hooked.');



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




})();