-
-
Save Subtixx/47f6db9ceabf51c3f689db1594a50b37 to your computer and use it in GitHub Desktop.
// ==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 | |
}); | |
})(); |
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."}
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...
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.
This is not an "error" it just shows that the function didn't return anything, which is correct, the function doesn't return anything.
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.
When do you know when the script is done running, when all the assets are claimed?
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
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:
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.
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
I've just experienced the same behavior with the page freezing
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
Got an error when running the script on fab.com
