skins_updater.lua 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. -- Skins update script
  2. local S = minetest.get_translator("skinsdb")
  3. local _ID_ = "Lua Skins Updater"
  4. local internal = {}
  5. internal.errors = {}
  6. -- Binary downloads are required
  7. if not core.features.httpfetch_binary_data then
  8. internal.errors[#internal.errors + 1] =
  9. "Feature 'httpfetch_binary_data' is missing. Update Minetest."
  10. end
  11. -- Insecure environment for saving textures and meta
  12. local ie, http = skins.ie, skins.http
  13. if not ie or not http then
  14. internal.errors[#internal.errors + 1] = "Insecure environment is required. " ..
  15. "Please add skinsdb to `secure.trusted_mods` in minetest.conf"
  16. end
  17. minetest.register_chatcommand("skinsdb_download_skins", {
  18. params = "<skindb start page> <amount of pages>",
  19. description = S("Downloads the specified range of skins and shuts down the server"),
  20. privs = {server=true},
  21. func = function(name, param)
  22. if #internal.errors > 0 then
  23. return false, "Cannot run " .. _ID_ .. ":\n\t" ..
  24. table.concat(internal.errors, "\n\t")
  25. end
  26. local parts = string.split(param, " ")
  27. local start = tonumber(parts[1])
  28. local len = tonumber(parts[2])
  29. if not (start and len and len > 0) then
  30. return false, "Invalid page number or amount of pages"
  31. end
  32. internal.get_pages_count(internal.fetch_function, start, len)
  33. return true, "Started downloading..."
  34. end,
  35. })
  36. if #internal.errors > 0 then
  37. return -- Nonsense to load something that's not working
  38. end
  39. -- http://minetest.fensta.bplaced.net/api/apidoku.md
  40. local root_url = "http://minetest.fensta.bplaced.net"
  41. local page_url = root_url .. "/api/v2/get.json.php?getlist&page=%i&outformat=base64" -- [1] = Page#
  42. local preview_url = root_url .. "/skins/1/%i.png" -- [1] = ID
  43. local mod_path = skins.modpath
  44. local meta_path = mod_path .. "/meta/"
  45. local skins_path = mod_path .. "/textures/"
  46. -- Fancy debug wrapper to download an URL
  47. local function fetch_url(url, callback)
  48. http.fetch({
  49. url = url,
  50. user_agent = _ID_
  51. }, function(result)
  52. if result.succeeded then
  53. if result.code ~= 200 then
  54. core.log("warning", ("%s: STATUS=%i URL=%s"):format(
  55. _ID_, result.code, url))
  56. end
  57. return callback(result.data)
  58. end
  59. core.log("warning", ("%s: Failed to download URL=%s"):format(
  60. _ID_, url))
  61. end)
  62. end
  63. -- Insecure workaround since meta/ and textures/ cannot be written to
  64. local function unsafe_file_write(path, contents)
  65. local f = ie.io.open(path, "w")
  66. f:write(contents)
  67. f:close()
  68. end
  69. -- Takes a valid skin table from the Skins Database and saves it
  70. local function safe_single_skin(skin)
  71. local meta = {
  72. skin.name,
  73. skin.author,
  74. skin.license
  75. }
  76. local name = "character_" .. skin.id
  77. -- core.safe_file_write does not work here
  78. unsafe_file_write(
  79. meta_path .. name .. ".txt",
  80. table.concat(meta, "\n")
  81. )
  82. unsafe_file_write(
  83. skins_path .. name .. ".png",
  84. core.decode_base64(skin.img)
  85. )
  86. fetch_url(preview_url:format(skin.id), function(preview)
  87. unsafe_file_write(skins_path .. name .. "_preview.png", preview)
  88. end)
  89. core.log("action", ("%s: Completed skin %s"):format(_ID_, name))
  90. end
  91. -- Get total pages since it'll just return the last page all over again
  92. internal.get_pages_count = function(callback, ...)
  93. local vars = {...}
  94. fetch_url(page_url:format(1) .. "&per_page=1", function(data)
  95. local list = core.parse_json(data)
  96. -- "per_page" defaults to 20 if left away (docs say something else, though)
  97. callback(math.ceil(list.pages / 20), unpack(vars))
  98. end)
  99. end
  100. -- Function to fetch a range of pages
  101. internal.fetch_function = function(pages_total, start_page, len)
  102. start_page = math.max(start_page, 1)
  103. local end_page = math.min(start_page + len - 1, pages_total)
  104. for page_n = start_page, end_page do
  105. local page_cpy = page_n
  106. fetch_url(page_url:format(page_n), function(data)
  107. core.log("action", ("%s: Page %i"):format(_ID_, page_cpy))
  108. local list = core.parse_json(data)
  109. for i, skin in pairs(list.skins) do
  110. assert(skin.type == "image/png")
  111. assert(skin.id ~= "")
  112. if skin.id ~= 1 then -- Skin 1 is bundled with skinsdb
  113. safe_single_skin(skin)
  114. end
  115. end
  116. if page_cpy == end_page then
  117. local log = _ID_ .. " finished downloading all skins. " ..
  118. "Shutting down server to reload media cache"
  119. core.log("action", log)
  120. core.request_shutdown(log, true, 3 --[[give some time for pending requests]])
  121. end
  122. end)
  123. end
  124. end