1
0
Files
Userscripts/country-flag-fixer.user.js
2025-11-11 00:58:22 +01:00

207 lines
7.4 KiB
JavaScript

// ==UserScript==
// @name Country Flag Fixer
// @namespace https://git.ponywave.de/Akamaru/Userscripts
// @version 1.2
// @description Ersetzt Ländercodes mit echten Flaggen-Emojis unter Windows/Chromium
// @author Akamaru
// @match *://*/*
// @icon https://www.google.com/s2/favicons?domain=git.ponywave.de&sz=32
// @grant GM_getResourceURL
// @resource TwemojiCountryFlags https://cdn.jsdelivr.net/npm/country-flag-emoji-polyfill@0.1.8/dist/TwemojiCountryFlags.woff2
// @updateURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/country-flag-fixer.user.js
// @downloadURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/country-flag-fixer.user.js
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const replacementFontName = "Twemoji Country Flags";
const extentionStyleTagId = "country-flag-fixer-ext";
const headStyleTagId = "country-flag-fixer-ext-head";
const loadFontFace = () => {
const fontUrl = GM_getResourceURL('TwemojiCountryFlags');
const style = document.createElement("style");
style.setAttribute("type", "text/css");
style.setAttribute("id", headStyleTagId);
style.textContent = `
@font-face {
font-family: "${replacementFontName}";
font-style: normal;
src: url('${fontUrl}') format('woff2');
unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;
}
@font-face {
font-family: "${replacementFontName}";
font-style: italic;
src: url('${fontUrl}') format('woff2');
unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;
}
`;
if (document.head != undefined) {
document.head.appendChild(style);
}
};
const extractFontFamilyRules = () => {
const fontFamilyRules = [];
for (const sheet of document.styleSheets) {
if (sheet.ownerNode?.id == extentionStyleTagId || sheet.ownerNode?.id == headStyleTagId)
continue;
const sheetMediaBlacklist = ['print', 'speech', 'aural', 'braille', 'handheld', 'projection', 'tty'];
if (sheetMediaBlacklist.includes(sheet.media.mediaText))
continue;
try {
for (const rule of sheet.cssRules) {
if (!rule.style || !rule.style?.fontFamily)
continue;
const selectorText = rule.selectorText;
const fontFamily = rule.style.fontFamily;
if (fontFamily == 'inherit')
continue;
if (fontFamily.toLowerCase().includes(replacementFontName.toLowerCase()))
continue;
fontFamilyRules.push({ selectorText, fontFamily });
}
}
catch (e) {
}
}
return fontFamilyRules;
};
const createNewStyleTag = (fontFamilyRules) => {
const style = document.createElement("style");
style.setAttribute("type", "text/css");
style.setAttribute("id", extentionStyleTagId);
fontFamilyRules.forEach((rule) => {
style.textContent += `${rule.selectorText} { font-family: '${replacementFontName}', ${rule.fontFamily} !important; }\n`;
});
return style;
};
const applyCustomFontStyles = () => {
const existingSheet = document.getElementById(extentionStyleTagId);
const fontFamilyRules = extractFontFamilyRules();
const newStyleTag = createNewStyleTag(fontFamilyRules);
if (existingSheet) {
existingSheet.parentNode.removeChild(existingSheet);
}
if (document.head == null)
return;
document.head.appendChild(newStyleTag);
};
const preserveCustomFonts = (element) => {
if (element == undefined)
return;
const inlineStyle = element.getAttribute('style');
if (!inlineStyle || !inlineStyle.includes('font-family'))
return;
const fontFamilyRegex = /font-family\s*:\s*([^;]+?)(\s*!important)?\s*(;|$)/;
const match = fontFamilyRegex.exec(inlineStyle);
if (!match)
return;
const hasIsImportant = match[2] && match[2].includes('!important');
if (hasIsImportant)
return;
const currentFontFamily = match[1].trim();
element.style.setProperty('font-family', currentFontFamily, 'important');
};
const processInitialElements = () => {
document.querySelectorAll('[style*="font-family"]').forEach(preserveCustomFonts);
};
const init = () => {
loadFontFace();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
applyCustomFontStyles();
processInitialElements();
});
} else {
applyCustomFontStyles();
processInitialElements();
}
let lastStyleSheets = new Set(Array.from(document.styleSheets).map(sheet => sheet.href || sheet.ownerNode?.textContent));
const observer = new MutationObserver((mutations) => {
let stylesheetChanged = false;
const elementsToProcess = new Set();
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.id === extentionStyleTagId || node.id === headStyleTagId)
return;
const isStylesheet = node.nodeName === 'LINK' && node.rel === 'stylesheet';
const isStyleNode = node.nodeName === 'STYLE';
if (isStylesheet || isStyleNode) {
const newStylesheetIdentifier = isStylesheet ? node.href : node.textContent;
if (!lastStyleSheets.has(newStylesheetIdentifier)) {
stylesheetChanged = true;
lastStyleSheets.add(newStylesheetIdentifier);
}
return;
}
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute?.('style') && node.getAttribute('style').includes('font-family')) {
elementsToProcess.add(node);
}
node.querySelectorAll?.('[style*="font-family"]').forEach(el => elementsToProcess.add(el));
}
});
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
const target = mutation.target;
if (target.hasAttribute('style') && target.getAttribute('style').includes('font-family')) {
elementsToProcess.add(target);
}
}
});
if (stylesheetChanged) {
applyCustomFontStyles();
}
elementsToProcess.forEach(preserveCustomFonts);
});
observer.observe(document, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style']
});
};
init();
})();