一键购买 Quixel megascans 所有资产

用于从Quixel添加所有项目的脚本

由于 quixel 正在被移除,所有物品都可以免费获得。此脚本用于自动将物品添加到您的帐户(截至撰写本文时,总共有18874件物品)

注意:此脚本仅在最新版本的 Chrome 中测试过。

如何使用

  1. 复制下面的脚本
  2. 登录   https://quixel.com
  3. 前往   https://quixel.com/megascans/collections
  4. 打开 devtools (F12) -> 转到“控制台”选项卡
  5. 粘贴脚本并按Enter
  6. 将会弹出一个对话框确认执行,单击“确定”
  7. 坐下来出现下方提示,表示全部购买完成

一键购买 Quixel megascans 所有资产

常见问题

  • 收到“禁止访问”错误。(即使刷新后,整个页面仍显示“禁止访问”)
    • API 添加速度过快可能会导致您达到 API 的速率限制。(我的测试是在 10 页之后进行的,所以大约有 10k 个项目)。
    • 等待约 10-20 分钟后继续。加载https://quixel.comCommon Fixes -> Restart script后请参阅继续执行。
  • 脚本似乎已暂停/挂起
    • 可能是日志记录太多了。尝试监控脚本,如果显示“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是否继续执行。

常见修复

重启脚本

  1. 注意它正在运行哪个页面
  2. 复制run.js脚本
  3. startPage = 0将第一行更新为startPage = 10(假设第 10 页被挂起)

变更日志

  • 初始脚本发布
  • 更新以清除日志,减少卡住的机会
  • [当前] 跳过已获取的项目。减少日志输出。脚本完成后增加更多显示信息,显示已购买项目的数量。由于现在跳过了已购买的项目,实际上您不再需要指定开始页面。
(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.");
  }
})();

 

Leave a Reply

后才能评论

最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。 若排除这种情况,可在对应资源底部留言,或者联系右侧管理员Q

如果您已经成功付款但是网站没有弹出成功提示,请你千万不要着急~~什么事情都不要慌 ~~~联系站长提供付款信息为您处理