You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
6.8KB

  1. --
  2. -- Minetest lurkcoin mod - Core
  3. --
  4. -- © 2019 by luk3yx
  5. --
  6. lurkcoin.version = 0
  7. lurkcoin.timeout = 10
  8. lurkcoin.exchange_rate = 1
  9. -- Do not allow other mods to modify this, or they may be able to bypass mod
  10. -- security restrictions!
  11. local baseurl = 'https://nz.xeroxirc.net:7000/v2'
  12. -- Get the username and API token
  13. lurkcoin.server_name = minetest.settings:get('lurkcoin.username')
  14. local token = minetest.settings:get('lurkcoin.token')
  15. -- Make sure lurkcoin.server_name exists
  16. if not lurkcoin.server_name then
  17. lurkcoin.server_name = '<unknown>'
  18. minetest.log('warning', 'lurkcoin has no server name set!')
  19. end
  20. -- Make sure the HTTP API exists
  21. local http = ...
  22. if not http then
  23. minetest.log('warning', 'lurkcoin is not allowed to use the HTTP API! ' ..
  24. 'Please add lurkcoin to secure.http_mods in minetest.conf.')
  25. end
  26. -- This user agent is not strictly required, however I might do something
  27. -- with the lurkcoin mod version later.
  28. lurkcoin.user_agent = 'Minetest ' .. minetest.get_version().string ..
  29. ' (with lurkcoin mod v' .. tostring(lurkcoin.version) .. ')'
  30. -- Download functions
  31. local function get(url, data, callback)
  32. -- To prevent race conditions, these callbacks wait until at least the next
  33. -- globalstep.
  34. if not data then
  35. data = {}
  36. elseif not http then
  37. minetest.after(0, callback, {
  38. completed = true,
  39. succeeded = false,
  40. timeout = true,
  41. code = 500,
  42. data = 'ERROR: The lurkcoin mod is not in secure.http_mods!'
  43. })
  44. return
  45. elseif not lurkcoin.server_name or not token then
  46. minetest.after(0, callback, {
  47. completed = true,
  48. succeeded = false,
  49. timeout = true,
  50. code = 401,
  51. data = 'ERROR: The lurkcoin mod does not have (correct) ' ..
  52. 'account credentials!'
  53. })
  54. return
  55. end
  56. data.name = lurkcoin.server_name
  57. data.token = token
  58. -- Minetest eats any non-200 code.
  59. data.force_200 = '200'
  60. http.fetch({
  61. url = baseurl .. '/' .. url,
  62. timeout = lurkcoin.timeout,
  63. post_data = data,
  64. user_agent = lurkcoin.user_agent,
  65. }, function(res)
  66. if res.timeout then
  67. minetest.log('warning', '[lurkcoin] Could not connect to lurkcoin!')
  68. res.code = 500
  69. res.data = 'ERROR: Could not connect to lurkcoin!'
  70. elseif res.code == 401 or res.code == 418 then
  71. minetest.log('warning', '[lurkcoin] Invalid username or API token!')
  72. lurkcoin.server_name, token = '<unknown>', nil
  73. elseif res.code == 200 and res.data:sub(1, 7) == 'ERROR: ' then
  74. res.code = 501
  75. end
  76. local success, msg = pcall(callback, res)
  77. if not success then
  78. minetest.log('error', '[lurkcoin] Error: ' .. msg)
  79. end
  80. end)
  81. end
  82. -- Process incoming transactions.
  83. local handled_transactions = {}
  84. local function _sync(res)
  85. if res.code == 200 then
  86. local data = minetest.parse_json(res.data)
  87. -- Update the exchange rate
  88. assert(type(data.exchange_rate) == 'number')
  89. assert(data.exchange_rate == data.exchange_rate)
  90. lurkcoin.exchange_rate = data.exchange_rate
  91. -- Process any unprocessed transactions
  92. for _, t in ipairs(data.transactions) do
  93. assert(type(t[3]) == 'number')
  94. local id = minetest.serialize(t)
  95. if not handled_transactions[id] then
  96. handled_transactions[id] = true
  97. core.log('action', '[lurkcoin] ' .. t[4])
  98. lurkcoin.bank.add(t[2], t[3])
  99. end
  100. end
  101. -- Tell lurkcoin to remove processed transactions
  102. if #data.transactions > 0 then
  103. get('remove_transactions', {count = tostring(#data.transactions)},
  104. function(res)
  105. if res.code == 200 then
  106. for _, t in ipairs(data.transactions) do
  107. handled_transactions[minetest.serialize(t)] = nil
  108. end
  109. end
  110. end)
  111. end
  112. end
  113. end
  114. -- Periodically sync with lurkcoin.
  115. local function sync()
  116. get('get_transactions', {as_object = 'true'}, _sync)
  117. minetest.after(300, sync)
  118. end
  119. -- Start syncing once the game is loaded.
  120. minetest.after(0, sync)
  121. -- Get an exchange rate
  122. function lurkcoin.get_exchange_rate(amount, to, callback)
  123. assert(callback)
  124. amount = amount and tonumber(amount)
  125. if not amount or amount ~= amount then return callback(nil) end
  126. get('exchange_rates', {
  127. from = lurkcoin.server_name,
  128. to = to or 'lurkcoin',
  129. amount = tostring(amount),
  130. }, function(res)
  131. if res.code == 200 then
  132. local amount = tonumber(res.data)
  133. if amount == amount then return callback(amount, nil) end
  134. end
  135. local msg
  136. if res.code == 502 then
  137. msg = 'That server does not exist!'
  138. else
  139. msg = res.data
  140. end
  141. return callback(nil, tostring(msg))
  142. end)
  143. end
  144. -- Pay a user (cross-server)
  145. function lurkcoin.pay(from, to, server, amount, callback)
  146. assert(type(amount) == 'number' and callback)
  147. -- Run lurkcoin.bank.pay() if this is not a cross-server transaction.
  148. if not server or server == '' or server:lower() ==
  149. lurkcoin.server_name:lower() then
  150. return callback(lurkcoin.bank.pay(from, to, amount))
  151. end
  152. -- Sanity checks
  153. amount = math.floor(amount * 100) / 100
  154. if not lurkcoin.bank.user_exists(from) then
  155. return callback(false, 'ERROR: The "from" user does not exist!')
  156. elseif amount ~= amount or amount <= 0 then
  157. return callback(false, 'ERROR: Invalid number!')
  158. elseif lurkcoin.bank.getbal(from) - amount < 0 then
  159. return callback(false, 'ERROR: You cannot afford to do that!')
  160. end
  161. if not lurkcoin.bank.subtract(from, amount, 'Transaction to "' .. to ..
  162. '" on server "' .. server .. '".') then
  163. return callback(false, 'ERROR: Transaction failed!')
  164. end
  165. -- Send the request
  166. return get('pay', {
  167. target = to,
  168. server = server,
  169. amount = tostring(amount),
  170. local_currency = 'true'
  171. }, function(res)
  172. if res.code ~= 200 then
  173. lurkcoin.bank.add(from, amount, 'Reverting failed transaction.')
  174. if res.data == 'ERROR: You cannot afford to do that!' then
  175. res.data = 'ERROR: This server cannot afford to do that!'
  176. end
  177. else
  178. minetest.log('action', '[lurkcoin] User ' .. from .. ' paid ' ..
  179. to .. ' (on server ' .. server .. ') ' .. tostring(amount) .. 'cr.')
  180. end
  181. return callback(res.code == 200, res.data or 'ERROR: Unknown error!')
  182. end)
  183. end
  184. -- Nuke lurkcoin.modpath
  185. lurkcoin.modpath = nil