--[[ Casino Central Server Copyright 2026 Herr Katze and minecartchris License: MIT ]] -- permission function takes uuid as argument local loop = require("taskmaster")() local sha256 = require("sha256") local nfc = peripheral.find("nfc_reader") local modem = peripheral.find("modem", function(n,w) return not w.isWireless() end) rednet.open(peripheral.getName(modem)) local permissions = {} function permissions.all() return true end function permissions.admin(player) --TODO: check player uuid against configurable admins, currently hardcoded to be Herr Katze if player == "1f558cbb-0752-49c0-ace4-7f9ed0506fe3" then return true end if player == "1de578d0-4eae-48db-abc9-7bf3354f809b" then return true end return false end local accounts = {} --[[ Account format: { username = "HerrKatzeGaming", uuid = "1f558cbb-0752-49c0-ace4-7f9ed0506fe3", balance = 69420, validCard = nil, -- either nil for no card, or a random uuid, banned = false } ]] local function saveAccounts() local data = textutils.serialize(accounts) local f = fs.open("accounts.slt","w") f.write(data) f.close() end if not fs.exists("accounts.slt") then saveAccounts() end do local f = fs.open("accounts.slt","r") data = f.readAll() f.close() accounts = textutils.unserialize(data) end local function createAccount(username,uuid,balance) balance = balance or 0 accounts[uuid] = { username = username, uuid = uuid, balance = balance } saveAccounts() end local function registerCard(uuid) local r = http.get("https://www.uuidgenerator.net/api/version4") local cardId = r.readAll() r.close() rednet.broadcast(cardId,"nfc_atm_write") local id, message = rednet.receive("casinoATMC2S", 61) -- 61 seconds so that the timeout should be handled properly if not id then chatbox.tell(uuid, "Did not receive response from ATM, Please ping a casino maintainer", "Chris's Casino", "minimessage") return end if message.type == "card_timeout" then chatbox.tell(uuid, "Card creation timed out. Re run the command to register your card.", "Chris's Casino", "minimessage") return end if message.type == "card_created" then accounts[uuid].validCard = cardId chatbox.tell(uuid, "Card registered.", "Chris's Casino", "minimessage") saveAccounts() return end print("[ERROR] Received Invalid message type",message.type) end local function revokeCard(uuid) if not accounts[uuid] then chatbox.tell(uuid,"Cannot revoke card for non-existant account", "Chris's Casino", "minimessage") return end if not accounts[uuid].validCard then chatbox.tell(uuid,"Card not created or already revoked.", "Chris's Casino", "minimessage") return end accounts[uuid].validCard = nil saveAccounts() chatbox.tell(uuid, "Card revoked. use \\casino register to obtain a new one. Your balance will transfer to the new card.", "Chris's Casino", "minimessage") end local function deleteAccount(uuid) accounts[uuid] = nil end local kromerNode = "https://kromer.reconnected.cc/api/krist" local pkey = "" local commands = {} local function makeaddressbyte(byte) local byte = 48 + math.floor(byte/7) return string.char(byte + 39 > 122 and 101 or byte > 57 and byte + 39 or byte) end local function make_address(key) local protein = {} local stick = sha256(sha256(key)) local n = 0 local link = 0 local v2 = "k" repeat if n<9 then protein[n] = string.sub(stick,0,2) stick = sha256(sha256(stick)) end n = n+1 until n==9 n=0 repeat link = tonumber(string.sub(stick,1+(2*n),2+(2*n)),16) % 9 if string.len(protein[link]) ~= 0 then v2 = v2 .. makeaddressbyte(tonumber(protein[link],16)) protein[link] = '' n=n+1 else stick = sha256(stick) end until n==9 return v2 end local function split(inputstr, sep) sep = sep or "," local t={} for str in string.gmatch(inputstr, "([^"..sep.."]+)") do table.insert(t, str) end return t end local address = make_address(pkey) print(address) local function handleWebSockets() local id = -1 local r,f = http.post(kromerNode.."/ws/start","{\"privatekey\":\""..pkey.."\"}",{["content-type"]="application/json"}) local resp = textutils.unserialiseJSON(r.readAll()) r.close() r = nil if resp.ok then socket = http.websocket(resp.url) print("Connected to Krist Websockets") id = id + 1 socket.send('{\"id\":'..id ..',\"type\":\"subscribe\",\"event\":\"ownTransactions\"}') while true do event = {os.pullEvent()} if event[1] == "websocket_message" then if event[2] == resp.url then wsevent = textutils.unserialiseJSON(event[3]) if wsevent.event == "transaction" and wsevent.transaction.to == address then local from = wsevent.transaction.from local hasMessage = false local hasError = false local hasUUID = false local otherUUID = nil err = "" if wsevent.transaction.metadata then mta = split(wsevent.transaction.metadata,";") for i,p in pairs(mta) do if p:match("useruuid") and not hasUUID then otherUUID = split(p,"=")[2] hasUUID = true end end end print(otherUUID) if not otherUUID then os.queueEvent("make_transaction",wsevent.transaction.from,wsevent.transaction.value) else if not accounts[otherUUID] then chatbox.tell(otherUUID,"Player doesn't have an account.", "Chris's Casino", "minimessage") os.queueEvent("make_transaction",wsevent.transaction.from,wsevent.transaction.value) else accounts[otherUUID].balance = accounts[otherUUID].balance + wsevent.transaction.value chatbox.tell(otherUUID,"Added "..wsevent.transaction.value.." to your balance", "Chris's Casino", "minimessage") saveAccounts() end end elseif wsevent.type == "keepalive" or wsevent.type == "response" then else end end elseif event[1] == "make_transaction" then id = id + 1 local rq = { id = id, type="make_transaction", to = event[2], amount = event[3], metadata = event[4], } socket.send(textutils.serialiseJSON(rq)) local c repeat c = socket.receive() c = textutils.unserialiseJSON(c) until c.type == "response" os.queueEvent("transaction_complete",c.ok) end end end end commands.balance = { exec= function(name, uuid, args) if accounts[uuid] and accounts[uuid].banned then chatbox.tell(uuid,"You have been banned from Chris's Casino, Please contact a casino maintainer for a balance refund.", "Chris's Casino", "minimessage") return end if #args == 0 or not permissions.admin(uuid) then if not accounts[uuid] then chatbox.tell(name,"You don't have an account, use \\casino register to get one", "Chris's Casino", "minimessage") return end chatbox.tell(name,"Your balance is "..accounts[uuid].balance.."", "Chris's Casino", "minimessage") else local otherUUID = args[1] if not accounts[otherUUID] then chatbox.tell(name,"Player doesn't have an account.", "Chris's Casino", "minimessage") return end chatbox.tell(name,""..accounts[otherUUID].username.."'s balance is "..accounts[otherUUID].balance.."", "Chris's Casino", "minimessage") end end, permission = permissions.all } commands.withdraw = { exec = function(name,uuid,args) if accounts[uuid] and accounts[uuid].banned then chatbox.tell(uuid,"You have been banned from Chris's Casino, Please contact a casino maintainer for a balance refund.", "Chris's Casino", "minimessage") return end if not accounts[uuid] then chatbox.tell(name,"You don't have an account, use \\casino register to get one", "Chris's Casino", "minimessage") return end if args[1] then local response = http.get(("https://kromer.reconnected.cc/api/v1/wallet/by-name/%s"):format(name)) response = textutils.unserializeJSON(response.readAll()) local address = response.data[1].address local amount = math.min(tonumber(args[1]),accounts[uuid].balance) print(amount) if amount == 0 then chatbox.tell(name,"You have no money.","Chris's Casino","minimessage") else accounts[uuid].balance = accounts[uuid].balance - amount saveAccounts() os.queueEvent("make_transaction",address,amount) chatbox.tell(name,"Withdrew "..amount.."kro from your balance. You have "..accounts[uuid].balance.." remaining.", "Chris's Casino", "minimessage") end end end, permission = permissions.all } commands.bal = commands.balance -- alias for \casino bal commands.register = { exec = function(name, uuid, args) if accounts[uuid] and accounts[uuid].banned then chatbox.tell(uuid,"You have been banned from Chris's Casino, Please contact a casino maintainer for a balance refund.", "Chris's Casino", "minimessage") return end if accounts[uuid] and accounts[uuid].validCard then chatbox.tell(name,"You already have a registered card, use \\casino revoke to revoke it, then create a new one with \\casino register","Chris's Casino", "minimessage") return end if not accounts[uuid] then createAccount(name,uuid) end chatbox.tell(name,"Creating card, tap on the NFC reader to register it. This card request will expire in 60s if not registered.", "Chris's Casino", "minimessage") registerCard(uuid) end, permission = permissions.all } commands.revoke = { exec = function(name, uuid, args) if accounts[uuid] and accounts[uuid].banned then chatbox.tell(uuid,"You have been banned from Chris's Casino, Please contact a casino maintainer for a balance refund.", "Chris's Casino", "minimessage") return end revokeCard(uuid) end, permission = permissions.all } commands.ban = { exec = function(name, uuid, args) local account = args[1] if not accounts[account] then createAccount("$unknown",uuid) end if accounts[account].banned then chatbox.tell(name,"User is already banned.", "Chris's Casino", "minimessage") return end accounts[account].banned = true chatbox.tell(name,"Banned user with uuid "..account, "Chris's Casino", "minimessage") end, permission = permissions.admin } commands.pardon = { exec = function(name, uuid, args) local account = args[1] if not accounts[account] or not accounts[account].banned then chatbox.tell(name,"User is not banned.", "Chris's Casino", "minimessage") return end accounts[account].banned = false if accounts[account].name == "$unknown" then deleteAccount(account) end -- Delete anonymous banned users. chatbox.tell(name,"Pardoned user with uuid "..account, "Chris's Casino", "minimessage") end, permission = permissions.admin } commands.add = { exec = function(name, uuid, args) if not args[2] then chatbox.tell(name,"Both a player UUID and a number must be provided", "Chris's Casino", "minimessage") return end local otherUUID = args[1] if not accounts[otherUUID] then chatbox.tell(name,"Player doesn't have an account.", "Chris's Casino", "minimessage") return end accounts[otherUUID].balance = accounts[otherUUID].balance + tonumber(args[2]) chatbox.tell(name,"Added "..args[2].." To "..accounts[otherUUID].username.."'s balance", "Chris's Casino", "minimessage") saveAccounts() end, permission = permissions.admin } commands.subtract = { exec = function(name, uuid, args) local otherUUID = args[1] if not args[2] then chatbox.tell(name,"Both a player UUID and a number must be provided", "Chris's Casino", "minimessage") return end if not accounts[otherUUID] then chatbox.tell(name,"Player doesn't have an account.", "Chris's Casino", "minimessage") return end accounts[otherUUID].balance = accounts[otherUUID].balance - tonumber(args[2]) chatbox.tell(name,"Subtracted "..args[2].." From "..accounts[otherUUID].username.."'s balance", "Chris's Casino", "minimessage") saveAccounts() end, permission = permissions.admin } commands.setbal = { exec = function(name, uuid, args) local otherUUID = args[1] if not args[2] then chatbox.tell(name,"Both a player UUID and a number must be provided", "Chris's Casino", "minimessage") return end if not accounts[otherUUID] then chatbox.tell(name,"Player doesn't have an account.", "Chris's Casino", "minimessage") return end accounts[otherUUID].balance = tonumber(args[2]) chatbox.tell(name,"Set"..accounts[otherUUID].username.."'s balance to "..args[2].."", "Chris's Casino", "minimessage") saveAccounts() end, permission = permissions.admin } local function commandHandler() while true do _, user, command, args, data = os.pullEvent("command") if command ~= "casino" then goto notOurCommand end local subcommand = table.remove(args,1) if not commands[subcommand] then chatbox.tell(user,"Invalid subcommand.", "Chris's Casino", "minimessage") goto continue end do local uuid = data.user.uuid if not commands[subcommand].permission(uuid) then chatbox.tell(user,"You do not have permission to run this command", "Chris's Casino", "minimessage") goto continue end loop:addFunction(commands[subcommand].exec,user,uuid,args) end ::continue:: if accounts[data.user.uuid] and accounts[data.user.uuid].username ~= user then accounts[data.user.uuid].username = user chatbox.tell(user," Your username has been updated in the account database.", "Chris's Casino", "minimessage") saveAccounts() end ::notOurCommand:: -- separate label so we don't respond to other people's commands with a username change. end end local function rednetMessageHandler() while true do local id, message, protocol = rednet.receive() if protocol == "machineBalanceModifier" then if type(message) ~= "table" or not message.uuid or not message.amount then goto continue_rednet end -- Guard against bullshit messages if message.type == "add" then accounts[message.uuid].balance = accounts[message.uuid].balance + message.amount end if message.type == "subtract" then accounts[message.uuid].balance = accounts[message.uuid].balance + message.amount end if message.type == "set" then accounts[message.uuid].balance = message.amount end elseif protocol == "getAccountData" then if type(message) ~= "table" or not message.card then goto continue_rednet end for _, account in pairs(accounts) do if "casinoAccount_"..account.validCard == message.card then -- Here's our guy! rednet.send(id,{ type = "account_data", uuid = account.uuid, username = account.username, balance = account.balance, cardId = message.card },"server_response") break end end end ::continue_rednet:: end end loop:task(commandHandler) :task(rednetMessageHandler) :task(handleWebSockets) :run()