From 298b51e16dd8acb88d85c3fd3019113b177d7174 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 26 Mar 2024 22:00:04 +0100 Subject: [PATCH] Clean up skin listing 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 --- init.lua | 6 -- settingtypes.txt | 3 - skinlist.lua | 152 ++++++++++++++++++++++++---------------- skins_updater.lua | 20 ++++-- textures/readme.txt | 45 +++++++----- updater/update_skins.py | 29 ++++---- 6 files changed, 146 insertions(+), 109 deletions(-) delete mode 100644 settingtypes.txt diff --git a/init.lua b/init.lua index 976c014..34e4540 100644 --- a/init.lua +++ b/init.lua @@ -8,12 +8,6 @@ skins = {} skins.modpath = minetest.get_modpath(minetest.get_current_modname()) skins.default = "character" --- see skindsdb/textures/readme.txt to avoid playername with underscore problem -skins.fsep = minetest.settings:get("skinsdb_fsep") or "_" -if skins.fsep == "_" then - minetest.log("warning", "skinsdb filename seperator is set to " .. skins.fsep .. ", see skindsdb/textures/readme.txt to avoid problems with playernames containing underscore") -end - dofile(skins.modpath.."/skin_meta_api.lua") dofile(skins.modpath.."/api.lua") dofile(skins.modpath.."/skinlist.lua") diff --git a/settingtypes.txt b/settingtypes.txt deleted file mode 100644 index e47be76..0000000 --- a/settingtypes.txt +++ /dev/null @@ -1,3 +0,0 @@ -# texture filename seperator, default "_" -# see skindsdb/textures/readme.txt to avoid playername with underscore problem -skinsdb_fsep (texture filename seperator) enum _ _,. \ No newline at end of file diff --git a/skinlist.lua b/skinlist.lua index dc8a155..1406675 100644 --- a/skinlist.lua +++ b/skinlist.lua @@ -1,72 +1,102 @@ -local skins_dir_list = minetest.get_dir_list(skins.modpath.."/textures") +local dbgprint = false and print or function() end -for _, fn in pairs(skins_dir_list) do - local name, sort_id, is_preview, playername - local nameparts = string.gsub(fn, "[.]", skins.fsep):split(skins.fsep) +--- @param path Path to the "textures" directory, without tailing slash. +--- @param filename Current file name, such as "player.groot.17.png". +local function process_skin_texture(path, filename) + -- See "textures/readme.txt" for allowed formats - -- check allowed prefix and file extension - if (nameparts[1] == 'player' or nameparts[1] == 'character') and - nameparts[#nameparts]:lower() == 'png' then + local prefix, sep, middlepart, extension = filename:match("^(%w+)([_.])(.*)%.(%w+)$") + --[[ + prefix: "character" or "player" + sep: "." (new) or "_" (legacy) + middlepart: number or name + ^ previews are explicity skipped + extension: "png" only due `skins.get_skin_format` + ]] - -- cut filename extension - table.remove(nameparts, #nameparts) + -- Filter out files that do not match the allowed patterns + if not extension or extension:lower() ~= "png" then + return -- Not a skin texture + end + if prefix ~= "player" and prefix ~= "character" then + return -- Unknown type + end - -- check preview suffix - if nameparts[#nameparts] == 'preview' then - is_preview = true - table.remove(nameparts, #nameparts) + local preview_suffix = sep .. "preview" + if middlepart:sub(-#preview_suffix) == preview_suffix then + -- skip preview textures + -- This is added by the main skin texture (if exists) + return + end + + dbgprint("Found skin", prefix, middlepart, extension) + + local sort_id -- number, sorting "rank" in the skin list + local playername -- string, if player-specific + if prefix == "player" then + -- Allow "player.PLAYERNAME.png" and "player.PLAYERNAME.123.png" + local splits = middlepart:split(sep) + playername = splits[1] + + -- Put in front + sort_id = 0 + (tonumber(splits[2]) or 0) + else -- Public skin "character*" + -- Less priority + sort_id = 5000 + (tonumber(middlepart) or 0) + end + + local filename_noext = prefix .. sep .. middlepart + + -- Register skin texture + local skin_obj = skins.get(filename_noext) or skins.new(filename_noext) + skin_obj:set_texture(filename) + skin_obj:set_meta("_sort_id", sort_id) + if playername then + skin_obj:set_meta("assignment", "player:"..playername) + skin_obj:set_meta("playername", playername) + end + + do + -- Get type of skin based on dimensions + local file = io.open(path .. "/" .. filename, "r") + local skin_format = skins.get_skin_format(file) + skin_obj:set_meta("format", skin_format) + file:close() + end + + skin_obj:set_hand_from_texture() + skin_obj:set_meta("name", middlepart) + + do + -- Optional skin information + local file = io.open(path .. "/../meta/" .. filename_noext .. ".txt", "r") + if file then + dbgprint("Found meta") + local data = string.split(file:read("*all"), "\n", 3) + skin_obj:set_meta("name", data[1]) + skin_obj:set_meta("author", data[2]) + skin_obj:set_meta("license", data[3]) end + end - -- Build technically skin name - name = table.concat(nameparts, '_') - - -- Handle metadata from file name - if not is_preview then - -- Get player name - if nameparts[1] == "player" then - playername = nameparts[2] - table.remove(nameparts, 1) - sort_id = 0 - else - sort_id = 5000 - end - - -- Get sort index - if tonumber(nameparts[#nameparts]) then - sort_id = sort_id + nameparts[#nameparts] - end + do + -- Optional preview texture + local preview_name = filename_noext .. sep .. "preview.png" + local fh = io.open(path .. "/" .. preview_name) + if fh then + dbgprint("Found preview", preview_name) + skin_obj:set_preview(preview_name) end + end +end - local skin_obj = skins.get(name) or skins.new(name) - if is_preview then - skin_obj:set_preview(fn) - else - skin_obj:set_texture(fn) - skin_obj:set_meta("_sort_id", sort_id) - if playername then - skin_obj:set_meta("assignment", "player:"..playername) - skin_obj:set_meta("playername", playername) - end - local file = io.open(skins.modpath.."/textures/"..fn, "r") - local skin_format = skins.get_skin_format(file) - skin_obj:set_meta("format", skin_format) - file:close() - skin_obj:set_hand_from_texture() - file = io.open(skins.modpath.."/meta/"..name..".txt", "r") - if file then - local data = string.split(file:read("*all"), "\n", 3) - file:close() - skin_obj:set_meta("name", data[1]) - skin_obj:set_meta("author", data[2]) - skin_obj:set_meta("license", data[3]) - else - -- remove player / character prefix if further naming given - if nameparts[2] and not tonumber(nameparts[2]) then - table.remove(nameparts, 1) - end - skin_obj:set_meta("name", table.concat(nameparts, ' ')) - end - end +do + -- Load skins from the current mod directory + local skins_path = skins.modpath.."/textures" + local skins_dir_list = minetest.get_dir_list(skins_path) + + for _, fn in pairs(skins_dir_list) do + process_skin_texture(skins_path, fn) end end diff --git a/skins_updater.lua b/skins_updater.lua index 61b99d5..41bb48e 100644 --- a/skins_updater.lua +++ b/skins_updater.lua @@ -50,9 +50,9 @@ end local root_url = "http://skinsdb.terraqueststudios.net" local page_url = root_url .. "/api/v1/content?client=mod&page=%i" -- [1] = Page# -local mod_path = skins.modpath -local meta_path = mod_path .. "/meta/" -local skins_path = mod_path .. "/textures/" +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) @@ -80,14 +80,22 @@ local function unsafe_file_write(path, contents) end -- Takes a valid skin table from the Skins Database and saves it -local function safe_single_skin(skin) +local function save_single_skin(skin) local meta = { skin.name, skin.author, skin.license } - local name = "character" .. skins.fsep .. skin.id + 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( @@ -128,7 +136,7 @@ internal.fetch_function = function(pages_total, start_page, len) assert(skin.id ~= "") if skin.id ~= 1 then -- Skin 1 is bundled with skinsdb - safe_single_skin(skin) + save_single_skin(skin) end end diff --git a/textures/readme.txt b/textures/readme.txt index e53e112..44ab8fd 100644 --- a/textures/readme.txt +++ b/textures/readme.txt @@ -1,25 +1,32 @@ -In this folder the skin files could be placed according the following file naming convention. +This location is where you can put your custom skins. -skinsdb uses an underscore as default seperator for filename splitting which can cause problems with playernames containing "_", -see https://github.com/minetest-mods/skinsdb/issues/54. -The config setting skinsdb_fsep (texture filename seperator) was added as a workaround which also offers "."(dot) as seperator, -dot is the only character which is allowed in textures but not in playernames. -To keep compatibility with older versions underscore is the default value. -fresh install: -you should change the seperator to "." to avoid that problem. -existing install: -- change the filenames according to the naming convention with dot as seperator instead of underscore -- change the texture filename seperator in settings or add "skinsdb_fsep = ." to your minetest.conf before starting your server +List of accepted texture names +------------------------------ Public skin available for all users: - character_[number-or-name].png + character.[number or name].png -One or multiple private skins for player "nick": - player_[nick].png or - player_[nick]_[number-or-name].png +One or multiple private skins for player "[nick]": + player.[nick].png + player.[nick].[number or name].png -Preview files for public and private skins. -Optional, overrides the generated preview - character_*_preview.png or - player_*_*_preview.png +Skin previews for public and private skins: + character.[number or name].preview.png + player.[nick].preview.png + player.[nick].[number or name].preview.png + + Note: This is optional and overrides automatically generated preciewws. + + +Legacy texture names +-------------------- + +The character `_` is accepted in player names, thus it is not recommended to +use such file names. For compatibility reasons, they are still recognized. + + character_[number or name].png + player_[nick]_png + player_[nick]_[number or name].png + +... and corresponding previews that end in `_preview.png`. diff --git a/updater/update_skins.py b/updater/update_skins.py index 00249e6..4c1c533 100644 --- a/updater/update_skins.py +++ b/updater/update_skins.py @@ -1,9 +1,4 @@ -import sys, requests, base64 - -# filename seperator to use, either default "-" or ".". see skinsdb/textures/readme.txt -#fsep = "_" -fsep = "." - +import os.path, sys, requests, base64 print("Downloading skins from skinsdb.terraqueststudio.net ...") @@ -22,21 +17,27 @@ print("Writing skins") for json in data["skins"]: id = str(json["id"]) + name = "character." + id + if True: + legacy_name = "character_" + id + if os.path.exists("../textures/" + legacy_name + ".png"): + name = legacy_name + + # Texture file raw_data = base64.b64decode(json["img"]) - file = open("../textures/character" + fsep + id + ".png", "wb") + file = open("../textures/" + name + ".png", "wb") file.write(bytearray(raw_data)) file.close() # Meta file - name = str(json["name"]) - author = str(json["author"]) - license = str(json["license"]) - file = open("../meta/character_" + id + ".txt", "w") - file.write(name + "\n" + author + "\n" + license + "\n") + meta_name = str(json["name"]) + meta_author = str(json["author"]) + meta_license = str(json["license"]) + file = open("../meta/" + name + ".txt", "w") + file.write(meta_name + "\n" + meta_author + "\n" + meta_license + "\n") file.close() - print("Added #%s Name: %s Author: %s License: %s" % (id, name, author, license)) + print("Added #%s Name: %s Author: %s License: %s" % (id, meta_name, meta_author, meta_license)) count += 1 - print("Fetched " + str(count) + " skins!")