| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // 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 = `
- <thead>
- <tr>
- <th>mat</th>
- <th data-tooltip="needed to supply all planets">want</th>
- <th>bids</th>
- <th data-tooltip="in warehouse">have</th>
- <th data-tooltip="want - bids - have">buy</th>
- <th data-tooltip="ask - bid">spread</th>
- <th data-tooltip="buy × spread">savings</th>
- </tr>
- </thead>
- <tbody></tbody>`;
- 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 = `
- <td>${m.ticker}</td>
- <td>${format(m.amount)}</td>
- <td>${format(m.bids)}</td>
- <td>${format(m.have)}</td>
- <td>${format(buyAmount)}</td>
- <td>${format(m.spread)}</td>
- <td>${format(m.savings)}</td>
- `;
- 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 = `
- <textarea readonly></textarea>
- <input type="button" value="copy">`;
- 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
|