Skip to content

Instantly share code, notes, and snippets.

@Subtixx
Last active July 2, 2025 21:56
Show Gist options
  • Select an option

  • Save Subtixx/47f6db9ceabf51c3f689db1594a50b37 to your computer and use it in GitHub Desktop.

Select an option

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 <d.hock@it-hock.de>
// @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
});
})();
@unleashed7
Copy link

This is extremely weird, on my laptop I can use the script no problem. On my PC I tried 3 different browsers, all hangs on Fab.com if the script is enabled making it unusable. Cannot figure out how that's possible.

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