skinsdb/skins_updater.lua
SmallJoker 312780c82e
Clean up skin listing (#100)
Supersedes the 'fsep' setting by automatically detecting
the texture name in both, the skin list and the updater
scripts.
New, automatically fetched skins will now always use the
'.' delimiter to avoid player name issues.
In case of ambiguous texture names, a warning is logged.
2024-06-05 17:55:55 +02:00

152 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 = S("<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://skinsdb.terraqueststudios.net"
local page_url = root_url .. "/api/v1/content?client=mod&page=%i" -- [1] = Page#
local download_path = skins.modpath
local meta_path = download_path .. "/meta/"
local skins_path = download_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 save_single_skin(skin)
local meta = {
skin.name,
skin.author,
skin.license
}
local name = "character." .. skin.id
do
local legacy_name = "character_" .. skin.id
local fh = ie.io.open(skins_path .. legacy_name .. ".png", "r")
-- Use the old name if either the texture ...
if fh then
name = legacy_name
end
end
-- 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)
)
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
save_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