Allchemy Challenge Mode

four random items, good luck

// ==UserScript==
// @name        Allchemy Challenge Mode
// @namespace   https://greasyfork.org/en/users/945115-unmatchedbracket
// @match       https://allchemy.io/*
// @grant       none
// @version     1.3.2
// @author      Unmatched Bracket
// @run-at      document-start
// @license     Unlicense
// @description four random items, good luck
// ==/UserScript==

// delete cache
{
  let ohno = "Could not delete Allchemy cache; some items may appear even if you haven't obtained them yet.\n"

  let oldinDB = window.indexedDB
  var request = oldinDB.open("keyval-store-c4");

  request.onsuccess = function(event) {
      // Close the database connection to ensure it can be deleted without being blocked
      request.result.close();

      // Request the deletion of the database
      var deleteRequest = oldinDB.deleteDatabase("keyval-store-c4");

      deleteRequest.onsuccess = function(event) {
          console.debug("Allchemy cache deleted");
      };

      deleteRequest.onerror = function(event) {
          console.error(ohno + "Reason - Delete request errored: ", request.error);
      };

      deleteRequest.onblocked = function(event) {
          console.warn(ohno + "Reason - Delete request blocked");
      };
  };

  request.onerror = function(event) {
      console.error(ohno + "Reason - Open request errored: ", request.error);
  };

  request.onblocked = function(event) {
      console.warn(ohno + "Reason - Open request blocked");
  };

  window.__defineGetter__("indexedDB", ()=>({
    open: function (name) {
      if (name == "keyval-store") {
        name = "keyval-store-c4"
      }
      return oldinDB.open(name)
    }
  }))
}


let c4 = {
  reset: () => {
    let items = [...c4.allItems].sort(() => 0.5 - Math.random())
    let goalItem = items.filter(x => x.depth == c4.config.goalComplexity)[0].item.id
    let startItems = items.filter(x => x.depth == c4.config.startComplexity && x.item.id != goalItem).map(x => x.item.id).slice(0, c4.config.startCount)
    c4.data = {
      start: [...startItems],
      unlocked: [...startItems],
      goal: goalItem
    }
    c4.write()
    localStorage["c4justReset"] = "true"
    if (inited) {
      location.reload()
    }
  },
  write: () => {
    localStorage.c4 = JSON.stringify(c4.data)
    localStorage.c4conf = JSON.stringify(c4.config)
  },
  read: () => {
    try {
      c4.config = JSON.parse(localStorage.c4conf)
    } catch (e) {}
    c4.data = JSON.parse(localStorage.c4)
  },
  data: null,
  config: {
    startComplexity: 2,
    startCount: 5,
    goalComplexity: 5,
  }
}

if (localStorage["c4justReset"] == "true") {
  localStorage.removeItem("c4justReset")
  localStorage.removeItem("pages/index/canvas/v1")
}

window.c4 = c4

let namemap = null;

let initFinish;
let inited = false;
let init = new Promise(r => {initFinish = () => {r(); inited = true}})
window.if = initFinish

const { fetch: originalFetch } = window;

let alreadyGotItems = false

window.fetch = async (...args) => {
  let [ resource, config ] = args;
  let actionType = null;

  let url = new URL(resource.url || resource, window.location)

  let jsonOverride = null
  let overrideOptions = {headers: {"content-type": "application/json"}}

  if (url.pathname == "/api/items") {
    actionType = "mix"
    await init
  } else if (url.pathname == "/api/users/me/items") {
    await init
    if (!alreadyGotItems) {
      jsonOverride = c4.allItems.filter(x => c4.data.unlocked.indexOf(x.item.id) >= 0)
      console.log(jsonOverride)
      alreadyGotItems = true
    } else jsonOverride = []
  }

  if (jsonOverride) {
    return new Response(JSON.stringify(jsonOverride), overrideOptions)
  }

  const response = await originalFetch(resource, config);

  switch (actionType) {
    case "mix":
      let data = await response.json()
      jsonOverride = data
      if (c4.data.unlocked.indexOf(data.item.id) == -1) {
        c4.data.unlocked.push(data.item.id)
        c4.write()
        if (data.item.id == c4.data.goal) {
          let startNames = c4.allItems.filter(x => c4.data.start.indexOf(x.item.id) >= 0).map(x => x.item.name)
          setTimeout(() => alert(`CHALLENGE COMPLETED
You made ${data.item.name} with only ${startNames.slice(0, startNames.length-1).join(", ")} and ${startNames.at(-1)}.
You used ${c4.data.unlocked.length - c4.data.start.length - 1} other items.
Click on the status text for menu and reset`), 0)
        }
      }
      overrideOptions.headers["x-owns-item"] = "true"
      overrideOptions.headers["x-energy-remaining"] = response.headers.get("x-energy-remaining")
  }

  if (jsonOverride) {
    return new Response(JSON.stringify(jsonOverride), overrideOptions)
  }

  return response;
};

function assembleConfig() {
  let tmp = `Current config:\n`
  Object.keys(c4.config).forEach(k => {
    tmp += `${k}: ${c4.config[k]}\n`
  })
  return tmp
}

function config () {
  let originalConf = JSON.stringify(c4.config)
  while (true) {
    let action = prompt(assembleConfig() + `Type, e.g. "startCount=7" to change values or nothing to exit`)
    if (!action) {
      break
    }
    let match = /^([a-zA-Z]+)=([0-9]+)$/.exec(action)
    if (match) {
      if (c4.config[match[1]] === undefined) {
        alert(`Key "${match[1]}" does not exist`)
      } else {
        let num = Number(match[2])
        if (match[1] == "startCount" && num == 0) {
          alert("Cannot set startCount to 0; must start with items")
        } else {
          c4.config[match[1]] = num
          c4.write()
        }
      }
    } else {
      alert(`Invalid command "${action}"`)
    }
  }
  if (JSON.stringify(c4.config) != originalConf) {
    if (confirm("Reset game with new settings?")) {
      c4.reset()
    }
  }
}

(async () => {
  console.log("AllChallenge: fetching all items")
  let itemsrq = await originalFetch("https://allchemy.io/api/users/me/items?after=air&count=999999")
  console.log("AllChallenge: parsing all items")
  let itemsjs = await itemsrq.json()
  console.log("AllChallenge: all items processed")

  c4.allItems = itemsjs

  try {
    c4.read()
  } catch (e) {
    c4.reset()
  }

  try {
    let titleThing = [...document.getElementsByClassName("title")].filter(x => x.innerText.indexOf("Items") != -1)[0]
    titleThing.children[0].style.display = "inline-block"
    let chaltag = document.createElement("span")
    chaltag.style.whiteSpace = "pre"
    let goal = c4.allItems.filter(x => x.item.id == c4.data.goal)[0] || {item: {name: c4.data.goal}}
    console.log("GOAL: ", goal)
    chaltag.innerText = " - Challenge Mode active (goal: " + goal.item.name + ")"
    chaltag.style.textAlign = "center"
    chaltag.classList.add("text")
    chaltag.style.display = "inline-block"
    titleThing.appendChild(chaltag)

    let canv = document.getElementsByClassName("canvas")[0]

    let template = document.createElement("div")
    template.innerHTML = `<button data-v-3ea00591="" class="absolute px-3 py-1 bg-white" style="left:8px;bottom:8px"><div class="text">Challenge</div></button>`
    let button = template.children[0]
    button.onclick = () => {
      let p = prompt(`CHALLENGE MODE
Starter items: ${c4.allItems.filter(x => c4.data.start.indexOf(x.item.id) >= 0).map(x => x.item.name).join(", ")}
Other items unlocked: ${c4.data.unlocked.length - c4.data.start.length}
Type "reset" to reset the challenge
"config" to change config
"import"/"export" to share games`)
      if (p == "reset" && (c4.data.unlocked.indexOf(c4.data.goal) >= 0 || confirm("Really reset?"))) {
        c4.reset()
      } else if (p == "config") {
        config()
      } else if (p == "export") {
        let dat = JSON.parse(JSON.stringify(c4.data))
        delete dat.unlocked
        prompt("Share this game JSON to share a game", JSON.stringify(dat))
      } else if (p == "import") {
        let txt = prompt("Paste the JSON for a game:")
        try {
          let dat = JSON.parse(txt)
          if (!dat) return
          // sanity check
          dat.start.aaaa, dat.goal.aaaa
          dat.unlocked = [...dat.start]
          c4.data = dat
          c4.write()
          localStorage["c4justReset"] = "true"
          location.reload()
        } catch (e) {
          alert("Invalid game. Did you copy the entire JSON, and are you sure this is a game?")
        }
      }
    }
    canv.appendChild(button)
  } catch (e) {}

  initFinish()
})()