一个脚本,将Quixel-Megascans所有扫描资源添加到帐号购买项目中 ! -丨CG小苏...
443 1
实名

通过了实名认证的内容创造者

发布于 2024-9-20 14:57:51

您需要 登录 才可以下载或查看,没有账号?注册

x

b94fbaca35cb306046ab04c07b6c3ff2.png

82a7ecd61cba0163b293d5f57631d562.jpg
由于 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                                 

本帖被以下画板推荐:

授人以鱼不如授人以渔!B站和微信公众号关注: CG小苏  每日分享最前沿视频教程和技术文章白嫖资源!
使用道具 <
mome  发表于 2024-9-21 09:46:44  
2#
你好强
回复 收起回复
使用道具
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表