-
-
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 | |
}); | |
})(); |
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
With the latest layout to fab.com, your script gives an error as follows: