I felt that having a plain red favicon for every youtube tab is a little boring. So, I wrote a TamperMonkey Script with the new ES6 promises that replaces the favicon with the channel's logo. Link to the script here.
The script works by first extracting the channel logo's URL from the window.ytInitialData object. Then, a mutation listener is attached to the title, and whenever the title of the page changes, the script makes a GET request using the window.location.href and then extracts the logo's URL from the responseText.
Since this is my first time writing a userscript, I wanted to confirm that I am following best practices and not breaking anything on the site. Also, is there a better way of achieving the same result?
Thank you for your time.
My code:
// ==UserScript==
// @name YouTube Favicon to Channel logo
// @version 1.1
// @description Changes the YouTube Favicon to the Channel logo
// @author Kartik Soneji
// @updateURL https://openuserjs.org/meta/KartikSoneji/YouTube_Favicon_to_Channel_logo.meta.js
// @match https://www.youtube.com/*
// @exclude https://www.youtube.com/
// @exclude https://www.youtube.com/tv*
// @exclude https://www.youtube.com/embed/*
// @exclude https://www.youtube.com/live_chat*
// @grant none
// @run-at document-end
// @license MIT
// ==/UserScript==
(function(){
'use strict';
const urlRegex = /https:\/\/yt3.ggpht.com\/a[/-]{1,2}[A-Za-z\d-_]+/;
function isInIframe(){
try{
return window.self !== window.top;
}
catch(e){
return true;
}
}
if(isInIframe()) return;
console.log("Started");
roundImageToDataUrl(JSON.stringify(window.ytInitialData).match(urlRegex)[0]).then(setFavicon);
function run(){
getChannelLogo(window.location.href).then(roundImageToDataUrl).then(setFavicon);
}
async function getChannelLogo(url){
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () =>{
let u = xhr.responseText.match(urlRegex)[0];
console.log(u);
resolve(u);
}
xhr.send();
});
}
let observer = new MutationObserver(run);
function registerUrlChangeListener(){
let e = document.querySelector("title");
if(e){
//observer.observe(e, {attributes: true});
observer.observe(document.querySelector("title"), {attributes: true, characterData: true, childList: true});
return;
}
setTimeout(registerUrlChangeListener, 250);
}
registerUrlChangeListener();
async function roundImageToDataUrl(url){
return new Promise((resolve, reject) => {
if(url.src) url = url.src;
let img = document.createElement("img");
img.crossOrigin = "anonymous";
img.src = url;
img.style = "position: absolute; top: -100000px; left: -100000px;";
img.onload = function(){
let canvas = document.createElement("canvas"), g = canvas.getContext("2d");
canvas.height = img.naturalHeight;
canvas.width = img.naturalWidth;
g.beginPath();
g.arc(canvas.width/2, canvas.height/2, canvas.width/2, 0, Math.PI * 2);
g.clip();
g.drawImage(img, 0, 0);
img.parentElement.removeChild(img);
resolve(canvas.toDataURL());
}
document.body.appendChild(img);
});
}
function setFavicon(url){
let a = document.querySelectorAll("link[rel *= 'icon']");
if(a.length == 0){
let link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'icon';
document.head.appendChild(link);
a = [link];
}
for(let i of a) i.href = url;
}
})();