skinsdb/skins_updater.lua
Suns ec30267223
Fix corrupted data in Windows systems (#47)
The base64 data from the decoder to the file writer is in binary, using only the "w" flag would work in most unix systems but its incorrect and for all systems with crazy line_feed/Carrier_return or any character conversion the right way to save a binary data is using the "wb" flag.
2020-05-23 08:56:30 +02:00

148 lines
4.2 KiB
Lua

-- Skins update script
local S = minetest.get_translator("skinsdb")
local _ID_ = "Lua Skins Updater"
local internal = {}
internal.errors = {}
-- Binary downloads are required
if not core.features.httpfetch_binary_data then
internal.errors[#internal.errors + 1] =
"Feature 'httpfetch_binary_data' is missing. Update Minetest."
end
-- Insecure environment for saving textures and meta
local ie, http = skins.ie, skins.http
if not ie or not http then
internal.errors[#internal.errors + 1] = "Insecure environment is required. " ..
"Please add skinsdb to `secure.trusted_mods` in minetest.conf"
end
minetest.register_chatcommand("skinsdb_download_skins", {
params = "<skindb start page> <amount of pages>",
description = S("Downloads the specified range of skins and shuts down the server"),
privs = {server=true},
func = function(name, param)
if #internal.errors > 0 then
return false, "Cannot run " .. _ID_ .. ":\n\t" ..
table.concat(internal.errors, "\n\t")
end
local parts = string.split(param, " ")
local start = tonumber(parts[1])
local len = tonumber(parts[2])
if not (start and len and len > 0) then
return false, "Invalid page number or amount of pages"
end
internal.get_pages_count(internal.fetch_function, start, len)
return true, "Started downloading..."
end,
})
if #internal.errors > 0 then
return -- Nonsense to load something that's not working
end
-- http://minetest.fensta.bplaced.net/api/apidoku.md
local root_url = "http://minetest.fensta.bplaced.net"
local page_url = root_url .. "/api/v2/get.json.php?getlist&page=%i&outformat=base64" -- [1] = Page#
local preview_url = root_url .. "/skins/1/%i.png" -- [1] = ID
local mod_path = skins.modpath
local meta_path = mod_path .. "/meta/"
local skins_path = mod_path .. "/textures/"
-- Fancy debug wrapper to download an URL
local function fetch_url(url, callback)
http.fetch({
url = url,
user_agent = _ID_
}, function(result)
if result.succeeded then
if result.code ~= 200 then
core.log("warning", ("%s: STATUS=%i URL=%s"):format(
_ID_, result.code, url))
end
return callback(result.data)
end
core.log("warning", ("%s: Failed to download URL=%s"):format(
_ID_, url))
end)
end
-- Insecure workaround since meta/ and textures/ cannot be written to
local function unsafe_file_write(path, contents)
local f = ie.io.open(path, "wb")
f:write(contents)
f:close()
end
-- Takes a valid skin table from the Skins Database and saves it
local function safe_single_skin(skin)
local meta = {
skin.name,
skin.author,
skin.license
}
local name = "character_" .. skin.id
-- core.safe_file_write does not work here
unsafe_file_write(
meta_path .. name .. ".txt",
table.concat(meta, "\n")
)
unsafe_file_write(
skins_path .. name .. ".png",
core.decode_base64(skin.img)
)
fetch_url(preview_url:format(skin.id), function(preview)
unsafe_file_write(skins_path .. name .. "_preview.png", preview)
end)
core.log("action", ("%s: Completed skin %s"):format(_ID_, name))
end
-- Get total pages since it'll just return the last page all over again
internal.get_pages_count = function(callback, ...)
local vars = {...}
fetch_url(page_url:format(1) .. "&per_page=1", function(data)
local list = core.parse_json(data)
-- "per_page" defaults to 20 if left away (docs say something else, though)
callback(math.ceil(list.pages / 20), unpack(vars))
end)
end
-- Function to fetch a range of pages
internal.fetch_function = function(pages_total, start_page, len)
start_page = math.max(start_page, 1)
local end_page = math.min(start_page + len - 1, pages_total)
for page_n = start_page, end_page do
local page_cpy = page_n
fetch_url(page_url:format(page_n), function(data)
core.log("action", ("%s: Page %i"):format(_ID_, page_cpy))
local list = core.parse_json(data)
for i, skin in pairs(list.skins) do
assert(skin.type == "image/png")
assert(skin.id ~= "")
if skin.id ~= 1 then -- Skin 1 is bundled with skinsdb
safe_single_skin(skin)
end
end
if page_cpy == end_page then
local log = _ID_ .. " finished downloading all skins. " ..
"Shutting down server to reload media cache"
core.log("action", log)
core.request_shutdown(log, true, 3 --[[give some time for pending requests]])
end
end)
end
end