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

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