milo cleanup + shop
This commit is contained in:
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ]]--
|
||||
|
||||
@@ -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
6
swshop/.package
Normal 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
209
swshop/json
Normal 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
122
swshop/jua.lua
Normal 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
413
swshop/k.lua
Normal 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
63
swshop/r.lua
Normal 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
119
swshop/shop.lua
Normal 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
134
swshop/w.lua
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user