// ==UserScript== // @name Proton Mail Unread Favicon // @namespace https://gist.github.com/kevinleedrum/d7e261c9d9b0b3281dcc75c16d69f143 // @version 0.1 // @description Updates the Proton Mail favicon to include a single-digit unread count // @author Kevin Lee Drum // @match https://mail.proton.me/u/* // @icon https://www.google.com/s2/favicons?sz=64&domain=proton.me // @grant none // ==/UserScript== const INBOX_SELECTOR = '[data-testid="navigation-link:inbox"]'; const UNREAD_SELECTOR = `${INBOX_SELECTOR} [data-unread-count]`; const BADGE_BG = "#fff"; const BADGE_FG = "#000"; const INBOX_TIMEOUT = 10; // 5 seconds let inboxChecks = 0; let defaultFavicon; (function () { let waitForInbox = setInterval(() => { const inbox = document.querySelector(INBOX_SELECTOR); if (inboxChecks > INBOX_TIMEOUT) { console.error("Proton Mail Unread Favicon: Timed out waiting for inbox"); clearInterval(waitForInbox); return; } inboxChecks++; if (!inbox) return; clearInterval(waitForInbox); getDefaultFavicon(); updateFavicon(); watchForInboxChange(); }, 500); })(); function getDefaultFavicon() { const svgFavicon = document.querySelector( 'link[rel="icon"][type="image/svg+xml"]' ); if (svgFavicon) svgFavicon.remove(); const favicon = document.querySelector('link[rel="icon"]'); defaultFavicon = favicon.href; } function watchForInboxChange() { const inbox = document.querySelector(INBOX_SELECTOR); if (!inbox) return; const observer = new MutationObserver(updateFavicon); observer.observe(inbox, { subtree: true, attributes: true, childList: true }); } function updateFavicon() { const favicon = document.querySelector('link[rel="icon"]'); const unreadCounter = document.querySelector(UNREAD_SELECTOR); let count = 0; if (unreadCounter) count = +unreadCounter.dataset.unreadCount; if (!count) { favicon.href = defaultFavicon; return; } if (count > 9) count = "*"; drawFavicon(count, favicon); } function drawFavicon(count, favicon) { const canvas = document.createElement("canvas"); canvas.width = 16; canvas.height = 16; const ctx = canvas.getContext("2d"); const img = document.createElement("img"); img.src = defaultFavicon; img.onload = () => { // Draw default favicon ctx.drawImage(img, 0, 0, 16, 16); // Add circle ctx.fillStyle = BADGE_BG; ctx.beginPath(); ctx.arc(12, 4, 5, 0, 2 * Math.PI); ctx.fill(); // Add count ctx.fillStyle = BADGE_FG; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font = "10px sans-serif"; ctx.fillText(count, 12, 5); // Update favicon href favicon.href = canvas.toDataURL("image/png"); }; }