milo cleanup + shop

This commit is contained in:
kepler155c@gmail.com
2019-01-11 10:01:37 -05:00
parent 42e72cf3c8
commit bfa528756e
23 changed files with 1141 additions and 101 deletions

View File

@@ -4,6 +4,6 @@
},
title = 'Schematic Builder',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/builder',
description = [[ Build structures from schematic files using a turtle or command computer. ]],
description = [[Build structures from schematic files using a turtle or command computer. ]],
licence = 'MIT',
}

View File

@@ -5,7 +5,9 @@
title = 'Milo: Advanced inventory management',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/milo',
description = [[Provides AE style crafting in computercraft.
Includes Importing, exporting, autocrafting, replenish and limits, and much more.
Includes over 200 standard Minecraft recipes.]],
Includes: importing, exporting, autocrafting, replenish, limits and much more.
Includes over 200 standard Minecraft recipes.]],
licence = 'MIT',
}

View File

@@ -47,29 +47,19 @@ end
function Milo:getState(key)
if not self.state then
self.state = { }
Config.load('milo.state', self.state)
self.state = Config.load('milo.state')
end
return self.state[key]
end
function Milo:setState(key, value)
if not self.state then
self.state = { }
Config.load('milo.state', self.state)
self.state = Config.load('milo.state')
end
self.state[key] = value
Config.update('milo.state', self.state)
end
function Milo:uniqueKey(item)
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
end
function Milo:splitKey(key)
return itemDB:splitKey(key)
end
function Milo:resetCraftingStatus()
self.context.storage.activity = { }
@@ -85,18 +75,8 @@ function Milo:registerTask(task)
table.insert(self.context.tasks, task)
end
function Milo:getItem(items, inItem, ignoreDamage, ignoreNbtHash)
if not ignoreDamage and not ignoreNbtHash then
return items[inItem.key or self:uniqueKey(inItem)]
end
for _,item in pairs(items) do
if item.name == inItem.name and
(ignoreDamage or item.damage == inItem.damage) and
(ignoreNbtHash or item.nbtHash == inItem.nbtHash) then
return item
end
end
function Milo:getItem(inItem)
return self:listItems()[inItem.key or itemDB:makeKey(inItem)]
end
-- returns a list of items that matches along with a total count
@@ -106,7 +86,7 @@ function Milo:getMatches(item, flags)
local items = self:listItems()
if not flags.ignoreDamage and not flags.ignoreNbtHash then
local key = item.key or Milo:uniqueKey(item)
local key = item.key or itemDB:makeKey(item)
local v = items[key]
if v then
t[key] = Util.shallowCopy(v)
@@ -146,7 +126,7 @@ function Milo:getTurtleInventory()
end
function Milo:requestCrafting(item)
local key = Milo:uniqueKey(item)
local key = itemDB:makeKey(item)
if not self.context.craftingQueue[key] then
item.crafted = 0
@@ -157,7 +137,7 @@ function Milo:requestCrafting(item)
end
end
-- queue up an action that reliees on the crafting grid
-- queue up an action that relies on the crafting grid
function Milo:queueRequest(request, callback)
if Util.empty(self.context.queue) then
os.queueEvent('milo_queue')
@@ -178,7 +158,7 @@ function Milo:craftAndEject(item, count)
end
function Milo:makeRequest(item, count, callback)
local current = Milo:getItem(Milo:listItems(), item) or { count = 0 }
local current = self:getItem(item) or { count = 0 }
if count <= 0 then
return {
@@ -187,18 +167,18 @@ function Milo:makeRequest(item, count, callback)
count = 0,
current = current.count,
item = item,
key = item.key or Milo:uniqueKey(item),
key = item.key or itemDB:makeKey(item),
}
end
local toCraft = count - math.min(current.count, count)
if toCraft > 0 then
local recipe = Craft.findRecipe(self:uniqueKey(item))
local recipe = Craft.findRecipe(itemDB:makeKey(item))
if not recipe then
toCraft = 0
else
-- if you ask for 1 stick, getCraftableAmount will return 4 (obviously)
toCraft = math.min(toCraft, Craft.getCraftableAmount(recipe, toCraft, Milo:listItems(), { }))
toCraft = math.min(toCraft, Craft.getCraftableAmount(recipe, toCraft, self:listItems(), { }))
end
end
@@ -208,11 +188,11 @@ function Milo:makeRequest(item, count, callback)
count = math.min(count, current.count),
current = current.count,
item = item,
key = item.key or Milo:uniqueKey(item),
key = item.key or itemDB:makeKey(item),
}
if request.count > 0 then
Milo:queueRequest(request, callback)
self:queueRequest(request, callback)
end
if request.craft > 0 then
@@ -244,7 +224,7 @@ function Milo:updateRecipe(result, recipe)
end
function Milo:saveMachineRecipe(recipe, result, machine)
local key = Milo:uniqueKey(result)
local key = itemDB:makeKey(result)
-- save the recipe
self.context.userRecipes[key] = recipe
@@ -261,32 +241,29 @@ function Milo:mergeResources(t)
t = Util.shallowCopy(t)
for k,v in pairs(self.context.resources) do
local key = itemDB:splitKey(k)
local item = self:getItem(t, key)
local item = t[k]
if item then
item = Util.shallowCopy(item)
else
item = key
item = itemDB:splitKey(k)
item.count = 0
item.key = k
end
Util.merge(item, v)
item.resource = v
t[item.key] = item
t[k] = item
end
for k in pairs(Craft.recipes) do
local v = itemDB:splitKey(k)
local item = self:getItem(t, v)
local item = t[k]
if not item then
item = v
item = itemDB:splitKey(k)
item.count = 0
item.key = k
else
item = Util.shallowCopy(item)
end
t[item.key] = item
item.has_recipe = true
t[k] = item
end
for key in pairs(Craft.machineLookup) do
@@ -294,7 +271,7 @@ function Milo:mergeResources(t)
if item then
item = Util.shallowCopy(item)
item.is_craftable = true
t[item.key] = item
t[key] = item
end
end

View File

@@ -116,6 +116,10 @@ function Storage:initStorage()
end
v.adapter.online = true
v.adapter.dirty = true
if v.adapter.isOn and not v.adapter.isOn() then -- turtle
v.adapter.turnOn()
end
elseif device[k] then
v.adapter = device[k]
v.adapter.online = true

View File

@@ -169,7 +169,7 @@ function page:eject(amount)
if amount == 'stack' then
amount = item.maxCount or 64
elseif amount == 'all' then
item = Milo:getItem(Milo:listItems(), item)
item = Milo:getItem(item)
if item then
amount = item.count
end

View File

@@ -340,7 +340,7 @@ function nodeWizard.filter:eventHandler(event)
elseif event.type == 'scan_turtle' then
local inventory = Milo:getTurtleInventory()
for _,item in pairs(inventory) do
self.entry.filter[Milo:uniqueKey(item)] = true
self.entry.filter[itemDB:makeKey(item)] = true
end
self:resetGrid()
self.grid:update()
@@ -359,7 +359,7 @@ function nodeWizard.filter:eventHandler(event)
self.form:save()
self.entry.filter = { }
for _,v in pairs(self.grid.values) do
self.entry.filter[Milo:uniqueKey(v)] = true
self.entry.filter[itemDB:makeKey(v)] = true
end
self:hide()
self.callback()

View File

@@ -31,7 +31,7 @@ function ExportTask:cycle(context)
if slot then
-- something is in the slot, find what we can export
for key in pairs(entry.filter) do
local filterItem = Milo:splitKey(key)
local filterItem = itemDB:splitKey(key)
if (slot.name == filterItem.name and
(entry.ignoreDamage or slot.damage == filterItem.damage) and
(entry.ignoreNbtHash or slot.nbtHash == filterItem.nbtHash)) then
@@ -50,7 +50,7 @@ function ExportTask:cycle(context)
-- slot is empty - export first matching item we have in storage
for key in pairs(entry.filter) do
local items = Milo:getMatches(Milo:splitKey(key), entry)
local items = Milo:getMatches(itemDB:splitKey(key), entry)
local _, item = next(items)
if item then
local count = math.min(item.count, itemDB:getMaxCount(item))

View File

@@ -17,12 +17,12 @@ function ImportTask:cycle(context)
local function itemMatchesFilter(item)
if not entry.ignoreDamage and not entry.ignoreNbtHash then
local key = Milo:uniqueKey(item)
local key = itemDB:makeKey(item)
return entry.filter[key]
end
for key in pairs(entry.filter) do
local v = Milo:splitKey(key)
local v = itemDB:splitKey(key)
if item.name == v.name and
(entry.ignoreDamage or item.damage == v.damage) and
(entry.ignoreNbtHash or item.nbtHash == v.nbtHash) then

View File

@@ -39,11 +39,12 @@ local manageTab = UI.Window {
}
function manageTab:setItem(item)
self.origItem = item
self.item = Util.shallowCopy(item)
self.res = item.resource or { }
self.item = item
self.res = Util.shallowCopy(context.resources[item.key] or { })
self.res.displayName = self.item.displayName
self.form:setValues(self.res)
-- TODO: ignore damage should not be active if there is not a maxDamage value
end
function manageTab:eventHandler(event)
@@ -51,19 +52,17 @@ function manageTab:eventHandler(event)
UI:setPreviousPage()
elseif event.type == 'form_complete' then
local item = self.item
if self.form:save() then
if self.res.displayName ~= self.origItem.displayName then
self.origItem.displayName = self.res.displayName
itemDB:add(self.origItem)
if self.res.displayName ~= self.item.displayName then
self.item.displayName = self.res.displayName
itemDB:add(self.item)
itemDB:flush()
-- TODO: ugh
if context.storage.cache[self.origItem.key] then
context.storage.cache[self.origItem.key].displayName = self.res.displayName
if context.storage.cache[self.item.key] then
context.storage.cache[self.item.key].displayName = self.res.displayName
end
--context.storage:setDirty()
end
self.res.displayName = nil
Util.prune(self.res, function(v)
if type(v) == 'boolean' then
@@ -75,20 +74,14 @@ function manageTab:eventHandler(event)
end)
local newKey = {
name = item.name,
damage = self.res.ignoreDamage and 0 or item.damage,
nbtHash = not self.res.ignoreNbtHash and item.nbtHash or nil,
name = self.item.name,
damage = self.res.ignoreDamage and 0 or self.item.damage,
nbtHash = not self.res.ignoreNbtHash and self.item.nbtHash or nil,
}
for k,v in pairs(context.resources) do
if v == self.res then
context.resources[k] = nil
break
end
end
context.resources[self.item.key] = nil
if not Util.empty(self.res) then
context.resources[Milo:uniqueKey(newKey)] = self.res
context.resources[itemDB:makeKey(newKey)] = self.res
end
Milo:saveResources()

View File

@@ -1,4 +1,5 @@
local Milo = require('milo')
local itemDB = require('itemDB')
local Milo = require('milo')
local LimitTask = {
name = 'limiter',
@@ -11,7 +12,7 @@ function LimitTask:cycle(context)
if trashcan then
for key,res in pairs(context.resources) do
if res.limit then
local items, count = Milo:getMatches(Milo:splitKey(key), res)
local items, count = Milo:getMatches(itemDB:splitKey(key), res)
if count > res.limit then
local amount = count - res.limit
for _, item in pairs(items) do

View File

@@ -111,7 +111,7 @@ function pages.confirmation:validate()
}
for k,v in pairs(inventory) do
recipe.ingredients[k] = Milo:uniqueKey(v)
recipe.ingredients[k] = itemDB:makeKey(v)
end
Milo:saveMachineRecipe(recipe, result, machine.name)

View File

@@ -25,7 +25,7 @@ function PotionImportTask:cycle(context)
if blazePowder then
context.storage:export(bs, 5, 1, blazePowder)
else
local item = itemDB:get(BLAZE_POWDER) or Milo:splitKey(BLAZE_POWDER)
local item = itemDB:get(BLAZE_POWDER) or itemDB:splitKey(BLAZE_POWDER)
item.requested = 1
Milo:requestCrafting(item)
end
@@ -35,7 +35,7 @@ function PotionImportTask:cycle(context)
-- brewing has completd
if self.brewQueue[bs.name] and list[1] then
local key = Milo:uniqueKey(list[1])
local key = itemDB:makeKey(list[1])
if not Craft.findRecipe(key) then
Milo:saveMachineRecipe(self.brewQueue[bs.name], list[1], bs.name)
end
@@ -68,7 +68,7 @@ function PotionImportTask:cycle(context)
if valid() then
for i = 1, 4 do
recipe.ingredients[i] = Milo:uniqueKey(list[i])
recipe.ingredients[i] = itemDB:makeKey(list[i])
end
self.brewQueue[bs.name] = recipe

View File

@@ -106,7 +106,7 @@ local function client(socket)
local slot = node.adapter.getItemMeta(slotNo)
if slot then
if context.storage:import(node, slotNo, slot.count, slot) > 0 then
local item = Milo:getItem(Milo:listItems(), slot)
local item = Milo:getItem(slot)
if item then
socket:write({
type = 'received',
@@ -127,7 +127,7 @@ local function client(socket)
if count == 'stack' then
count = itemDB:getMaxCount(data.item)
elseif count == 'all' then
local item = Milo:getItem(Milo:listItems(), data.item)
local item = Milo:getItem(data.item)
count = item and item.count or 0
end

View File

@@ -1,3 +1,4 @@
local itemDB = require('itemDB')
local Milo = require('milo')
local ReplenishTask = {
@@ -8,7 +9,7 @@ local ReplenishTask = {
function ReplenishTask:cycle(context)
for k,res in pairs(context.resources) do
if res.low then
local item = Milo:splitKey(k)
local item = itemDB:splitKey(k)
item.key = k
local _, count = Milo:getMatches(item, res)
@@ -27,7 +28,7 @@ function ReplenishTask:cycle(context)
replenish = true,
})
else
local request = context.craftingQueue[Milo:uniqueKey(item)]
local request = context.craftingQueue[itemDB:makeKey(item)]
if request and request.replenish then
--request.count = request.crafted
end

View File

@@ -8,7 +8,7 @@ local Util = require('util')
local colors = _G.colors
local os = _G.os
local config = Config.load('store')
local config = Config.load('shop')
--[[ Display ]]--
local function createPage(node)
@@ -123,18 +123,14 @@ local pages = { }
-- called when an item to sell has been changed
Event.on('store_refresh', function()
config = Config.load('store')
config = Config.load('shop')
end)
Event.on('store_provide', function(_, item, quantity)
local count = 0
for k, v in pairs(config) do
if v.name == item then
count = Milo:eject(itemDB:splitKey(k), quantity)
break
end
end
os.queueEvent('store_provided', item, count)
Event.on('store_provide', function(_, item, quantity, uid)
Milo:queueRequest({ }, function()
local count = Milo:eject(item, quantity)
os.queueEvent('store_provided', uid, count)
end)
end)
--[[ Task ]]--

View File

@@ -55,7 +55,7 @@ local function learnRecipe()
]]--
maxCount = 1
newRecipe.craftingTools[Milo:uniqueKey(tool)] = true
newRecipe.craftingTools[itemDB:makeKey(tool)] = true
v1.craftingTool = true
break
end
@@ -80,7 +80,7 @@ local function learnRecipe()
newRecipe.count = recipe.count
local key = Milo:uniqueKey(recipe)
local key = itemDB:makeKey(recipe)
if recipe.maxCount ~= 64 then
newRecipe.maxCount = recipe.maxCount
end
@@ -88,7 +88,7 @@ local function learnRecipe()
if ingredient.maxDamage > 0 then
-- ingredient.damage = '*' -- I don't think this is right
end
ingredients[k] = Milo:uniqueKey(ingredient)
ingredients[k] = itemDB:makeKey(ingredient)
end
Milo:updateRecipe(key, newRecipe)

6
swshop/.package Normal file
View File

@@ -0,0 +1,6 @@
{
title = 'Switchcraft basic shop',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/swshop',
description = 'Modification of the k store by Lemmmy',
licence = 'MIT',
}

209
swshop/json Normal file
View File

@@ -0,0 +1,209 @@
------------------------------------------------------------------ utils
local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"}
local function isArray(t)
local max = 0
for k,v in pairs(t) do
if type(k) ~= "number" then
return false
elseif k > max then
max = k
end
end
return max == #t
end
local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true}
function removeWhite(str)
while whites[str:sub(1, 1)] do
str = str:sub(2)
end
return str
end
------------------------------------------------------------------ encoding
local function encodeCommon(val, pretty, tabLevel, tTracking)
local str = ""
-- Tabbing util
local function tab(s)
str = str .. ("\t"):rep(tabLevel) .. s
end
local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)
str = str .. bracket
if pretty then
str = str .. "\n"
tabLevel = tabLevel + 1
end
for k,v in iterator(val) do
tab("")
loopFunc(k,v)
str = str .. ","
if pretty then str = str .. "\n" end
end
if pretty then
tabLevel = tabLevel - 1
end
if str:sub(-2) == ",\n" then
str = str:sub(1, -3) .. "\n"
elseif str:sub(-1) == "," then
str = str:sub(1, -2)
end
tab(closeBracket)
end
-- Table encoding
if type(val) == "table" then
assert(not tTracking[val], "Cannot encode a table holding itself recursively")
tTracking[val] = true
if isArray(val) then
arrEncoding(val, "[", "]", ipairs, function(k,v)
str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
end)
else
arrEncoding(val, "{", "}", pairs, function(k,v)
assert(type(k) == "string", "JSON object keys must be strings", 2)
str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking)
end)
end
-- String encoding
elseif type(val) == "string" then
str = '"' .. val:gsub("[%c\"\\]", controls) .. '"'
-- Number encoding
elseif type(val) == "number" or type(val) == "boolean" then
str = tostring(val)
else
error("JSON only supports arrays, objects, numbers, booleans, and strings", 2)
end
return str
end
function encode(val)
return encodeCommon(val, false, 0, {})
end
function encodePretty(val)
return encodeCommon(val, true, 0, {})
end
------------------------------------------------------------------ decoding
local decodeControls = {}
for k,v in pairs(controls) do
decodeControls[v] = k
end
function parseBoolean(str)
if str:sub(1, 4) == "true" then
return true, removeWhite(str:sub(5))
else
return false, removeWhite(str:sub(6))
end
end
function parseNull(str)
return nil, removeWhite(str:sub(5))
end
local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}
function parseNumber(str)
local i = 1
while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do
i = i + 1
end
local val = tonumber(str:sub(1, i - 1))
str = removeWhite(str:sub(i))
return val, str
end
function parseString(str)
str = str:sub(2)
local s = ""
while str:sub(1,1) ~= "\"" do
local next = str:sub(1,1)
str = str:sub(2)
assert(next ~= "\n", "Unclosed string")
if next == "\\" then
local escape = str:sub(1,1)
str = str:sub(2)
next = assert(decodeControls[next..escape], "Invalid escape character")
end
s = s .. next
end
return s, removeWhite(str:sub(2))
end
function parseArray(str)
str = removeWhite(str:sub(2))
local val = {}
local i = 1
while str:sub(1, 1) ~= "]" do
local v = nil
v, str = parseValue(str)
val[i] = v
i = i + 1
str = removeWhite(str)
end
str = removeWhite(str:sub(2))
return val, str
end
function parseObject(str)
str = removeWhite(str:sub(2))
local val = {}
while str:sub(1, 1) ~= "}" do
local k, v = nil, nil
k, v, str = parseMember(str)
val[k] = v
str = removeWhite(str)
end
str = removeWhite(str:sub(2))
return val, str
end
function parseMember(str)
local k = nil
k, str = parseValue(str)
local val = nil
val, str = parseValue(str)
return k, val, str
end
function parseValue(str)
local fchar = str:sub(1, 1)
if fchar == "{" then
return parseObject(str)
elseif fchar == "[" then
return parseArray(str)
elseif tonumber(fchar) ~= nil or numChars[fchar] then
return parseNumber(str)
elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then
return parseBoolean(str)
elseif fchar == "\"" then
return parseString(str)
elseif str:sub(1, 4) == "null" then
return parseNull(str)
end
return nil
end
function decode(str)
str = removeWhite(str)
t = parseValue(str)
return t
end
function decodeFromFile(path)
local file = assert(fs.open(path, "r"))
local decoded = decode(file.readAll())
file.close()
return decoded
end

122
swshop/jua.lua Normal file
View File

@@ -0,0 +1,122 @@
local juaVersion = "0.0"
juaRunning = false
eventRegistry = {}
timedRegistry = {}
local function registerEvent(event, callback)
if eventRegistry[event] == nil then
eventRegistry[event] = {}
end
table.insert(eventRegistry[event], callback)
end
local function registerTimed(time, repeating, callback)
if repeating then
callback(true)
end
table.insert(timedRegistry, {
time = time,
repeating = repeating,
callback = callback,
timer = os.startTimer(time)
})
end
local function discoverEvents(event)
local evs = {}
for k,v in pairs(eventRegistry) do
if k == event or string.match(k, event) or event == "*" then
for i,v2 in ipairs(v) do
table.insert(evs, v2)
end
end
end
return evs
end
function on(event, callback)
registerEvent(event, callback)
end
function setInterval(callback, time)
registerTimed(time, true, callback)
end
function setTimeout(callback, time)
registerTimed(time, false, callback)
end
function tick()
local eargs = {os.pullEventRaw()}
local event = eargs[1]
if eventRegistry[event] == nil then
eventRegistry[event] = {}
else
local evs = discoverEvents(event)
for i, v in ipairs(evs) do
v(unpack(eargs))
end
end
if event == "timer" then
local timer = eargs[2]
for i = #timedRegistry, 1, -1 do
local v = timedRegistry[i]
if v.timer == timer then
v.callback(not v.repeating or nil)
if v.repeating then
v.timer = os.startTimer(v.time)
else
table.remove(timedRegistry, i)
end
end
end
end
end
function run()
os.queueEvent("init")
juaRunning = true
while juaRunning do
tick()
end
end
function go(func)
on("init", func)
run()
end
function stop()
juaRunning = false
end
function await(func, ...)
local args = {...}
local out
local finished
func(function(...)
out = {...}
finished = true
end, unpack(args))
while not finished do tick() end
return unpack(out)
end
return {
on = on,
setInterval = setInterval,
setTimeout = setTimeout,
tick = tick,
run = run,
go = go,
stop = stop,
await = await
}

413
swshop/k.lua Normal file
View File

@@ -0,0 +1,413 @@
local w
local r
local jua
local json
local await
local endpoint = "krist.ceriat.net"
local wsEndpoint = "ws://"..endpoint
local httpEndpoint = "http://"..endpoint
local function asserttype(var, name, vartype, optional)
if not (type(var) == vartype or optional and type(var) == "nil") then
error(name..": expected "..vartype.." got "..type(var), 3)
end
end
function init(juai, jsoni, wi, ri)
asserttype(juai, "jua", "table")
asserttype(jsoni, "json", "table")
asserttype(wi, "w", "table", true)
asserttype(ri, "r", "table")
jua = juai
await = juai.await
json = jsoni
w = wi
r = ri
end
local function prints(...)
local objs = {...}
for i, obj in ipairs(objs) do
print(textutils.serialize(obj))
end
end
local function url(call)
return httpEndpoint..call
end
local function api_request(cb, api, data)
local success, url, handle = await(r.request, url(api) .. (api:find("%%?") and "?cc" or "&cc"), {["Content-Type"]="application/json"}, data and json.encode(data))
if success then
cb(success, json.decode(handle.readAll()))
handle.close()
else
cb(success)
end
end
local function authorize_websocket(cb, privatekey)
asserttype(cb, "callback", "function")
asserttype(privatekey, "privatekey", "string", true)
api_request(function(success, data)
cb(success and data and data.ok, data.url and data.url:gsub("wss:", "ws:") or data)
end, "/ws/start", {
privatekey = privatekey
})
end
function address(cb, address)
asserttype(cb, "callback", "function")
asserttype(address, "address", "string")
api_request(function(success, data)
if data.address then
data.address.address = address
end
cb(success and data and data.ok, data.address or data)
end, "/addresses/"..address)
end
function addressTransactions(cb, address, limit, offset)
asserttype(cb, "callback", "function")
asserttype(address, "address", "string")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success and data and data.ok, data.transactions or data)
end, "/addresses/"..address.."/transactions?limit="..(limit or 50).."&offset="..(offset or 0))
end
function addressNames(cb, address)
asserttype(cb, "callback", "function")
asserttype(address, "address", "string")
api_request(function(success, data)
cb(success and data and data.ok, data.names or data)
end, "/addresses/"..address.."/names")
end
function addresses(cb, limit, offset)
asserttype(cb, "callback", "function")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success and data and data.ok, data.addresses or data)
end, "/addresses?limit="..(limit or 50).."&offset="..(offset or 0))
end
function rich(cb, limit, offset)
asserttype(cb, "callback", "function")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success and data and data.ok, data.addresses or data)
end, "/addresses/rich?limit="..(limit or 50).."&offset="..(offset or 0))
end
function transactions(cb, limit, offset)
asserttype(cb, "callback", "function")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success and data and data.ok, data.transactions or data)
end, "/transactions?limit="..(limit or 50).."&offset="..(offset or 0))
end
function latestTransactions(cb, limit, offset)
asserttype(cb, "callback", "function")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success and data and data.ok, data.transactions or data)
end, "/transactions/latest?limit="..(limit or 50).."&offset="..(offset or 0))
end
function transaction(cb, txid)
asserttype(cb, "callback", "function")
asserttype(txid, "txid", "number")
api_request(function(success, data)
cb(success and data and data.ok, data.transaction or data)
end, "/transactions/"..txid)
end
function makeTransaction(cb, privatekey, to, amount, metadata)
asserttype(cb, "callback", "function")
asserttype(privatekey, "privatekey", "string")
asserttype(to, "to", "string")
asserttype(amount, "amount", "number")
asserttype(metadata, "metadata", "string", true)
api_request(function(success, data)
cb(success and data and data.ok, data.transaction or data)
end, "/transactions", {
privatekey = privatekey,
to = to,
amount = amount,
metadata = metadata
})
end
local wsEventNameLookup = {
blocks = "block",
ownBlocks = "block",
transactions = "transaction",
ownTransactions = "transaction",
names = "name",
ownNames = "name",
ownWebhooks = "webhook",
motd = "motd"
}
local wsEvents = {}
local wsReqID = 0
local wsReqRegistry = {}
local wsEvtRegistry = {}
local wsHandleRegistry = {}
local function newWsID()
local id = wsReqID
wsReqID = wsReqID + 1
return id
end
local function registerEvent(id, event, callback)
if wsEvtRegistry[id] == nil then
wsEvtRegistry[id] = {}
end
if wsEvtRegistry[id][event] == nil then
wsEvtRegistry[id][event] = {}
end
table.insert(wsEvtRegistry[id][event], callback)
end
local function registerRequest(id, reqid, callback)
if wsReqRegistry[id] == nil then
wsReqRegistry[id] = {}
end
wsReqRegistry[id][reqid] = callback
end
local function discoverEvents(id, event)
local evs = {}
for k,v in pairs(wsEvtRegistry[id]) do
if k == event or string.match(k, event) or event == "*" then
for i,v2 in ipairs(v) do
table.insert(evs, v2)
end
end
end
return evs
end
wsEvents.success = function(id, handle)
-- fire success event
wsHandleRegistry[id] = handle
if wsEvtRegistry[id] then
local evs = discoverEvents(id, "success")
for i, v in ipairs(evs) do
v(id, handle)
end
end
end
wsEvents.failure = function(id)
-- fire failure event
if wsEvtRegistry[id] then
local evs = discoverEvents(id, "failure")
for i, v in ipairs(evs) do
v(id)
end
end
end
wsEvents.message = function(id, data)
local data = json.decode(data)
--print("msg:"..tostring(data.ok)..":"..tostring(data.type)..":"..tostring(data.id))
--prints(data)
-- handle events and responses
if wsReqRegistry[id] and wsReqRegistry[id][tonumber(data.id)] then
wsReqRegistry[id][tonumber(data.id)](data)
elseif wsEvtRegistry[id] then
local evs = discoverEvents(id, data.type)
for i, v in ipairs(evs) do
v(data)
end
if data.event then
local evs = discoverEvents(id, data.event)
for i, v in ipairs(evs) do
v(data)
end
end
local evs2 = discoverEvents(id, "message")
for i, v in ipairs(evs2) do
v(id, data)
end
end
end
wsEvents.closed = function(id)
-- fire closed event
if wsEvtRegistry[id] then
local evs = discoverEvents(id, "closed")
for i, v in ipairs(evs) do
v(id)
end
end
end
local function wsRequest(cb, id, type, data)
local reqID = newWsID()
registerRequest(id, reqID, function(data)
cb(data)
end)
data.id = tostring(reqID)
data.type = type
wsHandleRegistry[id].send(json.encode(data))
end
local function barebonesMixinHandle(id, handle)
handle.on = function(event, cb)
registerEvent(id, event, cb)
end
return handle
end
local function mixinHandle(id, handle)
handle.subscribe = function(cb, event, eventcb)
local data = await(wsRequest, id, "subscribe", {
event = event
})
registerEvent(id, wsEventNameLookup[event], eventcb)
cb(data.ok, data)
end
return barebonesMixinHandle(id, handle)
end
function connect(cb, privatekey, preconnect)
asserttype(cb, "callback", "function")
asserttype(privatekey, "privatekey", "string", true)
asserttype(preconnect, "preconnect", "function", true)
local url
if privatekey then
local success, auth = await(authorize_websocket, privatekey)
url = success and auth or wsEndpoint
end
local id = w.open(wsEvents, url)
if preconnect then
preconnect(id, barebonesMixinHandle(id, {}))
end
registerEvent(id, "success", function(id, handle)
cb(true, mixinHandle(id, handle))
end)
registerEvent(id, "failure", function(id)
cb(false)
end)
end
local domainMatch = "^([%l%d-_]*)@?([%l%d-]+).kst$"
local commonMetaMatch = "^(.+)=(.+)$"
function parseMeta(meta)
asserttype(meta, "meta", "string")
local tbl = {meta={}}
for m in meta:gmatch("[^;]+") do
if m:match(domainMatch) then
-- print("Matched domain")
local p1, p2 = m:match("([%l%d-_]*)@"), m:match("@?([%l%d-]+).kst")
tbl.name = p1
tbl.domain = p2
elseif m:match(commonMetaMatch) then
-- print("Matched common meta")
local p1, p2 = m:match(commonMetaMatch)
tbl.meta[p1] = p2
else
-- print("Unmatched standard meta")
table.insert(tbl.meta, m)
end
-- print(m)
end
-- print(textutils.serialize(tbl))
return tbl
end
local g = string.gsub
sha256 = loadstring(g(g(g(g(g(g(g(g('Sa=XbandSb=XbxWSc=XlshiftSd=unpackSe=2^32SYf(g,h)Si=g/2^hSj=i%1Ui-j+j*eVSYk(l,m)Sn=l/2^mUn-n%1VSo={0x6a09e667Tbb67ae85T3c6ef372Ta54ff53aT510e527fT9b05688cT1f83d9abT5be0cd19}Sp={0x428a2f98T71374491Tb5c0fbcfTe9b5dba5T3956c25bT59f111f1T923f82a4Tab1c5ed5Td807aa98T12835b01T243185beT550c7dc3T72be5d74T80deb1feT9bdc06a7Tc19bf174Te49b69c1Tefbe4786T0fc19dc6T240ca1ccT2de92c6fT4a7484aaT5cb0a9dcT76f988daT983e5152Ta831c66dTb00327c8Tbf597fc7Tc6e00bf3Td5a79147T06ca6351T14292967T27b70a85T2e1b2138T4d2c6dfcT53380d13T650a7354T766a0abbT81c2c92eT92722c85Ta2bfe8a1Ta81a664bTc24b8b70Tc76c51a3Td192e819Td6990624Tf40e3585T106aa070T19a4c116T1e376c08T2748774cT34b0bcb5T391c0cb3T4ed8aa4aT5b9cca4fT682e6ff3T748f82eeT78a5636fT84c87814T8cc70208T90befffaTa4506cebTbef9a3f7Tc67178f2}SYq(r,q)if e-1-r[1]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',"S"," local "),"T",",0x"),"U"," return "),"V"," end "),"W","or "),"X","bit32."),"Y","function "),"Z"," do "))()
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
function makev2address(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
function toKristWalletFormat(passphrase)
return sha256("KRISTWALLET"..passphrase).."-000"
end
return {
init = init,
address = address,
addressTransactions = addressTransactions,
addressNames = addressNames,
addresses = addresses,
rich = rich,
transactions = transactions,
latestTransactions = latestTransactions,
transaction = transaction,
makeTransaction = makeTransaction,
connect = connect,
parseMeta = parseMeta,
sha256 = sha256,
makeaddressbyte = makeaddressbyte,
makev2address = makev2address,
toKristWalletFormat = toKristWalletFormat
}

63
swshop/r.lua Normal file
View File

@@ -0,0 +1,63 @@
local jua = nil
local idPatt = "#R%d+"
callbackRegistry = {}
local function gfind(str, patt)
local t = {}
for found in str:gmatch(patt) do
table.insert(t, found)
end
if #t > 0 then
return t
else
return nil
end
end
local function findID(url)
local found = gfind(url, idPatt)
return tonumber(found[#found]:sub(found[#found]:find("%d+")))
end
local function newID()
return #callbackRegistry + 1
end
local function trimID(url)
local found = gfind(url, idPatt)
local s, e = url:find(found[#found])
return url:sub(1, s-1)
end
function request(callback, url, headers, postData)
local id = newID()
local newUrl = url .. "#R" .. id
http.request(newUrl, postData, headers)
callbackRegistry[id] = callback
end
function init(jua)
jua = jua
jua.on("http_success", function(event, url, handle)
local id = findID(url)
if callbackRegistry[id] then
callbackRegistry[id](true, trimID(url), handle)
table.remove(callbackRegistry, id)
end
end)
jua.on("http_failure", function(event, url, handle)
local id = findID(url)
if callbackRegistry[id] then
callbackRegistry[id](false, trimID(url), handle)
table.remove(callbackRegistry, id)
end
end)
end
return {
request = request,
init = init
}

119
swshop/shop.lua Normal file
View File

@@ -0,0 +1,119 @@
local programDir = fs.getDir(shell.getRunningProgram())
os.loadAPI(programDir .. '/'.. 'json')
local w = require("w")
local r = require("r")
local k = require("k")
local jua = require("jua")
local await = jua.await
local fs = _G.fs
local json = _G.json
local os = _G.os
local textutils = _G.textutils
r.init(jua)
w.init(jua)
k.init(jua, json, w, r)
local domain
local password
local privatekey
local address
jua.on("terminate", function()
jua.stop()
_G.printError("Terminated")
end)
local function getItemDetails(item)
local f = fs.open('usr/config/shop', "r")
if f then
local t = f.readAll()
f.close()
t = textutils.unserialize(t)
for k, v in pairs(t) do
if v.name == item then
return k, v.price
end
end
end
end
local function handleTransaction(transaction)
local from = transaction.from
local to = transaction.to
local value = transaction.value
if to ~= address or not transaction.metadata then return end
local metadata = k.parseMeta(transaction.metadata)
if not metadata.domain or metadata.domain ~= domain then return end
local recipient = metadata.meta and (metadata.meta["return"] or from) or from
print("Handling transaction from ", recipient)
print('purchase: ' .. tostring(metadata.name))
print('value: ' .. value)
local function refundTransaction(amount, reason)
print("Refunding to ", recipient)
await(k.makeTransaction, privatekey, recipient, amount, reason)
end
local itemId, price = getItemDetails(metadata.name)
if not itemId or not price then
print('invalid item')
--return refundTransaction(value, "error=Item specified is not valid")
return -- there could be multiple stores...
end
if value < price then
print('value too low')
return refundTransaction(value, "error=Please pay the price listed on-screen.")
end
local count = math.floor(value / price)
local uid = math.random()
os.queueEvent('store_provide', itemId, count, uid)
local timerId = os.startTimer(5)
while true do
local e, p1, p2 = os.pullEvent()
if e == 'timer' and p1 == timerId then
print('timed out waiting for provide')
refundTransaction(value, "error=Timed out attempting to provide items")
break
elseif e == 'store_provided' and p1 == uid then
local extra = value - (price * p2)
if extra > 0 then
print('extra: ' .. extra)
refundTransaction(extra, "message=Here's your change!")
end
break
end
end
end
jua.on('open_store', function(_, _domain, _password)
domain = _domain
password = _password
print('opening store for: ' .. domain)
privatekey = k.toKristWalletFormat(password)
address = k.makev2address(privatekey)
local success, ws = await(k.connect, privatekey)
assert(success, "Failed to get websocket URL")
print("Connected to websocket.")
success = await(ws.subscribe, "ownTransactions", function(data)
local transaction = data.transaction
handleTransaction(transaction)
end)
assert(success, "Failed to subscribe to event")
end)
jua.go(function()
print("Ready")
end)

134
swshop/w.lua Normal file
View File

@@ -0,0 +1,134 @@
local jua = nil
local idPatt = "#R%d+"
if not ((socket and socket.websocket) or http.websocketAsync) then
error("You do not have CC:Tweaked/CCTweaks installed or you are not on the latest version.")
end
local newws = socket and socket.websocket or http.websocketAsync
local async
if socket and socket.websocket then
async = false
else
async = true
end
callbackRegistry = {}
wsRegistry = {}
local function gfind(str, patt)
local t = {}
for found in str:gmatch(patt) do
table.insert(t, found)
end
if #t > 0 then
return t
else
return nil
end
end
local function findID(url)
local found = gfind(url, idPatt)
return tonumber(found[#found]:sub(found[#found]:find("%d+")))
end
local function newID()
return #callbackRegistry + 1
end
local function trimID(url)
local found = gfind(url, idPatt)
local s, e = url:find(found[#found])
return url:sub(1, s-1)
end
function open(callback, url, headers)
local id
if async then
id = newID()
end
local newUrl
if async then
newUrl = url .. "#R" .. id
newws(newUrl, headers)
else
if headers then
error("Websocket headers not supported under CCTweaks")
end
local ws = newws(url)
ws.send = ws.write
id = ws.id()
wsRegistry[id] = ws
end
callbackRegistry[id] = callback
return id
end
function init(jua)
jua = jua
if async then
jua.on("websocket_success", function(event, url, handle)
local id = findID(url)
if id and callbackRegistry[id] and callbackRegistry[id].success then
callbackRegistry[id].success(id, handle)
end
end)
jua.on("websocket_failure", function(event, url)
local id = findID(url)
if id and callbackRegistry[id] and callbackRegistry[id].failure then
callbackRegistry[id].failure(id)
end
table.remove(callbackRegistry, id)
end)
jua.on("websocket_message", function(event, url, data)
local id = findID(url)
if id and callbackRegistry[id] and callbackRegistry[id].message then
callbackRegistry[id].message(id, data)
end
end)
jua.on("websocket_closed", function(event, url)
local id = findID(url)
if id and callbackRegistry[id] and callbackRegistry[id].closed then
callbackRegistry[id].closed(id)
end
table.remove(callbackRegistry, id)
end)
else
jua.on("socket_connect", function(event, id)
if id and callbackRegistry[id] and callbackRegistry[id].success then
callbackRegistry[id].success(id, wsRegistry[id])
end
end)
jua.on("socket_error", function(event, id, msg)
if id and callbackRegistry[id] and callbackRegistry[id].failure then
callbackRegistry[id].failure(id, msg)
end
table.remove(callbackRegistry, id)
end)
jua.on("socket_message", function(event, id)
if id and callbackRegistry[id] and callbackRegistry[id].message then
local data = wsRegistry[id].read()
callbackRegistry[id].message(id, data)
end
end)
jua.on("socket_closed", function(event, id)
if id and callbackRegistry[id] and callbackRegistry[id].closed then
callbackRegistry[id].closed(id)
end
table.remove(callbackRegistry, id)
end)
end
end
return {
open = open,
init = init
}