Skip to content

Instantly share code, notes, and snippets.

@nexpid
Last active November 30, 2025 03:23
Show Gist options
  • Select an option

  • Save nexpid/fc93225e03903907fbcaffcc8877ea9b to your computer and use it in GitHub Desktop.

Select an option

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.4.3
// ==/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";
return jsx("div", {
staticClass: "icon-details-svg-dl display-inline-flex flex-items-center",
staticStyle: { "--button-margin-bottom": "0", "--button-background": "transparent", "--button-hover-background": "transparent", "--button-hover-color": "var(--fa-dk-blue)", "--button-active-color": "var(--fa-dk-blue)", "--margin-bottom": "0", "--padding-left": "0", "gap": "var(--spacing-4xs)" }
}, [
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 =
icon.family === "classic" ?
"Pro" :
icon.family.split("-").map(x => x[0].toUpperCase() + x.slice(1).toLowerCase()).join(" ");
const weight = {
solid: 900,
regular: 400,
light: 300,
thin: 100,
}[icon.style] ?? 400;
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 colors = [
{ label: "White", color: "#ffffff" },
{ label: "Black", color: "#000000" },
{ label: "Discord gray", color: "#4e5058" },
{ label: "Discord blurple", color: "#5865f2" },
{ label: "Custom", custom: true }
];
const index = prompt(`What color would you like to use?\n\n${colors.map((x, i) => `${i}. ${x.label} ${x.color ? `(${x.color})` : ""}`).join("\n")}`);
const preset = colors[index !== "" ? index : 0];
let color = preset?.color;
if (preset?.custom) {
const clr = prompt("Enter the custom #HEX color you'd like to use");
if (!clr) return;
color = clr;
}
if (!color) return;
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 7 ${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 }[], global?: boolean }[]} */
const patches = [
{
search: '"icon-svg-download"',
patches: [
{
match: /\i\.hasAccessToSvg/g,
replace: "!0"
},
{
match: /("can-use-icon"):.*?,/,
replace: "$1:()=>!0,"
},
{
match: /(((\i)\.\i)\(" "\),(\i)\("icon-svg-download".*?(\i\.icon),.*?}}\))/,
replace: "$1,window.fabypassrender($4,$3[window.faiconroot],$2,$5)"
}
]
},
{
search: '"IconPackLanding",',
patches: [
{
match: /(hasAccessTo(?:Download)?IconPack)\(\){return.*?}/g,
replace: "$1(){return!0}"
}
]
},
{
search: "$permits",
global: true,
patches: [
{
match: /\i\.$permits\.has\(.*?\)/g,
replace: "!0"
},
{
match: /\i\.$permits\.get\(.*?\)/g,
replace: "!0"
}
]
},
{
search: ".solid.fa",
global: true,
patches: [
{
match: /\i\.(\i)\.solid\.fa/,
replace: (match, iconRoot) => {
window.faiconroot = iconRoot;
return match;
}
}
]
}
];
function mkMatch(match) {
const raw = typeof match === "string" ? match : match.source;
const src = raw.replaceAll("\\i", String.raw`(?:[A-Za-z_$][\w$]*)`);
const reg = new RegExp(src, match.flags);
reg.toString = match.toString.bind(match);
return reg;
}
/** @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(mkMatch(patch.match), patch.replace),
funcStr
)}${footer}`
);
patc.used = modules[id];
if (!patc.global) console.log(
`[PATCHER] Patched ${id} with ${JSON.stringify(patc.search)}!`,
modules[id]
);
} catch (e) {
console.error(`[PATCHER] An error! Patch ${JSON.stringify(patc.search)} module`, modules[id], e);
alert("A patch errored!");
break;
}
}
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