您需要 登录 才可以下载或查看,没有账号?注册
x
由于 quixel 正在被移除,所有物品都可以免费获得。此脚本用于自动将物品添加到您的帐户(截至撰写本文时,总共有 件18874物品)注意:此脚本仅在最新版本的 Chrome 中测试过。
如何使用复制下面的脚本(run.js)登录https://quixel.com前往https://quixel.com/megascans/collections打开 devtools (F12) -> 转到“控制台”选项卡粘贴脚本并按Enter。将会弹出一个对话框确认执行,单击“确定”坐下来等待
常见问题收到“禁止访问”错误。(即使刷新后,整个页面仍显示“禁止访问”)API 添加速度过快可能会导致您达到 API 的速率限制。(我的测试是在 10 页之后进行的,所以大约有 10k 个项目)。等待约 10-20 分钟后继续。Common Fixes -> Restart script加载https://quixel.com后请参阅继续执行。脚本似乎已暂停/挂起可能是日志记录太多了。尝试监控脚本,如果显示“END PAGE X”,记下页码(以备需要重新启动)并通过单击 devtools 中的“🚫”图标清除控制台。参见Common Fixes -> Restart script修复。获取错误**UNABLE TO ADD ITEM**应该会有 中显示的错误信息( )。如果是user already owns specified asset at a higher or equal resolution,则表示它已在您的帐户中。获取错误cannot find authentication token. Please login again清除浏览器 cookies 并重新登录 quixel。尝试手动添加 1 个项目。如果成功,则查看Common Fixes -> Restart script是否继续执行。常见修复重启脚本注意它正在运行哪个页面复制run.js脚本startPage = 0将第一行更新为startPage = 10(假设第 10 页被挂起)更改日志初始脚本发布更新以清除日志以减少挂起的机会[当前] 跳过添加已获得的物品。减少日志。脚本完成后添加更多信息以显示购买的物品数量。由于现在跳过了购买的物品,从技术上讲您不再需要指定startPage。(F12) -> 转到“控制台”选项卡
输入后复制下面的 (脚本) 文件下载
( 复制脚本)(async (startPage = 0, autoClearConsole = true) => {
const getCookie = (name) => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
};
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const fetchWithTimeout = (resource, options = {}) => {
const { timeout = 10000 } = options;
return Promise.race([
fetch(resource, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
),
]);
};
const callCacheApi = async (params = {}) => {
const defaultParams = {
page: 0,
maxValuesPerFacet: 1000,
hitsPerPage: 1000,
attributesToRetrieve: ["id", "name"].join(","),
};
const fetchData = async () => {
const response = await fetchWithTimeout("https://proxy-algolia-prod.quixel.com/algolia/cache", {
headers: {
"x-api-key": "2Zg8!d2WAHIUW?pCO28cVjfOt9seOWPx@2j",
},
body: JSON.stringify({
url: "https://6UJ1I5A072-2.algolianet.com/1/indexes/assets/query?x-algolia-application-id=6UJ1I5A072&x-algolia-api-key=e93907f4f65fb1d9f813957bdc344892",
params: new URLSearchParams({ ...defaultParams, ...params }).toString(),
}),
method: "POST",
});
if (!response.ok) {
throw new Error(`Error fetching from Cache API: ${response.statusText}`);
}
return await response.json();
};
return await retryOperation(fetchData, 2000, 5);
};
const callAcl = async ({ id, name }) => {
const fetchData = async () => {
const response = await fetchWithTimeout("https://quixel.com/v1/acl", {
headers: {
authorization: "Bearer " + authToken,
"content-type": "application/json;charset=UTF-8",
},
body: JSON.stringify({ assetID: id }),
method: "POST",
});
if (!response.ok) {
throw new Error(`Error adding item ${id} | ${name}: ${response.statusText}`);
}
const json = await response.json();
if (json?.isError) {
console.error(` --> **Failed to add item** Item ${id} | ${name} (${json?.msg})`);
} else {
console.log(` --> Added item ${id} | ${name}`);
}
};
return await retryOperation(fetchData, 2000, 5);
};
const callAcquired = async () => {
const fetchData = async () => {
const response = await fetchWithTimeout("https://quixel.com/v1/assets/acquired", {
headers: {
authorization: "Bearer " + authToken,
"content-type": "application/json;charset=UTF-8",
},
method: "GET",
});
if (!response.ok) {
throw new Error(`Error fetching acquired items: ${response.statusText}`);
}
return await response.json();
};
return await retryOperation(fetchData, 2000, 5);
};
const retryOperation = async (operation, delay, retries) => {
let lastError;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
console.warn(`Attempt ${attempt} failed (${error.message}). Retrying in ${delay}ms...`);
await sleep(delay);
delay *= 2; // Exponential backoff
}
}
throw lastError;
};
let authToken = "";
const initialize = async () => {
console.log("-> Checking Auth API Token...");
try {
const authCookie = getCookie("auth") ?? "{}";
authToken = JSON.parse(decodeURIComponent(authCookie))?.token;
if (!authToken) {
throw new Error("-> Error: Authentication token not found. Please log in again.");
}
} catch (_) {
throw new Error("-> Error: Authentication token not found. Please log in again.");
}
console.log("-> Fetching acquired items...");
acquiredItems = (await callAcquired()).map((a) => a.assetID);
console.log("-> Fetching total number of pages...");
const initialData = await callCacheApi();
totalPages = initialData.nbPages;
itemsPerPage = initialData.hitsPerPage;
totalItems = initialData.nbHits;
console.log("-> ==============================================");
console.log(`-> Total items: ${totalItems}`);
console.log(`-> ${totalPages} total pages with ${itemsPerPage} items per page`);
console.log(`-> Total items to add: ${totalItems - acquiredItems.length}.`);
console.log("-> ==============================================");
if (!confirm(`Click OK to add ${totalItems - acquiredItems.length} items to your account.`)) {
throw new Error("-> Process cancelled by user.");
}
};
let acquiredItems = [];
let totalPages = 0;
let itemsPerPage = 0;
let totalItems = 0;
const MAX_CONCURRENT_REQUESTS = 5;
const mainProcess = async () => {
for (let pageIdx = startPage || 0; pageIdx < totalPages; pageIdx++) {
console.log(`-> ======================= PAGE ${pageIdx + 1}/${totalPages} START =======================`);
console.log("-> Fetching items from page " + (pageIdx + 1) + " ...");
const pageData = await callCacheApi({ page: pageIdx });
const items = pageData.hits;
console.log("-> Adding unacquired items...");
// Filter out already acquired items
const unownedItems = items.filter((i) => !acquiredItems.includes(i.id));
// Save current progress in localStorage
localStorage.setItem('currentPage', pageIdx);
// Limit concurrent requests
const queue = [...unownedItems];
const workers = Array.from({ length: MAX_CONCURRENT_REQUESTS }, async () => {
while (queue.length > 0) {
const item = queue.shift();
try {
await callAcl(item);
} catch (error) {
console.error(`Error with item ${item.id}: ${error.message}`);
}
}
});
await Promise.all(workers);
console.log(`-> ======================= PAGE ${pageIdx + 1}/${totalPages} COMPLETED =======================`);
if (autoClearConsole) console.clear();
}
};
const finalize = async () => {
console.log("-> Fetching new acquisition info...");
const newAcquiredItems = await callAcquired();
const newItemsAcquired = newAcquiredItems.length;
const newTotalCount = (await callCacheApi()).nbHits;
console.log(`-> Completed. Your account now has a total of ${newItemsAcquired} out of ${newTotalCount} items.`);
alert(`-> Your account now has a total of ${newItemsAcquired} out of ${newTotalCount} items.\n\nIf you find some items missing, try refreshing the page and run the script again.`);
};
try {
// Check if progress was saved
const savedPage = localStorage.getItem('currentPage');
if (savedPage !== null) {
startPage = parseInt(savedPage, 10);
console.log(`-> Resuming from page ${startPage + 1}`);
}
await initialize();
await mainProcess();
await finalize();
// Clear progress
localStorage.removeItem('currentPage');
} catch (error) {
console.error(error.message);
console.log("-> The script could not be completed.");
}
})();
资源下载中
下载完成后
脚本中实现的更新API 请求的超时处理:引入了fetchWithTimeout为所有 API 请求设置 10 秒超时的功能。这可防止脚本因网络响应缓慢或服务器超时而挂起。采用指数退避算法的重试机制:添加了retryOperation重试失败操作(API 请求)最多 5 次的功能。它实施指数退避,以在连续失败的情况下逐步增加重试之间的延迟。这确保在处理网络问题时具有更好的可靠性。并行请求控制:引入了MAX_CONCURRENT_REQUESTS限制(默认设置为 5),用于控制发送到服务器的并发请求数。这可防止网络或服务器过载,并确保更稳定的性能,尤其是在处理许多项目时。通过 LocalStorage 实现进度持久化:实现了进度持久性,将当前页面索引保存到localStorage。如果脚本中断或失败,它将在下次运行时从上次保存的页面恢复。这可以防止用户从头开始重新启动该过程。改进的错误处理和日志记录:增强了脚本的错误处理功能,API 调用失败时会显示详细的错误消息。这可以更清楚地反馈错误原因,并改善整体故障排除。为每次重试添加警告,显示当前尝试和延迟消息,以便更好地跟踪重试过程。控制台自动清除选项:添加了在处理完每个页面后自动清除控制台的选项(autoClearConsole),以防止过多的日志压垮控制台。false如果用户希望保留日志,可以将此选项设置为 。保存和恢复状态:如果脚本之前被中断,系统会提示用户从上次中断的地方继续执行。这可确保长时间运行的操作的连续性。改进的用户提示和警报:该脚本现在包含更清晰、更具描述性的提示和警报,以指导用户完成整个过程,包括添加项目前的确认和操作结束时的摘要。
1
|