Last active
November 30, 2025 03:23
-
-
Save nexpid/fc93225e03903907fbcaffcc8877ea9b to your computer and use it in GitHub Desktop.
Bypass Font Awesome Pro.user.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==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