메가스캔 에셋을 모두 추가하는 스크립트

2024년 말까지만 유효합니다.

돌아가기

사용 방법

아래 스크립트 run.js를 복사합니다.

https://quixel.com 에 로그인합니다.

개발자 도구(F12)를 열고 "Console" 탭으로 이동합니다.

복사한 스크립트를 붙여넣고 Enter 키를 누릅니다.

실행을 확인하는 대화 상자가 나타나면 "OK"를 클릭합니다.


run.js
((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 callCacheApi = async (params = {}) => {
    const defaultParams = {
      page: 0,
      maxValuesPerFacet: 1000,
      hitsPerPage: 1000,
      attributesToRetrieve: ["id", "name"].join(",")
    }
    const response = await fetch("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",
    })
    return await response.json()
  }

  const callAcl = async ({ id, name }) => {
    const response = await fetch("https://quixel.com/v1/acl", {
      "headers": {
        "authorization": "Bearer " + authToken,
        "content-type": "application/json;charset=UTF-8",
      },
      "body": JSON.stringify({ assetID: id }),
      "method": "POST",
    });
    const json = await response.json()
    if (json?.isError) {
      console.error(`  --> **UNABLE TO ADD ITEM** Item ${id} | ${name} (${json?.msg})`)
    } else {
      console.log(`  --> ADDED ITEM Item ${id} | ${name}`)
    }
  }

  const callAcquired = async () => {
    const response = await fetch("https://quixel.com/v1/assets/acquired", {
      "headers": {
        "authorization": "Bearer " + authToken,
        "content-type": "application/json;charset=UTF-8",
      },
      "method": "GET",
    });
    return await response.json()
  }

  // 1. Check token exist, quixel API needs it
  console.log("-> Checking Auth API Token...")
  let authToken = ""
  try {
    const authCookie = getCookie("auth") ?? "{}"
    authToken = JSON.parse(decodeURIComponent(authCookie))?.token
    if (!authToken) {
      return console.error("-> Error: cannot find authentication token. Please login again.")
    }
  } catch (_) {
    return console.error("-> Error: cannot find authentication token. Please login again.")
  }

  // 2. Get all currently acquired items
  console.log("-> Get Acquired Items...")
  const acquiredItems = (await callAcquired()).map(a => a.assetID)

  // 3. Get total count of items
  console.log("-> Getting Total Number of Pages....")
  const { nbPages: totalPages, hitsPerPage: itemsPerPage, nbHits: totalItems } = await callCacheApi()


  console.log("-> ==============================================")
  console.log(`-> Total # of items: ${totalItems}`)
  console.log(`-> ${totalPages} total pages with ${itemsPerPage} per page`)
  console.log(`-> Total Items to add: ${(totalItems - acquiredItems.length)}.`)
  console.log("-> ==============================================")

  if (!confirm(`Click OK to start adding ${(totalItems - acquiredItems.length)} items in your account.`)) return

  // Loop
  for (let pageIdx = startPage || 0; pageIdx < totalPages; pageIdx++) {
    console.log(`-> ======================= PAGE ${pageIdx + 1}/${totalPages} START =======================`)

    console.log("-> Getting Items from page " + (pageIdx + 1) + " ...")

    const { hits: items } = await callCacheApi({ page: pageIdx })

    console.log("-> Adding non-acquired items...")

    // Filter out owned items
    const unownedItems = items.filter(i => !acquiredItems.includes(i.id))
    const aclPromises = unownedItems.map(callAcl)

    await Promise.all(aclPromises)
    console.log(`-> ======================= PAGE ${pageIdx + 1}/${totalPages} COMPLETED =======================`)
    if (autoClearConsole) console.clear() // Fix the issue that too much log hangs the console. Set autoClearConsole = false to keep the logs
  }

  console.log("-> Getting new acquired info...")
  // Get acquired items again
  const newItemsAcquired = (await callAcquired()).length
  const newTotalCount = (await callCacheApi()).nbHits

  console.log(`-> Completed. Your account now have a total of ${newItemsAcquired} out of ${newTotalCount} items.`)

  alert(`-> Your account now have a total of ${newItemsAcquired} out of ${newTotalCount} items.\n\nIf you find some items missing, try refresh the page and run the script again.`)
})())

자주 발생하는 문제

  • "Forbidden" 오류가 발생하는 경우 (새로 고침 후에도 페이지가 계속 "Forbidden" 상태로 표시됨)

    • API가 너무 빠르게 추가되어 API의 속도 제한에 걸릴 가능성이 있습니다. (테스트 결과 약 10페이지 정도, 약 10,000개 항목 후 발생)
    • 약 10~20분 후에 다시 시도해보세요. https://quixel.com 페이지를 로드할 수 있게 되면, Common Fixes -> Restart script을 통해 실행을 계속할 수 있습니다.
  • 스크립트가 멈추는 경우

    • 로그가 너무 많이 출력되어 문제가 발생할 수 있습니다. 스크립트를 모니터링하고 "END PAGE X"라는 메시지가 나타나면 페이지 번호를 기록한 후 개발자 도구 콘솔에서 "🚫" 아이콘을 클릭해 콘솔을 지우십시오.
    • 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 오류가 발생하는 경우

    • 브라우저 쿠키를 삭제한 후 quixel에 다시 로그인합니다. 에셋 하나를 수동으로 추가해보세요. 성공하면 Common Fixes -> Restart script을 통해 실행을 계속할 수 있습니다.

일반적인 해결 방법

스크립트 재시작

스크립트가 실행 중이던 페이지를 기록합니다.

run.js 스크립트를 복사합니다.

첫 번째 줄에 있는 startPage = 0startPage = 10으로 업데이트합니다.
(예를 들어, 페이지 10에서 멈췄을 경우)

변경 사항

초기 스크립트 출시

로그를 지워 멈출 확률을 줄이는 업데이트

이미 획득한 항목을 건너뛰도록 수정. 로그를 줄임.
스크립트 실행 완료 후 구매된 항목 수를 표시하는 정보 추가.
이제 구매된 항목을 건너뛰므로 startPage를 지정할 필요가 없어짐.


참고 자료

작성자

안동민

게시일

2024년 9월 19일