Twitter X Icon

自定义 Twitter 图标,展现独特风格。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// @description:ja              Twitterアイコンをカスタマイズして個性を表現しましょう。
// @description:zh-TW           自訂 Twitter 圖示,展現獨特風格。
// @description:zh-CN           自定义 Twitter 图标,展现独特风格。

// @description:ko              트위터 아이콘을 원하는 이미지로 변경하여 개성을 표현하세요.
// @description:ru              Замените иконку Twitter на свое изображение и выразите свою индивидуальность.
// @description:af              Vervang die Twitter-ikoon met 'n persoonlike prent om jou individualiteit uit te druk.
// @description:az              Twitter nişanını şəxsi şəkil ilə dəyişdirin və özünüzü ifadə edin.
// @description:id              Ganti ikon Twitter dengan gambar pilihan Anda dan tunjukkan gaya pribadi.
// @description:ms              Tukar ikon Twitter dengan imej pilihan anda dan tunjukkan gaya peribadi.
// @description:bs              Zamijenite Twitter ikonu sa odabranom slikom i izrazite svoj stil.
// @description:ca              Canvieu la icona de Twitter amb una imatge personalitzada i mostreu el vostre estil.
// @description:cs              Změňte ikonu Twitteru na vlastní obrázek a vyjádřete svůj osobní styl.
// @description:da              Skift Twitter-ikonet med et personligt billede og vis din stil.
// @description:de              Ersetzen Sie das Twitter-Icon durch ein persönliches Bild und zeigen Sie Ihren Stil.
// @description:et              Asendage Twitteri ikoon isikliku pildiga ja näidake oma stiili.
// @description:es              Cambia el ícono de Twitter por una imagen personalizada y muestra tu estilo.
// @description:eu              Aldatu Twitter ikurria zure irudi pertsonal batekin eta erakutsi zure estiloa.
// @description:fr              Remplacez l'icône Twitter par une image personnalisée et montrez votre style.
// @description:gl              Cambia o icona de Twitter cunha imaxe personalizada e mostra o teu estilo.
// @description:hr              Zamijenite Twitter ikonu osobnom slikom i pokažite svoj stil.
// @description:zu              Thayela uhlobo lweTwitter ngezithombe ozokhetha ukuze uphazamise izibonelo zakho.
// @description:is              Skiptu út Twitter tákn með persónulegu myndi og sýndu stílinn þinn.
// @description:it              Sostituisci l'icona di Twitter con un'immagine personalizzata e mostra il tuo stile.
// @description:sw              Badilisha ishara ya Twitter na picha yako ya kibinafsi na onyesha mtindo wako.
// @description:lv              Mainiet Twitter ikonu ar personīgu attēlu un parādiet savu stilu.
// @description:lt              Pakeiskite „Twitter“ piktogramą asmeniniu vaizdu ir parodykite savo stilų.
// @description:hu              Cserélje le a Twitter ikont egyéni képre, és mutassa meg stílusát.
// @description:nl              Vervang het Twitter-pictogram door een aangepaste afbeelding en toon uw stijl.
// @description:uz              Twitter niqobini o'zgartiring va shaxsiy tasvir bilan o'zingizni ifodalang.
// @description:pl              Zmień ikonę Twittera na wybrany obraz i pokaż swój styl.
// @description:pt              Substitua o ícone do Twitter por uma imagem personalizada e mostre o seu estilo.
// @description:pt-BR           Substitua o ícone do Twitter por uma imagem personalizada e mostre o seu estilo.
// @description:ro              Înlocuiți iconița Twitter cu o imagine personalizată și arătați-vă stilul.
// @description:sq              Zëvendësoni ikonën e Twitter me një imazh të personalizuar dhe tregoni stilin tuaj.
// @description:sk              Nahraďte ikonu Twitteru vlastným obrázkom a ukážte svoj štýl.
// @description:sl              Zamenjajte ikono Twitter z izbrano sliko in izrazite svoj slog.
// @description:sr              Zamenite Twitter ikonu ličnom slikom i pokažite svoj stil.
// @description:fi              Vaihda Twitter-kuvake omalla kuvalla ja näytä oma tyyli.
// @description:sv              Byt ut Twitter-ikonen med en anpassad bild och visa din stil.
// @description:vi              Thay đổi biểu tượng Twitter bằng hình ảnh tùy chỉnh và thể hiện phong cách của bạn.
// @description:tr              Twitter simgesini istediğiniz bir görüntüyle değiştirin ve tarzınızı gösterin.
// @description:be              Замяніце іконку Twitter на свой малюнак і выразіце свой стыль.
// @description:bg              Заменете иконата на Twitter с изображение по ваш избор и покажете своя стил.
// @description:ky              Twitter иконкасын карата муркунун издесеңиз жана стилиңизди көрсөтүңүз.
// @description:kk              Twitter белгісін таңдап жатқан суретпен ауыстырып, стильіңізді көрсетіңіз.
// @description:mk              Заменете ја иконата на Twitter со слика по ваш избор и прикажете го вашиот стил.
// @description:mn              Twitter хэрэглэгчийн дүрсийг өөрчил, таны ихэнх стилийг харуулна уу.
// @description:uk              Замініть іконку Twitter на свій малюнок і виразіть свій стиль.
// @description:el              Αντικαταστήστε το εικονίδιο του Twitter με εικόνα της επιλογής σας και εμφανίστε το στυλ σας.
// @description:hy              Փոխարինեք Twitter-ի պատկերն անձնանշանով և ցուցադրեք ձեր ոլորտը:
// @description:ur              ٹوئٹر آئیکن کو آپ کی منتخب تصویر سے تبدیل کریں اور اپنی شہرت کو ظاہر کریں۔
// @description:ar              قم بتغيير أيقونة Twitter إلى صورة اختيارك وعرض أسلوبك الشخصي.
// @description:fa              آیکن Twitter را با تصویر انتخابی تغییر داده و سبک خود را نشان دهید.
// @description:ne              आफ्नो छवि छान्ने गरी ट्विटर चिन्ह परिवर्तन गर्नुहोस् र आफ्नो शैली प्रदर्शन गर्नुहोस्।
// @description:mr              Twitter चिन्हाची प्रतिनिधित्व करण्यासाठी आपली पसंतीची चित्रे वापरा.
// @description:hi              ट्विटर आइकन को अपनी पसंदीदा तस्वीर से बदलें और अपनी शैली दिखाएं।
// @description:as              আপোনাৰ বৰ্ণনাৰ সৈতে Twitter চিনত পৰিষ্কৰণ কৰক।
// @description:bn              আপনার পছন্দের ছবি দিয়ে Twitter চিহ্নিকা পরিবর্তন করুন এবং আপনার শৈলী প্রদর্শন করুন।
// @description:pa              ਆਪਣੀ ਚੋਣ ਦੀ ਤਸਵੀਰ ਨਾਲ Twitter ਚਿੰਨਕ ਨੂੰ ਬਦਲੋ ਅਤੇ ਆਪਣੀ ਸ਼ੈਲੀ ਦਿਖਾਓ।
// @description:gu              આપની પસંદની ચિત્રો સાથે Twitter ચિન્હ બદલો અને તમારી શૈલી બતાવો.
// @description:or              ଆପଣଙ୍କ ପସନ୍ଦର ଚିତ୍ରରେ Twitter ପ୍ରତିକା ବଦଳାନ୍ତୁ ଏବଂ ଆପଣଙ୍କ ସ୍ଟାଇଲ ଦର୍ଶନ ଦିଅନ୍ତୁ।
// @description:ta              உங்கள் ஆர்வம் உள்ள படத்தைப் பயன்படுத்தி Twitter ஐகானை மாற்றவும் உங்கள் பார்வையைக் காட்டுங்கள்.
// @description:te              మీ ఇష్టమైన చిత్రంతో Twitter గురించిన చిహ్నాన్ని మార్చండి మరియు మీ శైలిని చూపించండి.
// @description:kn              ನಿಮ್ಮ ಆಸಕ್ತಿಗೆ ಅನುಗುನವಾಗಿ Twitter ಚಿಹ್ನೆಯನ್ನು ಬದಲಾಯಿಸಿ ಮತ್ತು ನಿಮ್ಮ ಶೈಲಿಯನ್ನು ತೋರಿಸಿ.
// @description:ml              നിങ്ങളുടെ ഇഷ്ട ചിത്രം ഉപയോഗിച്ച് Twitter ചിഹ്നം മാറ്റുകയും നിങ്ങളുടെ ശൈലി കാണിക്കുകയും ചെയ്യുക.
// @description:si              ඔබේ ආදරයෙන් Twitter ලකුණ වෙනස් කිරීමේදී ඔබේ ප්‍රධාන වෙළඳ ආකර්ෂණය පෙන්වන්ද සඳහා ඉඩදීම.
// @description:th              เปลี่ยนไอคอน Twitter ด้วยภาพที่คุณเลือกและแสดงสไตล์ของคุณ
// @description:lo              ປ່ຽນເວັບໄຊ Twitter ໂດຍຮູບພາບທີ່ເຈົ້າເລືອກແລະສະແດງສະຖານທີ່ຂ້ອຍ
// @description:my              Twitter အိုင်ကွန်းရွေးကို သင်ရွေးသောပုံမျှ နှင့်သင်၏အပြင်ကိုပြပေးပါ။
// @description:ka              Twitter ხატულა შეიცვალეთ თქვენი რასიელი სურათით და გამოაჩინეთ თქვენი სტილი.
// @description:am              የጥንቃቄዎን ምስል በ Twitter አይኮን መቀየር እና ስብስቦችዎን ተማሪዎች እንዴት ያቀየረው እንደሚፈልግ ትስጋላችሁ።
// @description:km              ប្តូររូបក្នុង Twitter ជារូបសម្រាប់អ្នកដែលអ្នកស្រលាញ់យ៉ាងមាននងពីរបៀបផ្សេងៗ

// ==/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"]');

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

            if(link1) link1.href = nurl;
            if(link2) 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"], a[href="/home"][aria-label="X"]');
    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] *,
           a[href="/home"][aria-label="X"][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);

})();