- 复制以下脚本的内容
- 登录您的FAB控制面板
- 确保您已接受条款和条件(例如,尝试领取任何免费资产)
- 转到 Quixel 卖家列表,网址为 https://www.fab.com/sellers/Quixel
- 打开 devtools (F12) -> 转到“控制台”选项卡
- 粘贴脚本并按 Enter 键
您可以随意调整脚本最顶部的常量,以减少等待时间,或防止 tiemouts 或 throttles。
async function claimQuixelAssets() { // Configurable constants to resume const INITIAL_PAGE_CURSOR = null; const INITIAL_FILTER_INDEX = 0; const WAIT_TIME_BETWEEN_PAGES = 5; // in seconds const WAIT_TIME_BETWEEN_CLAIMS = 0; // in seconds // Debugging constants const CLEAR_CONSOLE = true; const PARALLEL_CLAIM = true; const CLAIM = true; // Hacky constants to overcome API limitations const SKIP_ITEM_IDS = [ '3e441085-0d1d-4476-93bc-e68d184026f2', 'f74671f7-d8a5-42e7-9dd4-e76639ca0ef0', 'd808f867-2560-4476-ad05-a1d999bd940b', ]; // A list of strings, in case an item constantly fails to claim due to 500 errors const FILTERS = [ // Environments 'listing_types=environment', // Materials 'listing_types=material', // 'categories=art-traditional&listing_types=material', // 'categories=building-human-made&listing_types=material', // 'categories=damage-grunge&listing_types=material', // 'categories=fabric-clothing&listing_types=material', // 'categories=nature-terrain&listing_types=material', // 'categories=organic&listing_types=material', // Decals 'listing_types=decal', // 'categories=art-traditional&listing_types=decal', // 'categories=building-human-made&listing_types=decal', // 'categories=damage-grunge&listing_types=decal', // 'categories=fabric-clothing&listing_types=decal', // 'categories=nature-terrain&listing_types=decal', // 'categories=organic&listing_types=decal', // Brushes 'listing_types=brush', // 'categories=art-traditional&listing_types=brush', // 'categories=building-human-made&listing_types=brush', // 'categories=damage-grunge&listing_types=brush', // Atlas 'listing_types=atlas', // 'categories=building-human-made&listing_types=atlas', // 'categories=damage-grunge&listing_types=atlas', // 'categories=nature-terrain&listing_types=atlas', // 3D models 'listing_types=3d-model', // 'categories=buildings-architecture&listing_types=3d-model', // 'categories=electronics-technology&listing_types=3d-model', // 'categories=food-drink&listing_types=3d-model', // 'categories=furniture-fixtures&listing_types=3d-model', // 'categories=nature-plants&listing_types=3d-model', // 'categories=environments-scenes&listing_types=3d-model', // 'categories=objects-decor&listing_types=3d-model', // ... ] // Counters let totalPages = 0; let claimedAssets = new Set(); // Functions const clearConsole = () => { if (CLEAR_CONSOLE) { console.clear() } else { console.log(`-------------------------------------------------------------------`); } } const sleep = (seconds) => { return new Promise(r => setTimeout(r, seconds * 1000)); } const getCSRFToken = () => { const cookieName = 'sb_csrftoken'; return document.cookie.match('(^|;)\\s*' + cookieName + '\\s*=\\s*([^;]+)')?.pop() || ''; } const postResource = async (path, body) => { const baseUrl = "https://www.fab.com/i/listings"; const formData = new FormData(); for (const name in body) { formData.append(name, body[name]); } const response = await fetch(`${baseUrl}/${path}`, { headers: { "accept": "application/json, text/plain, */*", "x-csrftoken": getCSRFToken(), }, method: "POST", body: formData, }); if (!response.ok) { throw new Error(`Failed request with ${response.status} status code`); } return await response.text(); } const getResource = async (path) => { const baseUrl = "https://www.fab.com/i/listings"; const response = await fetch(`${baseUrl}/${path}`, { headers: { "accept": "application/json, text/plain, */*", "x-csrftoken": getCSRFToken(), }, method: "GET", }); if (!response.ok) { throw new Error(`Failed request with ${response.status} status code`); } return await response.json(); } const fetchItem = async (itemId) => { try { return await getResource(itemId); } catch (error) { throw new Error(`Failed to fetch item ${itemId} due to: ${error}`) } } const claimItem = async (item) => { let offer_id = item.licenses.find((license) => license.slug == "professional").offerId; // offer_id = item.licenses[0].offerId; try { await postResource(`${item.uid}/add-to-library`, { offer_id }); } catch (error) { throw new Error(`Failed to claim item ${item.title} (${item.uid}) due to: ${error}`) } } const handleItem = async (listItem) => { if (SKIP_ITEM_IDS.includes(listItem.uid)) { console.log(`-> Skipping ${listItem.title} (id: ${listItem.uid})`); return; } if (claimedAssets.has(listItem.uid)) { console.log(`-> Already claimed ${listItem.title} (id: ${listItem.uid})`); return; } console.log(`-> Claiming ${listItem.title} (id: ${listItem.uid})`); if (CLAIM) { const item = await fetchItem(listItem.uid); await claimItem(item); } claimedAssets.add(listItem.uid); await sleep(WAIT_TIME_BETWEEN_CLAIMS); } const fetchItems = async (filterIndex, pageCursor) => { const filters = FILTERS[filterIndex]; try { const cursorQuery = pageCursor ? `&cursor=${pageCursor}` : '' return await getResource(`search?seller=Quixel&${filters}${cursorQuery}`); } catch (error) { throw new Error(`Failed to get page ${pageCursor || 'null'} due to: ${error}`); } } const handleListPage = async (filterIndex, pageCursor) => { console.log(`-> Getting content from page cursor ${pageCursor || ''}, with filter index ${filterIndex}...`); let page = await fetchItems(filterIndex, pageCursor); console.log(`-> Found ${page.results.length} results on this page`); if (PARALLEL_CLAIM) { const promises = page.results.map((item) => handleItem(item)); await Promise.all(promises) } else { for (const item of page.results) { await handleItem(item); } } await sleep(WAIT_TIME_BETWEEN_PAGES); clearConsole(); totalPages += 1; if (pageCursor != null && pageCursor == page.cursors.next) { throw new Error(`Cursor loop found on page ${pageCursor}; this is a problem on FAB's API, not this script`); } return page.cursors.next } // Main loop function const main = async () => { clearConsole(); try { for (let filterIndex = INITIAL_FILTER_INDEX; filterIndex < FILTERS.length; filterIndex += 1) { let pageCursor = INITIAL_PAGE_CURSOR; do { console.log(`-> Pages processed so far: ${totalPages} pages`); console.log(`-> Assets claimed so far: ${claimedAssets.size} assets`); pageCursor = await handleListPage(filterIndex, pageCursor) } while (pageCursor != null); } console.log('-> Script execution completed successfully!') console.log(`-> Total pages processed: ${totalPages} pages`); console.log(`-> Total assets claimed: ${claimedAssets.size} assets`); } catch (error) { console.error(`-> ${error.message}`); console.error("-> Try waiting for a couple of minutes, then re-running the script by changing INITIAL_FILTER_INDEX and INITIAL_PAGE_CURSOR"); console.error(error); } } return await main(); } claimQuixelAssets();
出于某种原因,搜索查询可能会停止,具体取决于所使用的排序键。或者,在某些情况下,一遍又一遍地重复同一页。我目前正在尝试找到解决方案,但这完全取决于服务器如何响应此脚本发出的搜索查询。
评论(2)
为什么无法运行呢
使用Microsoft Edge浏览器或者Firefox浏览器尝试~!