NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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 2.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/acquired-content?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 }); })();