// ts/cache.ts var fetchCache = new Map; async function cachedFetchJSON(url, options) { if (fetchCache.has(url)) return fetchCache.get(url); const response = await fetch(url, options).then((r) => r.json()); fetchCache.set(url, response); return response; } // ts/popover.ts function setupPopover() { const main = document.querySelector("main"); const popover = document.querySelector("#popover"); main.addEventListener("mouseover", (event) => { const target = event.target; if (target.dataset.tooltip) { popover.textContent = target.dataset.tooltip; const rect = target.getBoundingClientRect(); popover.style.left = `${rect.left}px`; popover.style.top = `${rect.bottom}px`; popover.showPopover(); } }); main.addEventListener("mouseout", (event) => { const target = event.target; if (target.dataset.tooltip) popover.hidePopover(); }); } // ts/buy.ts var warehouseNames = { AI1: "ANT", CI1: "BEN", IC1: "HRT", NC1: "MOR" }; var username = document.querySelector("#username"); var apiKey = document.querySelector("#api-key"); { const storedUsername = localStorage.getItem("fio-username"); if (storedUsername) username.value = storedUsername; const storedApiKey = localStorage.getItem("fio-api-key"); if (storedApiKey) apiKey.value = storedApiKey; } document.querySelector("#fetch").addEventListener("click", async () => { const supplyForDays = parseInt(document.querySelector("#days").value, 10); const loader = document.querySelector("#loader"); loader.innerHTML = ""; loader.style.display = "block"; try { await calculate(username.value, apiKey.value, supplyForDays); localStorage.setItem("fio-username", username.value); localStorage.setItem("fio-api-key", apiKey.value); } catch (e) { console.error(e); } loader.style.display = "none"; }); document.querySelector("#buys").addEventListener("click", (event) => { if (!event.target) return; const target = event.target; if (target.tagName !== "INPUT" || target.type !== "button") return; const xitSection = target.parentElement; if (!xitSection || !xitSection.classList.contains("xit-act")) return; navigator.clipboard.writeText(xitSection.querySelector("textarea").value); }); setupPopover(); async function calculate(username2, apiKey2, supplyForDays) { const buys = document.querySelector("#buys"); buys.innerHTML = ""; const closest = await cachedFetchJSON("/closest.json"); const planetsByCX = new Map; for (const planet of await getPlanets(username2, apiKey2)) { const wh = closest[planet.id]; const existing = planetsByCX.get(wh); if (existing) existing.push(planet); else planetsByCX.set(wh, [planet]); } for (const [cx, planets] of planetsByCX) buys.append(...await calcForCX(username2, apiKey2, supplyForDays, planets, cx)); } async function calcForCX(username2, apiKey2, supplyForDays, planets, cx) { const prices = await getPrices(cx); const avail = await warehouseInventory(username2, apiKey2, warehouseNames[cx]); const { bids, orders } = await getBids(username2, apiKey2, cx); const totalConsumption = new Map; for (const planet of planets) { for (const [ticker, consumption] of planet.netConsumption) totalConsumption.set(ticker, (totalConsumption.get(ticker) ?? 0) + consumption); for (const mat of planet.exporting) { const planetExport = planet.inventory.get(mat); if (planetExport) avail.set(mat, (avail.get(mat) ?? 0) + planetExport); } } const buy = new Map; for (const [mat, amount] of totalConsumption) if (amount > 0) buy.set(mat, (buy.get(mat) ?? 0) + amount * supplyForDays); const materials = []; for (const [mat, amount] of buy) { const remaining = Math.max(amount - (bids.get(mat) ?? 0) - (avail.get(mat) ?? 0), 0); const price = prices.get(mat); if (!price || price.Bid === null || price.Ask === null) { console.log(mat, "has no bid/ask"); continue; } const spread = price.Ask - price.Bid; materials.push({ ticker: mat, amount, bids: bids.get(mat) ?? 0, have: avail.get(mat) ?? 0, spread, savings: spread * remaining }); } materials.sort((a, b) => b.savings - a.savings); const h2 = document.createElement("h2"); h2.textContent = `${cx}/${warehouseNames[cx]}`; const span = document.createElement("span"); span.textContent = planets.map((p) => p.name).join(", "); const table = document.createElement("table"); table.innerHTML = ` mat want bids have buy spread savings `; const tbody = table.querySelector("tbody"); const toBuy = {}; const priceLimits = {}; const format = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format; for (const m of materials) { const tr = document.createElement("tr"); const buyAmount = Math.max(m.amount - m.bids - m.have, 0); tr.innerHTML = ` ${m.ticker} ${format(m.amount)} ${format(m.bids)} ${format(m.have)} ${format(buyAmount)} ${format(m.spread)} ${format(m.savings)} `; if (buyAmount > 0) { if (m.bids === 0) tr.children[2].classList.add("red"); toBuy[m.ticker] = Math.round(buyAmount); const bid = prices.get(m.ticker).Bid; const epsilon = 10 ** (Math.floor(Math.log10(bid)) - 2); const limit = bid + 2 * epsilon; priceLimits[m.ticker] = limit; } tbody.appendChild(tr); } const xitSection = document.createElement("section"); xitSection.classList.add("xit-act"); xitSection.innerHTML = ` `; xitSection.querySelector("textarea").value = JSON.stringify({ actions: [ { name: "BuyItems", type: "CX Buy", group: "A1", exchange: cx, priceLimits, buyPartial: true, allowUnfilled: true, useCXInv: false } ], global: { name: `${cx} buy orders for ${supplyForDays} days` }, groups: [{ type: "Manual", name: "A1", materials: toBuy }] }); orders.sort((a, b) => b.Limit * b.Amount - a.Limit * a.Amount); for (const order of orders) { const deposit = order.Limit * order.Amount; console.log(`${order.MaterialTicker.padEnd(4)} ${deposit} `); } return [h2, span, table, xitSection]; } async function getPrices(cx) { const rawPrices = await cachedFetchJSON("https://refined-prun.github.io/refined-prices/all.json"); const prices = new Map; for (const p of rawPrices) if (p.ExchangeCode === cx) prices.set(p.MaterialTicker, p); return prices; } async function getPlanets(username2, apiKey2) { const fioBurns = await cachedFetchJSON("https://rest.fnar.net/fioweb/burn/user/" + username2, { headers: { Authorization: apiKey2 } }); const planets = fioBurns.map((burn) => new Planet(burn)); return planets; } async function warehouseInventory(username2, apiKey2, whName) { const warehouses = await cachedFetchJSON("https://rest.fnar.net/sites/warehouses/" + username2, { headers: { Authorization: apiKey2 } }); const inventory = new Map; for (const warehouse of warehouses) if (warehouse.LocationNaturalId === whName) { const storage = await cachedFetchJSON(`https://rest.fnar.net/storage/${username2}/${warehouse.StoreId}`, { headers: { Authorization: apiKey2 } }); for (const item of storage.StorageItems) inventory.set(item.MaterialTicker, item.MaterialAmount); break; } return inventory; } async function getBids(username2, apiKey2, cx) { const allOrders = await cachedFetchJSON("https://rest.fnar.net/cxos/" + username2, { headers: { Authorization: apiKey2 } }); const orders = allOrders.filter((order) => order.OrderType === "BUYING" && order.Status !== "FILLED" && order.ExchangeCode === cx); const bids = new Map; for (const order of orders) bids.set(order.MaterialTicker, (bids.get(order.MaterialTicker) ?? 0) + order.Amount); return { bids, orders }; } class Planet { id; name; inventory; netConsumption; exporting; constructor(fioBurn) { this.id = fioBurn.PlanetId; this.name = fioBurn.PlanetName || fioBurn.PlanetNaturalId; this.inventory = new Map; for (const item of fioBurn.Inventory) this.inventory.set(item.MaterialTicker, item.MaterialAmount); this.netConsumption = new Map; for (const item of fioBurn.OrderProduction) this.netConsumption.set(item.MaterialTicker, -item.DailyAmount); for (const c of [...fioBurn.OrderConsumption, ...fioBurn.WorkforceConsumption]) { this.netConsumption.set(c.MaterialTicker, (this.netConsumption.get(c.MaterialTicker) ?? 0) + c.DailyAmount); } this.exporting = new Set; for (const item of fioBurn.OrderProduction) if ((this.netConsumption.get(item.MaterialTicker) ?? 0) < 0) this.exporting.add(item.MaterialTicker); } } //# debugId=D0751901C98C88F664756E2164756E21