Injeção de Fontes em Userscripts
Dette script bør ikke installeres direkte. Det er et bibliotek, som andre scripts kan inkludere med metadirektivet // @require https://update.greasyfork.org/scripts/564164/1773244/FontLoaderBypass.js
// ==UserScript==
// @name FontLoaderBypass
// @namespace http://github.com/0H4S
// @version 1.2
// @author OHAS
// @description Injeção de Fontes em Userscripts
// @license CC-BY-NC-ND-4.0
// @copyright 2025-2026 OHAS. All Rights Reserved. (https://gist.github.com/0H4S/ae2fa82957a089576367e364cbf02438)
// ==/UserScript==
/*
Copyright Notice & Terms of Use
Copyright © 2026 OHAS. All Rights Reserved.
This software is the exclusive property of OHAS and is licensed for personal, non-commercial use only.
You may:
- Install, use, and inspect the code for learning or personal purposes.
You may NOT (without prior written permission from the author):
- Copy, redistribute, or republish this software.
- Modify, sell, or use it commercially.
- Create derivative works.
For questions, permission requests, or alternative licensing, please contact via
- GitHub: https://github.com/0H4S
- Greasy Fork: https://greasyfork.org/users/1464180
This software is provided "as is", without warranty of any kind. The author is not liable for any damages arising from its use.
*/
(function() {
'use strict';
const API = {
xhr: (typeof GM_xmlhttpRequest !== 'undefined') ? GM_xmlhttpRequest : (typeof GM !== 'undefined' ? GM.xmlHttpRequest : null),
listValues: (typeof GM_listValues !== 'undefined') ? GM_listValues : (typeof GM !== 'undefined' ? GM.listValues : null),
deleteValue: (typeof GM_deleteValue !== 'undefined') ? GM_deleteValue : null,
setValue: (typeof GM_setValue !== 'undefined') ? GM_setValue : null,
getValue: (typeof GM_getValue !== 'undefined') ? GM_getValue : null
};
const FontLoaderBypass = {
STORAGE_KEY: 'FontLoaderBypass',
OLD_PREFIX: 'flb_cache_',
_init: async function() {
if (!API.getValue || !API.setValue || !API.listValues || !API.deleteValue) return;
try {
let currentData = API.getValue(this.STORAGE_KEY);
let needsSave = false;
let storageObj = {};
if (currentData) {
if (typeof currentData === 'string') {
try {
storageObj = JSON.parse(currentData);
needsSave = true;
} catch(e) { storageObj = {}; }
} else if (typeof currentData === 'object') {
storageObj = currentData;
}
}
const allKeys = await API.listValues();
const oldCacheKeys = allKeys.filter(k => k.startsWith(this.OLD_PREFIX));
if (oldCacheKeys.length > 0) {
for (const key of oldCacheKeys) {
const url = key.replace(this.OLD_PREFIX, '');
const rawData = API.getValue(key);
let base64Data = '';
let meta = { fontName: 'Migrated Font', fontWeight: 'normal', fontStyle: 'normal' };
try {
if (typeof rawData === 'string' && rawData.startsWith('{')) {
const parsed = JSON.parse(rawData);
base64Data = parsed.content || '';
meta = parsed.meta || meta;
} else if (typeof rawData === 'string' && rawData.startsWith('data:')) {
base64Data = rawData;
}
if (base64Data) {
const id = this._generateId();
storageObj[id] = {
"font-family": meta.fontName,
"font-style": meta.fontStyle,
"font-weight": meta.fontWeight,
"unicode-range": null,
"url": url,
"base64": base64Data
};
}
} catch (e) {}
API.deleteValue(key);
}
needsSave = true;
}
if (needsSave) {API.setValue(this.STORAGE_KEY, storageObj);}
} catch (e) {}
},
_generateId: function() {
const d = new Date();
const pad = (n) => String(n).padStart(2, '0');
const ms = String(d.getMilliseconds()).padStart(3, '0');
return `${d.getFullYear()}${pad(d.getMonth()+1)}${pad(d.getDate())}${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}${ms}`;
},
load: function(url, name, weight, style, unicodeRange) {
const isCss = url.includes('fonts.googleapis.com') || url.endsWith('.css');
if (isCss) {
this._processExternalCss(url);
} else {
if (!name) return;
this.loadFontBase64(url, name, weight || 'normal', style || 'normal', unicodeRange || null);
}
},
clear: function(url) {
const isCss = url.includes('fonts.googleapis.com') || url.endsWith('.css');
if (isCss) {
this._fetch(url, 'text')
.then(cssContent => {
const fontsFound = this._parseCssContent(cssContent);
if (fontsFound.length > 0) fontsFound.forEach(f => this._deleteEntry(f.src));
}).catch(() => {});
} else {
this._deleteEntry(url);
}
},
clearAll: function() {
if (API.deleteValue) API.deleteValue(this.STORAGE_KEY);
},
_processExternalCss: function(cssUrl) {
this._fetch(cssUrl, 'text')
.then(cssContent => {
const fontsFound = this._parseCssContent(cssContent);
if (fontsFound.length === 0) return;
fontsFound.forEach(f => {
this.loadFontBase64(f.src, f.family, f.weight, f.style, f.unicodeRange);
});
}).catch(() => {});
},
_parseCssContent: function(cssText) {
const results = [];
const blockRegex = /@font-face\s*{([\s\S]*?)}/g;
let match;
while ((match = blockRegex.exec(cssText)) !== null) {
const content = match[1];
const familyMatch = content.match(/font-family:\s*['"]?([^'";]+)['"]?/);
const styleMatch = content.match(/font-style:\s*([a-zA-Z]+)/);
const displayMatch = content.match(/font-display:\s*([a-zA-Z-]+)/);
const weightMatch = content.match(/font-weight:\s*([^;]+)/);
const unicodeMatch = content.match(/unicode-range:\s*([^;]+)/);
const srcMatch = content.match(/src:\s*url\((?:'|")?([^'")]+)(?:'|")?\)/);
if (familyMatch && srcMatch) {
results.push({
family: familyMatch[1].trim(),
style: styleMatch ? styleMatch[1].trim() : 'normal',
display: displayMatch ? displayMatch[1].trim() : 'swap',
weight: weightMatch ? weightMatch[1].trim() : '400',
unicodeRange: unicodeMatch ? unicodeMatch[1].trim() : null,
src: srcMatch[1].trim()
});
}
}
return results;
},
loadFontBase64: async function(url, fontFamilyName, fontWeight = 'normal', fontStyle = 'normal', unicodeRange = null, fontDisplay = 'swap') {
let blobFont = null;
if (API.getValue) {
try {
const storageData = API.getValue(this.STORAGE_KEY);
let dataObj = (typeof storageData === 'string') ? JSON.parse(storageData) : storageData;
if (dataObj) {
const entryId = Object.keys(dataObj).find(id => dataObj[id].url === url);
if (entryId) {
const entry = dataObj[entryId];
blobFont = this._base64ToBlob(entry.base64);
if(!fontFamilyName && entry['font-family']) fontFamilyName = entry['font-family'];
if(!fontWeight && entry['font-weight']) fontWeight = entry['font-weight'];
if(!fontStyle && entry['font-style']) fontStyle = entry['font-style'];
if(!unicodeRange && entry['unicode-range']) unicodeRange = entry['unicode-range'];
}
}
} catch (e) {}
}
if (!blobFont) {
try {
const responseBlob = await this._fetch(url, 'blob');
blobFont = responseBlob;
const reader = new FileReader();
const base64Promise = new Promise((resolve) => {
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blobFont);
});
const base64Data = await base64Promise;
if (API.setValue) {this._saveToStorage(url, fontFamilyName, fontWeight, fontStyle, unicodeRange, base64Data);}
} catch (err) {return;}
}
try {
const arrayBuffer = await blobFont.arrayBuffer();
const fontOptions = {
style: fontStyle,
display: fontDisplay
};
if (fontWeight) fontOptions.weight = fontWeight;
if (unicodeRange) fontOptions.unicodeRange = unicodeRange;
const fontFace = new FontFace(fontFamilyName, arrayBuffer, fontOptions);
await fontFace.load();
document.fonts.add(fontFace);
} catch (e) {}
},
_saveToStorage: function(url, family, weight, style, unicodeRange, base64) {
try {
let storageData = {};
const raw = API.getValue(this.STORAGE_KEY);
if (raw) {
storageData = (typeof raw === 'string') ? JSON.parse(raw) : raw;
}
if (!storageData) storageData = {};
let targetId = Object.keys(storageData).find(id => storageData[id].url === url);
if (!targetId) targetId = this._generateId();
storageData[targetId] = {
"font-family": family,
"font-style": style,
"font-display": "swap",
"font-weight": weight,
"unicode-range": unicodeRange,
"url": url,
"base64": base64
};
API.setValue(this.STORAGE_KEY, storageData);
} catch(e) {}
},
_deleteEntry: function(url) {
try {
const raw = API.getValue(this.STORAGE_KEY);
if (!raw) return;
const storageData = (typeof raw === 'string') ? JSON.parse(raw) : raw;
const targetId = Object.keys(storageData).find(id => storageData[id].url === url);
if (targetId) {
delete storageData[targetId];
API.setValue(this.STORAGE_KEY, storageData);
}
} catch(e) {}
},
_base64ToBlob: function(base64) {
const parts = base64.split(',');
const mimeType = parts[0].match(/:(.*?);/)[1];
const byteString = atob(parts[1]);
const arrayBuffer = new ArrayBuffer(byteString.length);
const int8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
int8Array[i] = byteString.charCodeAt(i);
}
return new Blob([int8Array], { type: mimeType });
},
_fetch: function(url, responseType) {
return new Promise((resolve, reject) => {
if (!API.xhr) return reject();
API.xhr({
method: 'GET',
url: url,
responseType: responseType,
onload: (res) => (res.status >= 200 && res.status < 300) ? resolve(res.response) : reject(),
onerror: () => reject(),
ontimeout: () => reject()
});
});
}
};
FontLoaderBypass._init();
const exportScope = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
exportScope.FontLoaderBypass = FontLoaderBypass;
})();