Продолжение поста «Добавил иконки в Gmail почту! Красота =)»1
Напомню. Я сделал расширение, которое добавляет иконки в Gmail почту. И в комментариях попросили сделать не расширением, а скриптом для Tampermonkey. Мне было лень, но потом я подумал почему бы и нет. Скинул код в chatGPT и он подогнал его под формат Tampermonkey. Ниже расскажу как поставить чтоб работало.
Выглядит вот так:
Как поставить?
Установить Tampermonkey если у вас его ещё нет https://chromewebstore.google.com/detail/tampermonkey/dhdgff...
Правой кнопкой по иконке расширения и Параметры
В верхнем меню панели Tampermonkey жмём на плюсик и открывается страница создания нового скрипта
Вставляем код скприта сюда и сохраняем
Готово.
Вот сам код скрипта:
// ==UserScript==
// @name Gmail Sender Favicon (base + S2, fallback letter)
// @namespace https://tampermonkey.net/
// @VERSION 2026-01-02
// @Description Gmail: show sender base-domain favicon; fallback to Google S2; then fallback to letter.
// @match https://mail.google.com/*
// @run-at document-idle
// ==/UserScript==
(() => {
'use strict';
const STYLE_ID = 'gm-sender-favicon-style-final';
const BADGE_CLASS = 'gm-sender-badge-final';
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
.${BADGE_CLASS}{
display:inline-flex;
align-items:center;
margin-right:6px;
vertical-align:middle;
flex: 0 0 auto;
transform: translateY(-0.5px);
}
.${BADGE_CLASS} img{
width:14px;height:14px;border-radius:3px;display:block;
}
.${BADGE_CLASS} .gm-letter{
font-weight:700;
font-size:12px;
line-height:1;
display:inline-block;
}
`;
document.documentElement.appendChild(style);
}
// ---------- domain/email helpers ----------
function extractEmailLike(str) {
if (!str) return '';
const s = String(str);
// <mail@domain>
const m1 = s.match(/<\s*([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})\s*>/i);
if (m1) return m1[1];
// mail@domain
const m2 = s.match(/([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})/i);
if (m2) return m2[1];
return '';
}
function getDomainFromEmail(email) {
const at = (email || '').indexOf('@');
if (at === -1) return '';
return email.slice(at + 1).trim().toLowerCase();
}
// Простая нормализация к base-domain: example.com
function baseDomain(domain) {
const parts = (domain || '').split('.').filter(Boolean);
if (parts.length < 2) return domain || '';
return parts.slice(-2).join('.');
}
// стабильный цвет по seed (чтобы буква была “привычного” цвета)
function stableColor(seed) {
let h = 0;
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) | 0;
const hue = Math.abs(h) % 360;
return `hsl(${hue}, 70%, 40%)`;
}
function pickLetter(senderText, base) {
const m = (senderText || '').match(/[a-z0-9а-яё]/i);
if (m) return m[0].toUpperCase();
if (base) return base[0].toUpperCase();
return '?';
}
// ---------- locating sender in a row ----------
function findSenderInfo(row) {
// 1) самый надежный: span[email]
const emailSpan = row.querySelector('span[email]');
if (emailSpan) {
const email = emailSpan.getAttribute('email') || '';
const senderText =
(emailSpan.getAttribute('name') || '').trim() ||
(emailSpan.getAttribute('aria-label') || '').trim() ||
(emailSpan.textContent || '').trim();
return { email, senderText, senderNode: emailSpan };
}
// 2) data-hovercard-id часто содержит email
const hcSpan = row.querySelector('span[data-hovercard-id]');
if (hcSpan) {
const hc = hcSpan.getAttribute('data-hovercard-id') || '';
const email = extractEmailLike(hc) || extractEmailLike(hcSpan.getAttribute('aria-label')) || '';
const senderText =
(hcSpan.getAttribute('name') || '').trim() ||
(hcSpan.getAttribute('aria-label') || '').trim() ||
(hcSpan.textContent || '').trim();
return { email, senderText, senderNode: hcSpan };
}
// 3) иногда sender виден как yP/zF
const nameSpan = row.querySelector('span.yP, span.zF');
if (nameSpan) {
const email =
extractEmailLike(nameSpan.getAttribute('aria-label')) ||
extractEmailLike(nameSpan.getAttribute('title')) ||
'';
const senderText =
(nameSpan.getAttribute('aria-label') || '').trim() ||
(nameSpan.getAttribute('title') || '').trim() ||
(nameSpan.textContent || '').trim();
return { email, senderText, senderNode: nameSpan };
}
return null;
}
// ---------- badge creation ----------
function makeBadge(base, letter, fontSizePx) {
const badge = document.createElement('span');
badge.className = BADGE_CLASS;
badge.setAttribute('data-base', base || '');
const img = document.createElement('img');
img.alt = '';
img.referrerPolicy = 'no-referrer';
const sources = base
? [
`https://${base}/favicon.ico`,
`https://www.google.com/s2/favicons?sz=64&domain=%24%7Ben...}`
]
: [];
let idx = 0;
const fallbackToLetter = () => {
img.remove();
const span = document.createElement('span');
span.className = 'gm-letter';
span.textContent = letter;
span.style.color = stableColor(base || letter || 'x');
if (fontSizePx) span.style.fontSize = fontSizePx;
badge.appendChild(span);
};
img.onerror = () => {
idx++;
if (idx < sources.length) {
img.src = sources[idx];
} else {
fallbackToLetter();
}
};
if (sources.length) {
img.src = sources[0];
badge.appendChild(img);
} else {
fallbackToLetter();
}
return badge;
}
// ---------- main enhancer ----------
function enhanceRow(row) {
const info = findSenderInfo(row);
if (!info) return;
const domain = getDomainFromEmail(info.email);
const base = baseDomain(domain);
// текст для буквы
const senderText = info.senderText || (info.senderNode?.textContent || '').trim();
const letter = pickLetter(senderText, base);
// куда вставлять:
// A) предпочитаем контейнер отправителя (как у тебя работало)
let target = row.querySelector('.yX.xY');
// B) если нет — рядом с узлом senderNode
let insertBeforeNode = null;
if (!target && info.senderNode && info.senderNode.parentElement) {
target = info.senderNode.parentElement;
insertBeforeNode = info.senderNode;
}
if (!target) return;
// уже вставлено?
const existing = target.querySelector(`:scope > .${BADGE_CLASS}`);
if (existing) {
const exBase = existing.getAttribute('data-base') || '';
if (exBase === (base || '')) return;
existing.remove();
}
const cs = window.getComputedStyle(target);
const fontSizePx = cs.fontSize ? `calc(${cs.fontSize} - 1px)` : '';
const badge = makeBadge(base, letter, fontSizePx);
if (insertBeforeNode) target.insertBefore(badge, insertBeforeNode);
else target.prepend(badge);
}
function enhanceAll() {
injectStyles();
document.querySelectorAll('.zA').forEach(enhanceRow);
}
// ---------- observer (throttled) ----------
let rafPending = false;
function schedule() {
if (rafPending) return;
rafPending = true;
requestAnimationFrame(() => {
rafPending = false;
enhanceAll();
});
}
const obs = new MutationObserver(schedule);
obs.observe(document.body, { childList: true, subtree: true });
enhanceAll();
})();




