-- -- starwatch.lua -- -- Watches ATM Star stock in a chest and any ATM Star autocrafts running on the -- ME network, and posts a status line to a Discord webhook every 4 hours. -- -- ME Bridge : right side -- Chest : top side (or a wired-modem inventory, see CHEST below) -- -- Set WEBHOOK before running. -- local WEBHOOK = "" -- https://discord.com/api/webhooks/... local BRIDGE = "right" local CHEST = "top" -- side, or a modem name like "minecraft:chest_12" local ITEM = "allthetweaks:atm_star" local LABEL = "ATM Star" local POST_EVERY = 4 * 60 * 60 -- seconds local REFRESH = 30 -- terminal refresh, seconds local function fail(msg) print(msg); error("", 0) end if not peripheral.isPresent(BRIDGE) then fail("No peripheral on the " .. BRIDGE .. " side (expected the ME Bridge).") end local bridge = peripheral.wrap(BRIDGE) -- The chest may be direct-attached or hanging off a wired modem, in which case -- "top" is the modem and the inventory has its own name. local function findChest() if peripheral.isPresent(CHEST) then local p = peripheral.wrap(CHEST) if p.list then return CHEST, p end if p.getNamesRemote then -- it's a modem for _, n in ipairs(p.getNamesRemote()) do local r = peripheral.wrap(n) if r and r.list then return n, r end end end end return nil end local chestName, chest = findChest() if not chest then fail("No inventory found on " .. CHEST .. " (nor on a modem there).") end -- Bridge methods vary by Advanced Peripherals version; probe once. local has = {} for _, m in ipairs(peripheral.getMethods(BRIDGE) or {}) do has[m] = true end if not has.getCraftingCPUs then fail("Peripheral on " .. BRIDGE .. " is not an ME Bridge (no getCraftingCPUs).") end local function countInChest() local ok, items = pcall(chest.list) if not ok or type(items) ~= "table" then return nil end local n = 0 for _, it in pairs(items) do if it.name == ITEM then n = n + (it.count or 0) end end return n end local function meStock() local ok, info = pcall(bridge.getItem, { name = ITEM }) if not ok or type(info) ~= "table" then return 0 end return info.amount or 0 end -- Returns { {cpu=name, amount=n}, ... } for CPUs currently making ATM Stars, -- plus busy/total counts for context. local function starJobs() local jobs, busy, total = {}, 0, 0 local ok, cpus = pcall(bridge.getCraftingCPUs) if ok and type(cpus) == "table" then for i, c in ipairs(cpus) do total = total + 1 if c.isBusy then busy = busy + 1 end local item = c.craftingItem if type(item) == "table" and item.name == ITEM then jobs[#jobs + 1] = { cpu = c.name or ("CPU #" .. i), amount = item.amount or item.count or 0 } end end end -- Older bridges don't expose craftingItem - fall back to a yes/no answer. if #jobs == 0 and busy > 0 and has.isItemCrafting then local ok2, crafting = pcall(bridge.isItemCrafting, { name = ITEM }) if ok2 and crafting then jobs[#jobs + 1] = { cpu = "unknown CPU", amount = 0 } end end return jobs, busy, total end local function snapshot() local jobs, busy, total = starJobs() return { chest = countInChest(), me = meStock(), jobs = jobs, busy = busy, total = total, } end local function describe(s) local lines = {} lines[#lines + 1] = string.format("**%s status**", LABEL) lines[#lines + 1] = string.format("Chest (`%s`): **%s**", chestName, s.chest and tostring(s.chest) or "unreadable") lines[#lines + 1] = string.format("ME network: **%d**", s.me) if #s.jobs == 0 then lines[#lines + 1] = "Autocrafting: none" else local parts = {} for _, j in ipairs(s.jobs) do parts[#parts + 1] = j.amount > 0 and string.format("%s x%d", j.cpu, j.amount) or j.cpu end lines[#lines + 1] = "Autocrafting: **" .. table.concat(parts, ", ") .. "**" end lines[#lines + 1] = string.format("CPUs busy: %d/%d", s.busy, s.total) return table.concat(lines, "\n") end local function post(s) if WEBHOOK == "" then return false, "no webhook configured" end local body = textutils.serialiseJSON({ username = "ATM Star Watch", content = describe(s), }) local res, err = http.post(WEBHOOK, body, { ["Content-Type"] = "application/json" }) if not res then return false, tostring(err) end local code = res.getResponseCode() res.close() if code >= 300 then return false, "HTTP " .. code end return true end local function draw(s, nextPost, lastPost) term.clear() term.setCursorPos(1, 1) print("=== " .. LABEL .. " watch ===") print("Chest : " .. (s.chest and tostring(s.chest) or "unreadable") .. " (" .. chestName .. ")") print("ME : " .. s.me) if #s.jobs == 0 then print("Craft : idle") else for _, j in ipairs(s.jobs) do print("Craft : " .. j.cpu .. (j.amount > 0 and (" x" .. j.amount) or "")) end end print("CPUs : " .. s.busy .. "/" .. s.total .. " busy") print("") local mins = math.max(0, math.floor((nextPost - os.clock()) / 60)) print(("Next post in %dh %dm"):format(math.floor(mins / 60), mins % 60)) if lastPost then print("Last post: " .. lastPost) end print("Ctrl+T to stop.") end local lastPost = nil local nextPost = os.clock() -- post once at startup so you know it works while true do local s = snapshot() if os.clock() >= nextPost then local ok, err = post(s) lastPost = ok and (textutils.formatTime(os.time(), true) .. " ok") or (textutils.formatTime(os.time(), true) .. " FAILED: " .. err) nextPost = os.clock() + POST_EVERY end draw(s, nextPost, lastPost) sleep(REFRESH) end