Skip to content

Instantly share code, notes, and snippets.

@Subtixx
Last active April 16, 2025 18:11
Show Gist options
  • Save Subtixx/47f6db9ceabf51c3f689db1594a50b37 to your computer and use it in GitHub Desktop.
Save Subtixx/47f6db9ceabf51c3f689db1594a50b37 to your computer and use it in GitHub Desktop.
Little Script to add all free items from fab into account
// ==UserScript==
// @name FAB Free Asset Getter
// @namespace Violentmonkey Scripts
// @copyright 2024, subtixx (https://openuserjs.org/users/subtixx)
// @match https://www.fab.com/channels/*
// @match https://www.fab.com/de/channels/*
// @match https://www.fab.com/search*
// @match https://www.fab.com/de/search*
// @grant none
// @license AGPL-3.0-or-later
// @version 1.0
// @author Dominic Hock <[email protected]>
// @description A script to get all free assets from the FAB marketplace
// @downloadURL https://update.greasyfork.org/scripts/518732/FAB%20Free%20Asset%20Getter.user.js
// @updateURL https://update.greasyfork.org/scripts/518732/FAB%20Free%20Asset%20Getter.meta.js
// ==/UserScript==
(function () {
`use strict`;
var added = false;
var notificationQueueContainer = null;
var assetProgressbar = null;
var innerAssetsProgressbar = null;
var assetStatus = null;
const resultGridID = ".oeSuy4_9";
// Function to show toast
function showToast(message, type = 'success', onfinish) {
const toast = document.createElement('div');
toast.textContent = message;
//toast.style.position = 'fixed';
//toast.style.bottom = '20px';
//toast.style.right = '20px';
toast.style.margin = "5px 0 5px 0";
toast.style.padding = '15px';
toast.style.backgroundColor = type === 'success' ? '#28a745' : '#dc3545'; // Green for success, red for error
toast.style.color = 'white';
toast.style.borderRadius = '5px';
toast.style.zIndex = '10000';
toast.style.fontFamily = 'Arial, sans-serif';
toast.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.5s ease';
// Append to body
notificationQueueContainer.appendChild(toast);
// Fade in
setTimeout(() => {
toast.style.opacity = '1';
}, 100);
// Auto-remove after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(toast);
if(onfinish !== null && onfinish !== undefined)
{
onfinish();
}
}, 500);
}, 3000);
}
function getCSRFToken() {
// Get from fab_csrftoken cookie
let cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.startsWith("fab_csrftoken=")) {
return cookie.split("=")[1];
}
}
return "";
}
async function getAcquiredIds(listings) {
assetStatus.innerText = "Requesting which items you own!";
console.log("Getting acquired ids");
// max listings is 24 so just cut
if (listings.length > 24) {
showToast("More than 24 listings requested. Not possible!", "error");
console.error("Too many listings");
return [];
}
let filteredListings = listings.filter(listing => !listing.isOwned);
if(filteredListings.length === 0)
{
showToast("No listings to check!");
return listings;
}
// Convert uid array to listing_ids=X&listing_ids=Y&listing_ids=Z
let ids = filteredListings
.map(listing => listing.id)
.join("&listing_ids=");
//[{"uid":"5059af80-527f-4dda-8e75-7dde4dfcdf81","acquired":true,"rating":null}]
let result = await fetch("https://www.fab.com/i/users/me/listings-states?listing_ids=" + ids, {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en",
"X-Requested-With": "XMLHttpRequest",
"X-CsrfToken": getCSRFToken(),
"Sec-GPC": "1",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin"
},
"referrer": "https://www.fab.com/channels/unreal-engine?is_free=1&sort_by=-createdAt&is_ai_generated=0",
"method": "GET",
"mode": "cors"
});
let json = await result.json();
let acquired = [];
for (let i = 0; i < json.length; i++) {
if (json[i].acquired) {
acquired.push(json[i].uid);
}
}
let alreadyAcquired = listings.filter(listing => listing.isOwned).length;
console.log("Acquired " + acquired.length + " of " + listings.length + " listings (" + alreadyAcquired + " already acquired were skipped)");
return acquired;
}
async function getIds() {
let resultGrid = document.querySelector(resultGridID);
if(!resultGrid) {
showToast("Failed to find results!", "error");
return;
}
let foundItems = resultGrid.querySelectorAll(".fabkit-Stack-root.d6kADL5Y.Bf_zHIaU");
if(!foundItems || foundItems.length === 0){
showToast("No items found? Check console!", "error");
console.error(resultGrid);
return;
}
let currentListings = [];
for (let i = 0; i < foundItems.length; i++) {
let root = foundItems[i];
let nameContainer = root.querySelector("a > div.fabkit-Typography-ellipsisWrapper");
if(!nameContainer) {
console.error(root);
showToast("Failed to get name for item. Check Console!", "error");
continue;
}
let name = nameContainer.innerText;
let url = root.querySelector("a").href;
let isOwned = root.querySelector("div > i.fabkit-Icon--intent-success") !== null;
if (url === undefined) {
console.error(url, root);
showToast("Failed to get url. Please check console!", "error");
return;
}
// Extract id
let id = url.split("/").pop();
if(!id){
showToast("Can't get id? Please check console!");
console.error(id);
return;
}
console.log(id, name, isOwned, url);
currentListings.push({
isOwned: isOwned,
name: name,
id: id
});
}
assetStatus.style.display = "block";
let acquired = [];
console.log("Need to check " + currentListings.length + " listings");
assetStatus.innerText = "Need to check " + currentListings.length + " listings";
if (currentListings.length > 24) {
showToast("Too many listings, splitting into 24 chunks!");
console.log("Too many listings, splitting into 24 chunks");
// Slice, request, join, until we are finished
for (let i = 0; i < currentListings.length; i += 24) {
let partial = await getAcquiredIds(currentListings.slice(i, i + 24));
acquired = acquired.concat(partial);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
else {
acquired = await getAcquiredIds(currentListings);
}
await new Promise(resolve => setTimeout(resolve, 1000));
assetProgressbar.style.display = "block";
// [{id:"",offerId:""}]
let offers = [];
for (let i = 0; i < currentListings.length; i++) {
assetStatus.innerText = "Checking " + currentListings[i].name + " (" + currentListings[i].id + ")";
innerAssetsProgressbar.style.width = (i / currentListings.length * 100) + "%";
let currentListing = currentListings[i];
if (acquired.includes(currentListing.id) || currentListing.isOwned) {
console.log(currentListing.name + " (" + currentListing.id + ") already acquired");
showToast("You already own " + currentListing.name + " (" + currentListing.id + ")");
continue;
}
let result = await fetch("https://www.fab.com/i/listings/" + currentListing.id, {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en",
"X-Requested-With": "XMLHttpRequest",
"X-CsrfToken": getCSRFToken(),
"Sec-GPC": "1",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Priority": "u=0"
},
"referrer": "https://www.fab.com/listings/" + currentListing.id,
"method": "GET",
"mode": "cors"
});
// licenses -> foreach -> get where price 0 -> buy
let json = await result.json();
let listingOffers = [];
for (let j = 0; j < json.licenses.length; j++) {
let license = json.licenses[j];
if (license.priceTier.price != 0) {
continue;
}
offers.push({
name: currentListing.name,
id: currentListing.id,
offerId: license.offerId
});
listingOffers.push(license.offerId);
console.log("Found free offer for " + currentListing.name + " (" + currentListing.id + ")");
}
if (listingOffers.length == 0) {
console.log("No free offers found for " + currentListing.name + " (" + currentListing.id + ")");
}
await new Promise(resolve => setTimeout(resolve, 500));
}
for (let i = 0; i < offers.length; i++) {
console.log("Trying to add " + offers[i].name + " (" + offers[i].id + ")");
let result = await fetch("https://www.fab.com/i/listings/" + offers[i].id + "/add-to-library", {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en",
"X-Requested-With": "XMLHttpRequest",
"X-CsrfToken": getCSRFToken(),
"Content-Type": "multipart/form-data; boundary=---------------------------4056384097365570293376228769",
"Sec-GPC": "1",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Priority": "u=0"
},
"referrer": "https://www.fab.com/listings/" + offers[i].id,
"body": "-----------------------------4056384097365570293376228769\r\nContent-Disposition: form-data; name=\"offer_id\"\r\n\r\n" + offers[i].offerId + "\r\n-----------------------------4056384097365570293376228769\r\n-----------------------------4056384097365570293376228769--\r\n",
"method": "POST",
"mode": "cors"
});
// check for 200
if (result.status == 200 || result.status == 201 || result.status == 202 || result.status == 204) {
showToast("Added " + offers[i].name + " (" + offers[i].id + ")");
}
else {
console.log();
showToast("Failed to add " + offers[i].name + " (" + offers[i].id + ")", "error");
}
console.log("Progress: " + (i + 1) + "/" + offers.length + " (" + ((i + 1) / offers.length * 100).toFixed(2) + "%)");
await new Promise(resolve => setTimeout(resolve, 500));
}
return foundItems[foundItems.length - 1];
}
async function getAll() {
let last;
last = await getIds();
for (let i = 0; i < 64; i++) {
// Scroll to last item and wait for 5 seconds
last.scrollIntoView();
showToast("Scrolling...");
await new Promise(resolve => setTimeout(resolve, 5000));
showToast("Refreshing...");
last = await getIds();
showToast("Done!");
}
}
function getSortContainer() {
return document.querySelector(`div.odQtzXCJ > ul._oqSjPnA`);
}
function addControls() {
notificationQueueContainer = document.createElement("div");
notificationQueueContainer.style.position = 'fixed';
notificationQueueContainer.style.bottom = '20px';
notificationQueueContainer.style.right = '20px';
document.body.appendChild(notificationQueueContainer);
var getAssetsButton = document.createElement("button");
getAssetsButton.className = "fabkit-Button-root fabkit-Button--sm fabkit-Button--menu";
getAssetsButton.type = "button";
getAssetsButton.innerHTML = `<span class="fabkit-Button-label">Add Free Assets</span>`;
getAssetsButton.addEventListener(`click`, function () {
getAll();
});
assetProgressbar = document.createElement("div");
assetProgressbar.style.width = "100%";
assetProgressbar.style.height = "32px";
assetProgressbar.style.background = "#1C1C20";
assetProgressbar.style.margin = "0 0 15px 0";
assetProgressbar.style.display = "none";
innerAssetsProgressbar = document.createElement("div");
innerAssetsProgressbar.style.width = "0";
innerAssetsProgressbar.style.height = "32px";
innerAssetsProgressbar.style.background = "#45C761";
innerAssetsProgressbar.style.color = "1C1C20";
innerAssetsProgressbar.style.weight = "bold";
innerAssetsProgressbar.style.padding = "6px";
assetProgressbar.appendChild(innerAssetsProgressbar);
//<div style="width: 100%;background: #1C1C20;height: 32px;"><div style="width: 50px;background: #45C761;height: 32px;padding: 6px;color: #1c1c20;font-weight: bold;">50%</div></div>
assetStatus = document.createElement("div");
assetStatus.style.font.size = "initial";
assetStatus.style.font.weight = "normal";
assetStatus.style.background = "#45C761";
assetStatus.style.color = "#1C1C20";
assetStatus.style.padding = "10px";
assetStatus.style.borderRadius = "10px";
assetStatus.style.display = "none";
//<div style="font-size: initial;font-weight: initial;background: #55FF55;border-radius: 10px;padding: 10px;color: #282A36;">Need to check 24 listings</div>
var titleContainer = document.querySelector(".ArhVH7Um");
if(!titleContainer) {
showToast("Failed to find title container!", "error");
return;
}
titleContainer.prepend(assetStatus);
titleContainer.prepend(assetProgressbar);
var sortContainer = getSortContainer();
if(!sortContainer) {
showToast("Failed to find sort container!", "error");
return;
}
sortContainer.appendChild(getAssetsButton);
added = true;
}
function onBodyChange(mut) {
if (!added) {
addControls();
}
}
var mo = new MutationObserver(onBodyChange);
mo.observe(document.body, {
childList: true,
subtree: true
});
})();
@lonewolf0708
Copy link

Got an error when running the script on fab.com
image

@lonewolf0708
Copy link

debugging the code suggests that the error is on line 51, column 22: `"Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'split')"'
image

@lonewolf0708
Copy link

lonewolf0708 commented Nov 18, 2024

More debugging points to the actual issue being that links like"https://www.fab.com/i/listings/30c310b0-8317-4e92-a9dd-42a55046da6b/add-to-library" return the following: {"detail":"Method "GET" not allowed."}

@Subtixx
Copy link
Author

Subtixx commented Nov 19, 2024

You need to be on: https://www.fab.com/channels/unreal-engine for example. For best and fast results filter for only free items.

Also it seems like epic is now blocking too fast of requests...

@Subtixx
Copy link
Author

Subtixx commented Nov 24, 2024

Updated. It's now a greasemonkey/violentmonkey script.

You must now also click this button
image

PS: I see no problem adding other free items such as unity.

@lonewolf0708
Copy link

I changed line 5 of the script to reflect the free price url https://www.fab.com/search?ui_filter_price=1&is_free=1 which will list every free asset from fab.com which seems to work flawlessly with Violentmonkey with the exception of an error upon clicking the Get Free Assets button. That error is Failed to load resource: the server responded with a status of 400 () /i/users/me/acquired-content?listing_ids=:1 but is claiming the free assets and scrolling the page.

image

@SYRIANCJ
Copy link

SYRIANCJ commented Dec 6, 2024

do you know why it's showing this error, I turned all extenstions off and tried three browsers but nothing helped
image

@Subtixx
Copy link
Author

Subtixx commented Dec 7, 2024

This is not an "error" it just shows that the function didn't return anything, which is correct, the function doesn't return anything.

@SYRIANCJ
Copy link

SYRIANCJ commented Dec 7, 2024

Got it, what's confusing is, it doesn't shows which items has been added to the library (personal or professional) and if it failed and when it's done, other than that, thank you.

@Schafknuddler
Copy link

When do you know when the script is done running, when all the assets are claimed?

@lonewolf0708
Copy link

lonewolf0708 commented Mar 7, 2025

With the latest layout to fab.com, your script gives an error as follows:

Uncaught (in promise) TypeError: can't access property "childNodes", results is undefined
   getIds moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:86
   getAll moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:228
   createCheckbox moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:279
   createCheckbox moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:278
   addControls moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:253
   onBodyChange moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:290
   VMdr593cu887p moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:294
   VMdr593cu887p moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:296
   VMdr593cu887p moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:297
   rn moz-extension://c1961467-c12f-403d-8a10-294394d4be31/sandbox/injected-web.js:1
   <anonymous> moz-extension://c1961467-c12f-403d-8a10-294394d4be31/ FAB Free Asset Getter.user.js#1:1

@Subtixx
Copy link
Author

Subtixx commented Mar 8, 2025

Should work again.

Plus:

  • Added toast notifications
  • Added button
  • Added progressbar

Untitled

@TommyLingL
Copy link

TommyLingL commented Mar 21, 2025

i couldn't get it to work, the fab website just freeze and i get the following errors in console, please help me to see what I am missing, many thanks)

1742583615091 addons.xpi WARN Checking C:\Program Files\Mozilla Firefox\distribution\extensions for addons
Experiment add-an-image-to-pdf-with-alt-text-rollout has unknown featureId: addAnImageInPDF RemoteSettingsExperimentLoader.sys.mjs:766:21
_validateBranches resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs:766
checkRecipe resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs:673
CustomizableUI: Could not localize property 'fxms-bmb-button.tooltiptext'. CustomizableUI.sys.mjs:2253
CustomizableUI: unable to normalize widget CustomizableUI.sys.mjs:3135
TypeError: aId is undefined
CustomizableUI.sys.mjs:1886:22
CustomizableUI: unable to normalize widget CustomizableUI.sys.mjs:3135
TypeError: aId is undefined
CustomizableUI.sys.mjs:1886:22
getScreenshot(https://outlook.live.com/mail/) failed: Error: page-thumbnail:error
observe resource://gre/modules/BackgroundPageThumbs.sys.mjs:141
_onCaptureOrTimeout resource://gre/modules/BackgroundPageThumbs.sys.mjs:496
done resource://gre/modules/BackgroundPageThumbs.sys.mjs:734
_done resource://gre/modules/BackgroundPageThumbs.sys.mjs:757
onStateChange resource://gre/modules/BackgroundPageThumbs.sys.mjs:331
Screenshots.sys.mjs:68:15
NS_ERROR_ILLEGAL_VALUE: Component returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE) [nsIFaviconService.setFaviconForPage]
setIconFromLink resource:///actors/LinkHandlerParent.sys.mjs:141
receiveMessage resource:///actors/LinkHandlerParent.sys.mjs:53
LinkHandlerParent.sys.mjs:150:17
CustomizableUI: unable to normalize widget CustomizableUI.sys.mjs:3135
TypeError: aId is undefined
CustomizableUI.sys.mjs:1886:22
Key event not available on some keyboard layouts: key=“z” modifiers=“control,alt” id=“toggleSidebarKb” browser.xhtml
Key event not available on some keyboard layouts: key=“x” modifiers=“accel,alt” id=“viewGenaiChatSidebarKb” browser.xhtml
Key event not available on some keyboard layouts: key=“i” modifiers=“accel,alt,shift” id=“key_browserToolbox” browser.xhtml
SearchSuggestionController found an unexpected string value: HTTP request timeout SearchSuggestionController.sys.mjs:602:17
Error: TypeError: NetworkError when attempting to fetch resource. TelemetryFeed.sys.mjs:805:15
Error: TypeError: NetworkError when attempting to fetch resource. TelemetryFeed.sys.mjs:805:15
Error: TypeError: NetworkError when attempting to fetch resource. TelemetryFeed.sys.mjs:805:15
Content process 40092 isn't responsive while sending "DevToolsProcessParent:addOrSetSessionDataEntry" request. DevTools will ignore this process for now. DevToolsProcessParent.sys.mjs:320:17
WindowsJumpLists: Failed to fetch favicon for https://www.fab.com/search NS_ERROR_FAILURE: WindowsJumpLists.sys.mjs:257:29
WindowsJumpLists: Failed to fetch favicon for https://outlook.live.com/ NS_ERROR_FAILURE: WindowsJumpLists.sys.mjs:257:29
WindowsJumpLists: Failed to fetch favicon for https://outlook.live.com/mail/ NS_ERROR_FAILURE: WindowsJumpLists.sys.mjs:257:29
WindowsJumpLists: Failed to fetch favicon for https://www.fab.com/library NS_ERROR_FAILURE: WindowsJumpLists.sys.mjs:257:29
Failed to sendQuery in DevToolsProcessParent DevToolsProcessParent:addOrSetSessionDataEntry DevToolsProcessParent.sys.mjs:352:19
AbortError: Actor 'BrowserToolboxDevToolsProcess' destroyed before query 'DevToolsProcessParent:addOrSetSessionDataEntry' was resolved DevToolsProcessParent.sys.mjs:353:19
NotFoundError: No such JSProcessActor 'BrowserToolboxDevToolsProcess'
CustomizableUI: unable to normalize widget CustomizableUI.sys.mjs:3135
TypeError: aId is undefined
CustomizableUI.sys.mjs:1886:22
SearchSuggestionController found an unexpected string value: HTTP request timeout SearchSuggestionController.sys.mjs:602:17
WindowsJumpLists: Failed to fetch favicon for https://www.fab.com/search NS_ERROR_FAILURE: WindowsJumpLists.sys.mjs:257:29
WindowsJumpLists: Failed to fetch favicon for https://outlook.live.com/owa/0/ NS_ERROR_FAILURE: WindowsJumpLists.sys.mjs:257:29
WindowsJumpLists: Failed to fetch favicon for https://outlook.live.com/ NS_ERROR_FAILURE: WindowsJumpLists.sys.mjs:257:29
WindowsJumpLists: Failed to fetch favicon for https://www.fab.com/library NS_ERROR_FAILURE:

Uploading QQ20250322-031059.png…

@Subtixx
Copy link
Author

Subtixx commented Mar 22, 2025

Woahhh buddy... It's not wise to post your microsoft e-mail links. I've removed them for you.

I don't see any issues realted to the script though.

@TommyLingL
Copy link

thanks mate, i am not very familiar with greasy monkey. I installed the plugin on firefox and added your script. Then i went to fab.com/search and the greasy monkey is showing me the script is running, but no button poped up and the web seams to be freezing. I wonder what I have been missing

@lonewolf0708
Copy link

lonewolf0708 commented Apr 2, 2025

I've just experienced the same behavior with the page freezing
image
image

Don't know if you'll be able to view this, but I've attached a profiler result:
https://share.firefox.dev/4hQeX2t

EDIT: I managed to get the script debugger to finally load and this is the result:

Script terminated by timeout at:
showToast@moz-extension://c1961467-c12f-403d-8a10-294394d4be31/%20FAB%20Free%20Asset%20Getter.user.js#1:28:23
addControls@moz-extension://c1961467-c12f-403d-8a10-294394d4be31/%20FAB%20Free%20Asset%20Getter.user.js#1:365:16
onBodyChange@moz-extension://c1961467-c12f-403d-8a10-294394d4be31/%20FAB%20Free%20Asset%20Getter.user.js#1:374:7
showToast@moz-extension://c1961467-c12f-403d-8a10-294394d4be31/%20FAB%20Free%20Asset%20Getter.user.js#1:28:23
addControls@moz-extension://c1961467-c12f-403d-8a10-294394d4be31/%20FAB%20Free%20Asset%20Getter.user.js#1:365:16
onBodyChange@moz-extension://c1961467-c12f-403d-8a10-294394d4be31/%20FAB%20Free%20Asset%20Getter.user.js#1:374:7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment