Refactor mod to use the new HTTPS API.
This commit makes several changes: ¤ The mod (finally) uses the lurkcoinV3 API. ¤ Transactions to non-existent users are reverted (provided both servers are using the updated mod) · There is a small processing fee on transactions to invalid users (definitely not a bug) ¤ Fix non-minetest_game formspec prepends on ATM formspecs. ¤ Stop overriding minetest.log. ¤ Add luacheck workflow based on the one at https://github.com/BlockySurvival/bls_custom ¤ Fix errors reported by luacheck.
This commit is contained in:
parent
640fbae0b3
commit
50f5b41ccd
|
@ -0,0 +1,13 @@
|
||||||
|
name: luacheck
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
luacheck:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Run luacheck
|
||||||
|
uses: Roang-zero1/factorio-mod-luacheck@master
|
||||||
|
with:
|
||||||
|
luacheckrc_url: https://raw.githubusercontent.com/luk3yx/minetest-lurkcoin/master/.luacheckrc
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- Originally from bls_custom
|
||||||
|
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
|
globals = {
|
||||||
|
'atm',
|
||||||
|
'accounts',
|
||||||
|
'default',
|
||||||
|
'economy',
|
||||||
|
'lurkcoin',
|
||||||
|
'minetest',
|
||||||
|
'money',
|
||||||
|
'ItemStack'
|
||||||
|
}
|
||||||
|
|
||||||
|
read_globals = {
|
||||||
|
string = {fields = {'split', 'trim'}},
|
||||||
|
table = {fields = {'copy'}},
|
||||||
|
}
|
90
atm-core.lua
90
atm-core.lua
|
@ -14,30 +14,34 @@ local formspecs = {}
|
||||||
|
|
||||||
-- 0.4 compatibility
|
-- 0.4 compatibility
|
||||||
lurkcoin.formspec_prepend = ''
|
lurkcoin.formspec_prepend = ''
|
||||||
if minetest.get_modpath('default') and rawget(_G, 'default') and
|
if minetest.global_exists('default') and default.gui_bg and
|
||||||
default.gui_bg and default.gui_bg_img and default.gui_slots then
|
default.gui_bg_img and default.gui_slots then
|
||||||
lurkcoin.formspec_prepend = default.gui_bg .. default.gui_bg_img ..
|
lurkcoin.formspec_prepend = default.gui_bg .. default.gui_bg_img ..
|
||||||
default.gui_slots
|
default.gui_slots
|
||||||
end
|
end
|
||||||
|
|
||||||
-- The formspec code is based on something random I did in 2017(?) for
|
local function centre_label(pos, label)
|
||||||
-- lurkcoinV1, formspecs are weird and I somehow got it right™ then.
|
return 'image_button[' .. pos .. ';blank.png;;' .. e(label) ..
|
||||||
|
';true;false;]'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The formspec code is based on something I did in 2017(?) for lurkcoinV1,
|
||||||
|
-- formspecs are weird and I somehow got it right then.
|
||||||
local function get_formspec(name, page, params)
|
local function get_formspec(name, page, params)
|
||||||
-- The formspec template
|
-- The formspec template
|
||||||
local formspec = 'size[8,9;]' .. lurkcoin.formspec_prepend ..
|
local formspec = 'size[8,9]' .. lurkcoin.formspec_prepend ..
|
||||||
'label[0.5,1.75;Your balance: ' ..
|
'label[0.5,1.75;Your balance: ' ..
|
||||||
e(lurkcoin.bank.getbal(name)) .. 'cr.]' ..
|
e(lurkcoin.bank.getbal(name)) .. 'cr.]' ..
|
||||||
'image_button[2,0.55;4,0.5;default_dirt.png^\\[colorize:#343434;y;' ..
|
centre_label('1,0.55;6,0.5', 'Welcome to a ' .. lurkcoin.server_name ..
|
||||||
'Welcome to a ' .. e(lurkcoin.server_name) .. ' ATM!]' ..
|
'ATM!') ..
|
||||||
'label[0.5,2.25;Exchange rate: \194\1641.00 is equal to ' ..
|
'label[0.5,2.25;Exchange rate: \194\1641.00 is equal to ' ..
|
||||||
e(lurkcoin.exchange_rate) .. 'cr.]' ..
|
e(lurkcoin.exchange_rate) .. 'cr.]' ..
|
||||||
'image_button[1.75,1.05;4.5,0.5;default_dirt.png^\\[colorize:' ..
|
centre_label('1.75,1.05;4.5,0.5', 'Your account: ' .. name) ..
|
||||||
'#343434;y; Your account: ' .. e(name) .. ']' ..
|
'image[0.5,0.5;1,1;default_mese_crystal.png]' ..
|
||||||
'image[0.5,0.5;1,1;default_mese_crystal.png]' ..
|
|
||||||
'image[6.5,0.5;1,1;default_mese_crystal.png]'
|
'image[6.5,0.5;1,1;default_mese_crystal.png]'
|
||||||
|
|
||||||
-- Get the page formspec
|
-- Get the page formspec
|
||||||
local page = formspecs[page] or formspecs.main
|
page = formspecs[page] or formspecs.main
|
||||||
if type(page) == 'string' then
|
if type(page) == 'string' then
|
||||||
formspec = formspec .. page
|
formspec = formspec .. page
|
||||||
elseif type(page) == 'function' then
|
elseif type(page) == 'function' then
|
||||||
|
@ -52,7 +56,7 @@ end
|
||||||
local withdrawls = false
|
local withdrawls = false
|
||||||
|
|
||||||
-- Payment screen
|
-- Payment screen
|
||||||
function formspecs.pay(name, fields, guessed_amount)
|
function formspecs.pay(name, fields)
|
||||||
fields = fields or {}
|
fields = fields or {}
|
||||||
local formspec =
|
local formspec =
|
||||||
'field[0.8,3.5;7,1;user;User to pay;' .. e(fields.user or '') .. ']' ..
|
'field[0.8,3.5;7,1;user;User to pay;' .. e(fields.user or '') .. ']' ..
|
||||||
|
@ -93,7 +97,7 @@ function formspecs.pay(name, fields, guessed_amount)
|
||||||
|
|
||||||
exc = tostring(math.floor(exc * 100) / 100)
|
exc = tostring(math.floor(exc * 100) / 100)
|
||||||
formspec = formspec .. '\n' .. fields.amount .. 'cr is ' ..
|
formspec = formspec .. '\n' .. fields.amount .. 'cr is ' ..
|
||||||
'equal to \194\164' .. exc .. '.'
|
'equal to ' .. exc .. '.'
|
||||||
end
|
end
|
||||||
formspec = formspec .. ']' ..
|
formspec = formspec .. ']' ..
|
||||||
'button[0.5,8;3.5,1;payuser;Cancel]' ..
|
'button[0.5,8;3.5,1;payuser;Cancel]' ..
|
||||||
|
@ -140,16 +144,20 @@ if minetest.get_modpath('currency') then
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Remove non-registered notes
|
-- Remove non-registered notes
|
||||||
|
local remove = {}
|
||||||
for id, note in pairs(withdrawls) do
|
for id, note in pairs(withdrawls) do
|
||||||
if not minetest.registered_items[note] then
|
if not minetest.registered_items[note] then
|
||||||
withdrawls[id] = nil
|
table.insert(remove, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
for _, id in ipairs(remove) do
|
||||||
|
withdrawls[id] = nil
|
||||||
|
end
|
||||||
assert(#withdrawls > 0, 'The "currency" mod did not register any notes!')
|
assert(#withdrawls > 0, 'The "currency" mod did not register any notes!')
|
||||||
|
|
||||||
-- Create a detached inventory for depositing
|
-- Create a detached inventory for depositing
|
||||||
local inv = minetest.create_detached_inventory('lurkcoin:atm_deposit', {
|
local inv = minetest.create_detached_inventory('lurkcoin:atm_deposit', {
|
||||||
on_put = function(inv, listname, index, stack, player)
|
on_put = function(inv, listname, _, stack, player)
|
||||||
local name = stack:get_name()
|
local name = stack:get_name()
|
||||||
if name:sub(1, 17) == 'currency:minegeld' then
|
if name:sub(1, 17) == 'currency:minegeld' then
|
||||||
local m = name:sub(19)
|
local m = name:sub(19)
|
||||||
|
@ -157,6 +165,8 @@ if minetest.get_modpath('currency') then
|
||||||
if m:sub(1, 5) == 'cent_' then
|
if m:sub(1, 5) == 'cent_' then
|
||||||
m = tonumber(m:sub(6))
|
m = tonumber(m:sub(6))
|
||||||
if m then m = m / 100 end
|
if m then m = m / 100 end
|
||||||
|
elseif m == 'bundle' then
|
||||||
|
m = 0.05 * 9
|
||||||
else
|
else
|
||||||
m = tonumber(m)
|
m = tonumber(m)
|
||||||
end
|
end
|
||||||
|
@ -170,16 +180,16 @@ if minetest.get_modpath('currency') then
|
||||||
if not lurkcoin.bank.add(pname, m, 'Deposit') then
|
if not lurkcoin.bank.add(pname, m, 'Deposit') then
|
||||||
player:get_inventory():add_item('main', stack)
|
player:get_inventory():add_item('main', stack)
|
||||||
end
|
end
|
||||||
core.log('action', 'Player ' .. pname .. ' deposits ' ..
|
minetest.log('action', 'Player ' .. pname ..
|
||||||
tostring(m) .. 'Mg (' .. stack:to_string() ..
|
' deposits ' .. tostring(m) .. 'Mg (' ..
|
||||||
') into a lurkcoin ATM.')
|
stack:to_string() .. ') into a lurkcoin ATM.')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
inv:set_list(listname, {})
|
inv:set_list(listname, {})
|
||||||
lurkcoin.show_atm(player:get_player_name(), 'deposit')
|
lurkcoin.show_atm(player:get_player_name(), 'deposit')
|
||||||
end,
|
end,
|
||||||
|
|
||||||
allow_put = function(inv, listname, index, stack, player)
|
allow_put = function(_, _, _, stack, _)
|
||||||
local name = stack:get_name()
|
local name = stack:get_name()
|
||||||
if name:sub(1, 17) == 'currency:minegeld' then
|
if name:sub(1, 17) == 'currency:minegeld' then
|
||||||
return stack:get_count()
|
return stack:get_count()
|
||||||
|
@ -198,8 +208,7 @@ if minetest.get_modpath('currency') then
|
||||||
'list[current_player;main;0,6.08;8,3;8]' ..
|
'list[current_player;main;0,6.08;8,3;8]' ..
|
||||||
'list[detached:lurkcoin:atm_deposit;lurkcoin;3.5,3;1,1;]' ..
|
'list[detached:lurkcoin:atm_deposit;lurkcoin;3.5,3;1,1;]' ..
|
||||||
'listring[]' ..
|
'listring[]' ..
|
||||||
'image_button[0,3;3.5,1;default_dirt.png^\\[colorize:#343434;' ..
|
centre_label('0,3;3.5,1', 'Deposit money here →') ..
|
||||||
'ignore;Deposit money here →]' ..
|
|
||||||
'button[5.25,3;2,1;home;Finish]'
|
'button[5.25,3;2,1;home;Finish]'
|
||||||
else
|
else
|
||||||
-- When there is no physical currency, the only thing you can do is pay
|
-- When there is no physical currency, the only thing you can do is pay
|
||||||
|
@ -208,19 +217,17 @@ else
|
||||||
end
|
end
|
||||||
|
|
||||||
-- "Transaction accepted" screen
|
-- "Transaction accepted" screen
|
||||||
function formspecs.success(name, text)
|
function formspecs.success(_, text)
|
||||||
return 'image[2.1,3;4.5,4.5;lurkcoin_success.png]' ..
|
return 'image[2.1,3;4.5,4.5;lurkcoin_success.png]' ..
|
||||||
'image_button[1,7;6,1;default_dirt.png^\\[colorize:#343434;' ..
|
centre_label('1,7;6,1', text or 'Transaction sent!') ..
|
||||||
'ignore;' .. e(text or 'Transaction sent!') .. ']' ..
|
|
||||||
'button[0.5,8;3.5,1;home;Go back]' ..
|
'button[0.5,8;3.5,1;home;Go back]' ..
|
||||||
'button_exit[4,8;3.5,1;quit;Quit]'
|
'button_exit[4,8;3.5,1;quit;Quit]'
|
||||||
end
|
end
|
||||||
|
|
||||||
-- "Transaction failed" screen
|
-- "Transaction failed" screen
|
||||||
function formspecs.error(name, text)
|
function formspecs.error(_, text)
|
||||||
return 'image[2.1,3;4.5,4.5;lurkcoin_error.png]' ..
|
return 'image[2.1,3;4.5,4.5;lurkcoin_error.png]' ..
|
||||||
'image_button[1,7;6,1;default_dirt.png^\\[colorize:#343434;' ..
|
centre_label('1,7;6,1', text or 'An error has occurred!') ..
|
||||||
'ignore;' .. e(text or 'An error has occurred!') .. ']' ..
|
|
||||||
'button[0.5,8;3.5,1;home;Go back]' ..
|
'button[0.5,8;3.5,1;home;Go back]' ..
|
||||||
'button_exit[4,8;3.5,1;quit;Quit]'
|
'button_exit[4,8;3.5,1;quit;Quit]'
|
||||||
end
|
end
|
||||||
|
@ -228,10 +235,8 @@ end
|
||||||
-- Processing screen
|
-- Processing screen
|
||||||
formspecs.processing =
|
formspecs.processing =
|
||||||
'image[2.1,3;4.5,4.5;lurkcoin_processing.png]' ..
|
'image[2.1,3;4.5,4.5;lurkcoin_processing.png]' ..
|
||||||
'image_button[1,7;6,1;default_dirt.png^\\[colorize:#343434;' ..
|
centre_label('1,7;6,1', 'Processing your transaction...') ..
|
||||||
'ignore;Processing your transaction...]' ..
|
centre_label('1,8;6,1', 'This should only take a few seconds.')
|
||||||
'image_button[1,8;6,1;default_dirt.png^\\[colorize:#343434;' ..
|
|
||||||
'ignore;This should only take a few seconds.]'
|
|
||||||
|
|
||||||
-- A wrapper function
|
-- A wrapper function
|
||||||
function lurkcoin.show_atm(name, page, params)
|
function lurkcoin.show_atm(name, page, params)
|
||||||
|
@ -267,10 +272,12 @@ minetest.register_on_player_receive_fields(function(player, formname, raw)
|
||||||
'ERROR: You cannot afford to do that!')
|
'ERROR: You cannot afford to do that!')
|
||||||
end
|
end
|
||||||
|
|
||||||
local note = false
|
local note
|
||||||
for id, item in pairs(withdrawls) do
|
for id, item in pairs(withdrawls) do
|
||||||
|
-- HACK: Multiply both amount and id by 1000 to avoid
|
||||||
|
-- floating-point bugs.
|
||||||
if (not note or id > note) and
|
if (not note or id > note) and
|
||||||
math.floor(amount / id) * id == amount then
|
(amount * 1000) % (id * 1000) == 0 then
|
||||||
local def = minetest.registered_items[item]
|
local def = minetest.registered_items[item]
|
||||||
if def and amount / id <= (def.stack_max or 99) then
|
if def and amount / id <= (def.stack_max or 99) then
|
||||||
note = id
|
note = id
|
||||||
|
@ -286,7 +293,7 @@ minetest.register_on_player_receive_fields(function(player, formname, raw)
|
||||||
|
|
||||||
local stack = ItemStack({
|
local stack = ItemStack({
|
||||||
name = withdrawls[note],
|
name = withdrawls[note],
|
||||||
count = amount / note,
|
count = (amount * 1000) / (note * 1000),
|
||||||
})
|
})
|
||||||
local inv = player:get_inventory()
|
local inv = player:get_inventory()
|
||||||
if not inv:room_for_item('main', stack) then
|
if not inv:room_for_item('main', stack) then
|
||||||
|
@ -372,14 +379,5 @@ minetest.register_on_player_receive_fields(function(player, formname, raw)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
minetest.register_on_leaveplayer(function(player)
|
minetest.register_on_leaveplayer(function(player)
|
||||||
-- TODO: I forgot what one register_on_leaveplayer uses and am too lazy to
|
open_atms[player:get_player_name()] = nil
|
||||||
-- check.
|
|
||||||
local name
|
|
||||||
if type(player) == 'string' then
|
|
||||||
name = player
|
|
||||||
else
|
|
||||||
name = player:get_player_name()
|
|
||||||
end
|
|
||||||
|
|
||||||
open_atms[name] = nil
|
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -22,7 +22,7 @@ minetest.register_node('lurkcoin:atm', {
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
on_rightclick = function(_, _, clicker, _, _)
|
||||||
return lurkcoin.show_atm(clicker:get_player_name())
|
return lurkcoin.show_atm(clicker:get_player_name())
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
254
core.lua
254
core.lua
|
@ -4,28 +4,40 @@
|
||||||
-- © 2019 by luk3yx
|
-- © 2019 by luk3yx
|
||||||
--
|
--
|
||||||
|
|
||||||
lurkcoin.version = 0
|
-- Change this if you are hosting your own lurkcoin-core instance
|
||||||
|
local baseurl = 'https://us.xeroxirc.net:7000'
|
||||||
|
|
||||||
|
lurkcoin.version = 3
|
||||||
lurkcoin.timeout = 10
|
lurkcoin.timeout = 10
|
||||||
lurkcoin.exchange_rate = 1
|
lurkcoin.exchange_rate = 1
|
||||||
|
|
||||||
-- Do not allow other mods to modify this, or they may be able to bypass mod
|
local function log(level, text)
|
||||||
-- security restrictions!
|
if text then
|
||||||
local baseurl = 'https://us.xeroxirc.net:7000/v2'
|
text = text:gsub('[\r\n]', ' ')
|
||||||
|
else
|
||||||
|
level, text = 'action', level:gsub('[\r\n]', ' ')
|
||||||
|
end
|
||||||
|
return minetest.log(level, '[lurkcoin] ' .. text)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function logf(text, ...)
|
||||||
|
return log(text:format(...))
|
||||||
|
end
|
||||||
|
|
||||||
-- Get the username and API token
|
-- Get the username and API token
|
||||||
lurkcoin.server_name = minetest.settings:get('lurkcoin.username')
|
lurkcoin.server_name = minetest.settings:get('lurkcoin.username')
|
||||||
local token = minetest.settings:get('lurkcoin.token')
|
local token = minetest.settings:get('lurkcoin.token')
|
||||||
|
|
||||||
-- Make sure lurkcoin.server_name exists
|
-- Make sure lurkcoin.server_name exists
|
||||||
if not lurkcoin.server_name then
|
if not lurkcoin.server_name then
|
||||||
lurkcoin.server_name = '<unknown>'
|
lurkcoin.server_name = '<unknown>'
|
||||||
minetest.log('warning', 'lurkcoin has no server name set!')
|
log('warning', 'lurkcoin.server_name is not set!')
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Make sure the HTTP API exists
|
-- Make sure the HTTP API exists
|
||||||
local http = ...
|
local http = ...
|
||||||
if not http then
|
if not http then
|
||||||
minetest.log('warning', 'lurkcoin is not allowed to use the HTTP API! ' ..
|
log('warning', 'lurkcoin is not allowed to use the HTTP API! ' ..
|
||||||
'Please add lurkcoin to secure.http_mods in minetest.conf.')
|
'Please add lurkcoin to secure.http_mods in minetest.conf.')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -34,177 +46,185 @@ end
|
||||||
lurkcoin.user_agent = 'Minetest ' .. minetest.get_version().string ..
|
lurkcoin.user_agent = 'Minetest ' .. minetest.get_version().string ..
|
||||||
' (with lurkcoin mod v' .. tostring(lurkcoin.version) .. ')'
|
' (with lurkcoin mod v' .. tostring(lurkcoin.version) .. ')'
|
||||||
|
|
||||||
|
-- Create the HTTP header list
|
||||||
|
local headers
|
||||||
|
if token then
|
||||||
|
local raw = lurkcoin.server_name:gsub(':', '_') .. ':' .. token
|
||||||
|
local auth = minetest.encode_base64(raw)
|
||||||
|
headers = {
|
||||||
|
-- minetest.encode_base64() doesn't add padding
|
||||||
|
'Authorization: Basic ' .. auth .. ('='):rep(4 - #auth % 4),
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'X-Force-OK: true'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function E(code, msg)
|
||||||
|
return {sucess = false, error = code, message = msg}
|
||||||
|
end
|
||||||
|
|
||||||
-- Download functions
|
-- Download functions
|
||||||
local function get(url, data, callback)
|
local function get(url, data, callback)
|
||||||
-- To prevent race conditions, these callbacks wait until at least the next
|
-- To prevent race conditions, these callbacks wait until at least the next
|
||||||
-- globalstep.
|
-- globalstep.
|
||||||
if not data then
|
if not http then
|
||||||
data = {}
|
minetest.after(0, callback, E('ERR_CONNECTIONFAILED',
|
||||||
elseif not http then
|
'The lurkcoin mod is not in secure.http_mods!'))
|
||||||
minetest.after(0, callback, {
|
|
||||||
completed = true,
|
|
||||||
succeeded = false,
|
|
||||||
timeout = true,
|
|
||||||
code = 500,
|
|
||||||
data = 'ERROR: The lurkcoin mod is not in secure.http_mods!'
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
elseif not lurkcoin.server_name or not token then
|
elseif not headers then
|
||||||
minetest.after(0, callback, {
|
minetest.after(0, callback, E('ERR_INVALIDLOGIN',
|
||||||
completed = true,
|
'The lurkcoin mod does not have (correct) credentials!'))
|
||||||
succeeded = false,
|
|
||||||
timeout = true,
|
|
||||||
code = 401,
|
|
||||||
data = 'ERROR: The lurkcoin mod does not have (correct) ' ..
|
|
||||||
'account credentials!'
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
data.name = lurkcoin.server_name
|
|
||||||
data.token = token
|
|
||||||
|
|
||||||
-- Minetest eats any non-200 code.
|
|
||||||
data.force_200 = '200'
|
|
||||||
|
|
||||||
http.fetch({
|
http.fetch({
|
||||||
url = baseurl .. '/' .. url,
|
url = baseurl .. '/v3/' .. url,
|
||||||
timeout = lurkcoin.timeout,
|
timeout = lurkcoin.timeout,
|
||||||
post_data = data,
|
post_data = data and minetest.write_json(data),
|
||||||
user_agent = lurkcoin.user_agent,
|
user_agent = lurkcoin.user_agent,
|
||||||
}, function(res)
|
extra_headers = headers
|
||||||
if res.timeout then
|
}, function(http_res)
|
||||||
minetest.log('warning', '[lurkcoin] Could not connect to lurkcoin!')
|
local res
|
||||||
res.code = 500
|
if http_res.timeout then
|
||||||
res.data = 'ERROR: Could not connect to lurkcoin!'
|
log('warning', 'Could not connect to lurkcoin!')
|
||||||
elseif res.code == 401 or res.code == 418 then
|
res = E('ERR_CONNECTIONFAILED', 'Could not connect to lurkcoin!')
|
||||||
minetest.log('warning', '[lurkcoin] Invalid username or API token!')
|
else
|
||||||
lurkcoin.server_name, token = '<unknown>', nil
|
res = minetest.parse_json(http_res.data)
|
||||||
elseif res.code == 200 and res.data:sub(1, 7) == 'ERROR: ' then
|
if type(data) ~= 'table' then data = nil end
|
||||||
res.code = 501
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local success, msg = pcall(callback, res)
|
local ok, msg = pcall(callback, res or E('ERR_UNKNOWNERROR', '???'))
|
||||||
if not success then
|
if not ok then
|
||||||
minetest.log('error', '[lurkcoin] Error: ' .. msg)
|
log('error', msg)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Process incoming transactions.
|
-- Process incoming transactions.
|
||||||
local handled_transactions = {}
|
local acknowledged_transactions = {}
|
||||||
|
|
||||||
local function _sync(res)
|
local function sync_callback(res)
|
||||||
if res.code == 200 then
|
if not res.success then return end
|
||||||
local data = minetest.parse_json(res.data)
|
|
||||||
|
|
||||||
-- Update the exchange rate
|
-- Process any unprocessed transactions
|
||||||
assert(type(data.exchange_rate) == 'number')
|
local reject = {}
|
||||||
assert(data.exchange_rate == data.exchange_rate)
|
for _, t in ipairs(res.result) do
|
||||||
lurkcoin.exchange_rate = data.exchange_rate
|
local id = t.id
|
||||||
|
if not acknowledged_transactions[id] then
|
||||||
-- Process any unprocessed transactions
|
if lurkcoin.bank.user_exists(t.target) then
|
||||||
for _, t in ipairs(data.transactions) do
|
acknowledged_transactions[id] = true
|
||||||
assert(type(t[3]) == 'number')
|
logf('[%s] \194\164%s (sent %s, received %scr) - ' ..
|
||||||
local id = minetest.serialize(t)
|
'Transaction from %q on %q to %q.',
|
||||||
if not handled_transactions[id] then
|
t.id, t.amount, t.sent_amount, t.received_amount, t.source,
|
||||||
handled_transactions[id] = true
|
t.source_server, t.target)
|
||||||
core.log('action', '[lurkcoin] ' .. t[4])
|
lurkcoin.bank.add(t.target, t.received_amount)
|
||||||
lurkcoin.bank.add(t[2], t[3])
|
else
|
||||||
|
logf('Rejecting transaction %s, user %q does not exist.',
|
||||||
|
t.id, t.target)
|
||||||
|
table.insert(reject, t.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Tell lurkcoin to remove processed transactions
|
-- Acknowledge transactions (if any)
|
||||||
if #data.transactions > 0 then
|
if next(acknowledged_transactions) then
|
||||||
get('remove_transactions', {count = tostring(#data.transactions)},
|
local ack = {}
|
||||||
function(res)
|
for id, _ in pairs(acknowledged_transactions) do
|
||||||
if res.code == 200 then
|
table.insert(ack, id)
|
||||||
for _, t in ipairs(data.transactions) do
|
|
||||||
handled_transactions[minetest.serialize(t)] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
get('acknowledge_transactions', {transactions = ack}, function(res2)
|
||||||
|
if res2.success then
|
||||||
|
for _, id in ipairs(ack) do
|
||||||
|
acknowledged_transactions[id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reject transactions (if any)
|
||||||
|
if #reject > 0 then
|
||||||
|
get('reject_transactions', {transactions = reject}, function() end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Periodically sync with lurkcoin.
|
-- Periodically sync with lurkcoin.
|
||||||
local function sync()
|
local function sync()
|
||||||
get('get_transactions', {as_object = 'true'}, _sync)
|
get('pending_transactions', nil, sync_callback)
|
||||||
|
|
||||||
|
-- Only set lurkcoin.exchange_rate once
|
||||||
|
if not lurkcoin.exchange_rate then
|
||||||
|
get('exchange_rates', {source = lurkcoin.server_name, target = '',
|
||||||
|
amount = 1}, function(res)
|
||||||
|
if res.success and type(res.result) == 'number' then
|
||||||
|
lurkcoin.exchange_rate = res.result
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
minetest.after(300, sync)
|
minetest.after(300, sync)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Start syncing once the game is loaded.
|
|
||||||
minetest.after(0, sync)
|
minetest.after(0, sync)
|
||||||
|
|
||||||
-- Get an exchange rate
|
-- Get an exchange rate
|
||||||
function lurkcoin.get_exchange_rate(amount, to, callback)
|
function lurkcoin.get_exchange_rate(amount, target, callback)
|
||||||
assert(callback)
|
assert(callback)
|
||||||
amount = amount and tonumber(amount)
|
|
||||||
if not amount or amount ~= amount then return callback(nil) end
|
|
||||||
|
|
||||||
get('exchange_rates', {
|
get('exchange_rates', {
|
||||||
from = lurkcoin.server_name,
|
source = lurkcoin.server_name,
|
||||||
to = to or 'lurkcoin',
|
target = target or '',
|
||||||
amount = tostring(amount),
|
amount = amount,
|
||||||
}, function(res)
|
}, function(res)
|
||||||
if res.code == 200 then
|
if res.success then
|
||||||
local amount = tonumber(res.data)
|
callback(res.result, nil)
|
||||||
if amount == amount then return callback(amount, nil) end
|
elseif res.error == 'ERR_TARGETSERVERNOTFOUND' then
|
||||||
end
|
callback(nil, 'That server does not exist!')
|
||||||
local msg
|
|
||||||
if res.code == 502 then
|
|
||||||
msg = 'That server does not exist!'
|
|
||||||
else
|
else
|
||||||
msg = res.data
|
callback(nil, tostring(res.message))
|
||||||
end
|
end
|
||||||
return callback(nil, tostring(msg))
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Pay a user (cross-server)
|
-- Pay a user (cross-server)
|
||||||
function lurkcoin.pay(from, to, server, amount, callback)
|
function lurkcoin.pay(source, target, target_server, amount, callback)
|
||||||
assert(type(amount) == 'number' and callback)
|
assert(type(amount) == 'number' and callback)
|
||||||
|
|
||||||
-- Run lurkcoin.bank.pay() if this is not a cross-server transaction.
|
-- Run lurkcoin.bank.pay() if this is not a cross-server transaction.
|
||||||
if not server or server == '' or server:lower() ==
|
if not target_server or target_server == '' or
|
||||||
lurkcoin.server_name:lower() then
|
target_server:lower() == lurkcoin.server_name:lower() then
|
||||||
return callback(lurkcoin.bank.pay(from, to, amount))
|
return callback(lurkcoin.bank.pay(source, target, amount))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sanity checks
|
-- Sanity checks
|
||||||
amount = math.floor(amount * 100) / 100
|
amount = math.floor(amount * 100) / 100
|
||||||
if not lurkcoin.bank.user_exists(from) then
|
if not lurkcoin.bank.user_exists(source) then
|
||||||
return callback(false, 'ERROR: The "from" user does not exist!')
|
return callback(false, 'ERROR: The "from" user does not exist!')
|
||||||
elseif amount ~= amount or amount <= 0 then
|
elseif amount ~= amount or amount <= 0 then
|
||||||
return callback(false, 'ERROR: Invalid number!')
|
return callback(false, 'ERROR: Invalid number!')
|
||||||
elseif lurkcoin.bank.getbal(from) - amount < 0 then
|
elseif amount > lurkcoin.bank.getbal(source) then
|
||||||
return callback(false, 'ERROR: You cannot afford to do that!')
|
return callback(false, 'ERROR: You cannot afford to do that!')
|
||||||
end
|
end
|
||||||
|
|
||||||
if not lurkcoin.bank.subtract(from, amount, 'Transaction to "' .. to ..
|
if not lurkcoin.bank.subtract(source, amount, 'Transaction to "' ..
|
||||||
'" on server "' .. server .. '".') then
|
source .. '" on server "' .. target_server .. '".') then
|
||||||
return callback(false, 'ERROR: Transaction failed!')
|
return callback(false, 'ERROR: Transaction failed!')
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Send the request
|
-- Send the request
|
||||||
return get('pay', {
|
return get('pay', {
|
||||||
target = to,
|
source = source,
|
||||||
server = server,
|
target = target,
|
||||||
amount = tostring(amount),
|
target_server = target_server,
|
||||||
local_currency = 'true'
|
amount = amount,
|
||||||
|
local_currency = true
|
||||||
}, function(res)
|
}, function(res)
|
||||||
if res.code ~= 200 then
|
if res.success then
|
||||||
lurkcoin.bank.add(from, amount, 'Reverting failed transaction.')
|
logf('User %q paid %q (on server %q) %scr.', source, target,
|
||||||
if res.data == 'ERROR: You cannot afford to do that!' then
|
target_server, amount)
|
||||||
res.data = 'ERROR: This server cannot afford to do that!'
|
return callback(true, 'Transaction sent!')
|
||||||
end
|
|
||||||
else
|
|
||||||
minetest.log('action', '[lurkcoin] User ' .. from .. ' paid ' ..
|
|
||||||
to .. ' (on server ' .. server .. ') ' .. tostring(amount) .. 'cr.')
|
|
||||||
end
|
end
|
||||||
|
lurkcoin.bank.add(source, amount, 'Reverting failed transaction.')
|
||||||
return callback(res.code == 200, res.data or 'ERROR: Unknown error!')
|
if res.code == 'ERR_CANNOTAFFORD' then
|
||||||
|
res.message = 'This server cannot afford to do that!'
|
||||||
|
end
|
||||||
|
return callback(false, 'ERROR: ' .. tostring(res.message))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
24
init.lua
24
init.lua
|
@ -26,30 +26,6 @@ dofile(modpath .. '/atm-core.lua')
|
||||||
-- Load the ATM blocks
|
-- Load the ATM blocks
|
||||||
dofile(modpath .. '/atm-nodes.lua')
|
dofile(modpath .. '/atm-nodes.lua')
|
||||||
|
|
||||||
-- Backport https://github.com/minetest/minetest/pull/8420 if required
|
|
||||||
if not minetest.get_modpath('cloaking') or not cloaking.hide_player then
|
|
||||||
table.insert(minetest.registered_on_chat_messages, 1, function(name, msg)
|
|
||||||
if msg:find('[\r\n]') then
|
|
||||||
minetest.chat_send_player(name,
|
|
||||||
'New lines are not permitted in chat messages')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Also tweak minetest.log because of paranoia.
|
|
||||||
local log = minetest.log
|
|
||||||
function minetest.log(level, text)
|
|
||||||
level = level:gsub('[\r\n]', ' ')
|
|
||||||
if text then
|
|
||||||
text = text:gsub('[\r\n]', ' ')
|
|
||||||
else
|
|
||||||
text = level
|
|
||||||
level = 'none'
|
|
||||||
end
|
|
||||||
return log(level, text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Display loaded message
|
-- Display loaded message
|
||||||
minetest.log('action', '[lurkcoin] Loaded on server "' ..
|
minetest.log('action', '[lurkcoin] Loaded on server "' ..
|
||||||
lurkcoin.server_name .. '".')
|
lurkcoin.server_name .. '".')
|
||||||
|
|
Loading…
Reference in New Issue