diff --git a/README.md b/README.md index b330155..5eae6cc 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,29 @@ This Minetest mod offers changeable player skins with a graphical interface for - Full [3d_armor](https://forum.minetest.net/viewtopic.php?t=4654) support - Compatible to 1.0 and 1.8 Minecraft skins format + +## Installing skins + +### Download from the database + +1) Get Minetest 5.1.0-dev-cb00632 or newer +2) Uncomment the lines in `init.lua` mentioning `skins_updater.lua` +3) Start your world and wait until it reports that the skins were downloaded. +4) Let the Minetest server shut down +5) Comment the lines in `init.lua` again +6) Start the server again + +You might want to run `minetest` in a Terminal/Console window to check the log output instantly. + +### Manual addition + +1) Copy your skin textures to `textures` as documented in `textures/readme.txt` +2) Create `meta/character_.txt` with the following fields (separated by new lines): + * Skin name + * Author + * Skin license + + ## License: - GPLv3 diff --git a/init.lua b/init.lua index c836486..afa0aa6 100644 --- a/init.lua +++ b/init.lua @@ -30,6 +30,13 @@ if minetest.get_modpath("sfinv") then dofile(skins.modpath.."/sfinv_page.lua") end +-- ie.loadfile does not exist? +--[[skins.ie = minetest.request_insecure_environment() +skins.http = minetest.request_http_api() +dofile(skins.modpath.."/skins_updater.lua") +skins.ie = nil +skins.http = nil]] + -- 3d_armor compatibility if minetest.global_exists("armor") then skins.armor_loaded = true diff --git a/skins_updater.lua b/skins_updater.lua new file mode 100644 index 0000000..1b0f039 --- /dev/null +++ b/skins_updater.lua @@ -0,0 +1,115 @@ +-- Skins update script +-- Load it in init.lua or write a frontend GUI/chatcommand for it. Good luck. + +local _ID_ = "Lua Skins Updater" +local _SKIN_PAGE_START_ = 1 -- Starting page to fetch the skins +local _SKIN_PAGE_END_ = nil -- End page number (nil = all skins) + +if not core.features.httpfetch_binary_data then + error(_ID_ .. " requires the feature 'httpfetch_binary_data'. Update Minetest.") +end + +local ie, http = skins.ie, skins.http +if not ie or not http then + error(_ID_ .. " requires the insecure environment. " .. + "Please add skinsdb to `secure.trusted_mods` in minetest.conf") +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, "w") + 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 +local function get_pages_count(callback) + fetch_url(page_url:format(1) .. "&per_page=5", function(data) + local list = core.parse_json(data) + print(dump(list)) + callback(list.pages) + end) +end + +-- Just fetch them all. YOLO +get_pages_count(function(pages_total) + local start_page = _SKIN_PAGE_START_ or 1 + local end_page = math.min(pages_total, _SKIN_PAGE_END_ or pages_total) + + for page_n = 1, 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 + core.log("action", _ID_ .. " finished downloading all skins. " .. + "Please comment out this script to reduce server traffic.") + core.request_shutdown("Reloading skinsdb media cache after download", + true, 3 --[[give some time for pending requests]]) + end + end) + end +end) \ No newline at end of file diff --git a/textures/readme.txt b/textures/readme.txt index 9add1c7..93776ed 100644 --- a/textures/readme.txt +++ b/textures/readme.txt @@ -1,4 +1,12 @@ -In this folder the skin files could be placed according the next file naming convention -character_[number-or-name].png - Public skin, available for all users -player_[nick].png or player_[nick]_[number-or-name].png - one or multiple private skins for player "nick" -*_preview.png - Preview files for public and private skins +In this folder the skin files could be placed according the following file naming convention. + +Public skin available for all users: + character_[number-or-name].png + +One or multiple private skins for player "nick": + player_[nick].png or + player_[nick]_[number-or-name].png + +Preview files for public and private skins: + character_*_preview.png or + player_*_*_preview.png