Skip to content

Instantly share code, notes, and snippets.

@nexpid
Last active March 9, 2025 07:09
Show Gist options
  • Save nexpid/fc93225e03903907fbcaffcc8877ea9b to your computer and use it in GitHub Desktop.
Save nexpid/fc93225e03903907fbcaffcc8877ea9b to your computer and use it in GitHub Desktop.
Bypass Font Awesome Pro.user.js
// ==UserScript==
// @name Bypass Font Awesome Pro
// @namespace https://gist.github.com/nexpid/fc93225e03903907fbcaffcc8877ea9b/edit
// @updateURL https://gist.github.com/nexpid/fc93225e03903907fbcaffcc8877ea9b/raw/bypass.fontawesome.user.js
// @description Bypasses Font Awesome's Pro paywall, because it sucks.
// @author nexpid
// @icon https://fontawesome.com/favicon.ico
// @match *://fontawesome.com/*
// @run-at document-start
// @version 1.2.4
// ==/UserScript==
(() => {
const patched = Symbol("is patched");
/**
* @param {(element: "div" | "span" | "font-awesome-icon", props: {
* staticClass?: string;
* staticStyle?: object;
* attrs?: object;
* on?: { click: () => void }
* }, children: any[])} jsx
* @param {{ solid: Record<string, any> }} icons
* @param {(text: string) => any} text
* @param {{ name: string, label: string, unicode: string, family: "classic" | "duotone" | "sharp" | "sharp-doutone", style: "solid" | "regular" | "light" | "thin" }} icon
*/
window.fabypassrender = (jsx, icons, text, icon) => {
const label = "Snap HD Png";
window.HawkTuah = icons;
window.HonkTuah = icon;
return jsx("div", { staticStyle: { marginLeft: "0.2rem" } }, [
jsx(
"button",
{
staticClass:
"icon-action-download-svg flat compact display-inline-block padding-x-2xs",
staticStyle: { "--button-color": "var(--fa-navy)" },
attrs: {
type: "button",
"aria-label": label,
"data-balloon-pos": "up",
},
on: {
click: () => {
const font = {
classic: "Pro",
duotone: "Duotone",
sharp: "Sharp",
"sharp-duotone": "Sharp Duotone",
}[icon.family];
const weight = {
solid: 900,
regular: 400,
light: 300,
thin: 100,
}[icon.style];
const symbol = String.fromCharCode(parseInt(icon.unicode, 16));
const layers = icon.family.includes("duotone")
? [symbol, symbol + symbol]
: [symbol];
const canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 512;
canvas.style.position = "fixed";
canvas.style.left = 0;
canvas.style.top = 0;
canvas.style.backgroundColor = "#000";
const ctx = canvas.getContext("2d");
const color = {
"1": "#4e5058"
}[prompt("What color would you like to use?\n\n0 = White\n1 = Discord gray")] ?? "#ffffff";
ctx.textRendering = "geometricPrecision";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = color;
const maxSize = canvas.width * 0.8;
let size = 100;
const fontify = () =>
(ctx.font = `${weight} ${size}px "Font Awesome 6 ${font}", serif`);
while (size < 1000) {
size++;
fontify();
const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx.measureText(layers[0]);
const height = actualBoundingBoxAscent + actualBoundingBoxDescent;
if (width > maxSize || height > maxSize) {
console.debug("Width:", width, "Height:", height, "Size:", size, "Max size:", maxSize);
size--;
fontify();
break;
}
}
ctx.fillText(layers[0], canvas.width / 2, canvas.height / 2);
if (layers[1]) {
ctx.fillStyle = color + "66";
ctx.fillText(layers[1], canvas.width / 2, canvas.height / 2);
}
const url = canvas.toDataURL("image/png");
const a = document.createElement("a");
a.href = url;
a.download = `fa${icon.label.replace(/ +/g, "")}.png`;
a.click();
},
},
},
[
jsx("font-awesome-icon", {
attrs: {
icon: icons.solid.faCameraRetro,
"fixed-width": "",
size: "lg",
},
}),
text(" "),
jsx("span", { staticClass: "sr-only" }, [text(label)]),
],
1
),
]);
};
/** @type {{ search: string, patches: { match: RegExp, replace: string }[] }[]} */
const patches = [
{
search: 'name:"IconCodeSnippet"',
patches: [
{
match: /this\.canDownloadIcon/,
replace: "true",
},
],
},
{
search: 'name:"IconSvgDownload"',
patches: [
{
match: /this\.canDownloadIcon/,
replace: "true",
},
{
match:
/\[((\w)\("div",{stat.*?(\w\.\$.*?)\..*?(\w\._v).*?,1\)\]\))\]/,
replace: "[$1,window.fabypassrender($2,$3,$4,t.icon)]",
},
],
},
{
search: 'name:"IconNameAndLink"',
patches: [
{
match: /\w\.isProUser/,
replace: "true",
},
],
},
{
search: '"icon-customize"',
patches: [
{
match: /\w\.isProUser/g,
replace: "true",
},
],
},
];
/** @param {Record<string, Function} modules */
function patchModules(modules) {
for (const id in modules) {
if (modules[id][patched]) continue;
/** @type {string} */
const funcStr = Function.prototype.toString.apply(modules[id]);
const patc = patches.find((x) => funcStr.includes(x.search));
const footer = `\n//# sourceURL=WebpackModule${id}`;
if (patc && patc.patches && patc.patches[0]) {
try {
modules[id] = (0, eval)(
`// Webpack Module ${id} - Patched by Font Awesome Pro Bypass\n// Debug - patch group ${JSON.stringify(
patc.search
)}\n${patc.patches.reduce(
(str, patch) => str.replace(patch.match, patch.replace),
funcStr
)}${footer}`
);
console.log(
`[PATCHER] Patched ${id} with ${JSON.stringify(patc.search)}!`,
modules[id]
);
} catch (e) {
if (!confirm("A patch errored! Continue?")) location.reload();
}
}
modules[id][patched] = true;
}
}
let base = [];
Object.defineProperty(window, "webpackChunkfontawesome", {
set(value) {
base = value;
if (!value.push[patched]) {
/** @type {Function} */
const realPush = value.push;
value.push = (...args) => {
const [chunk] = args;
if (!chunk[patched]) {
chunk[patched] = true;
patchModules(chunk[1]);
}
return realPush.apply(value, args);
};
value.push[patched] = true;
}
},
get() {
return base;
},
configurable: true,
});
console.log("[Patcher] Attached!");
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment