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
This commit is contained in:
SmallJoker 2024-03-26 22:00:04 +01:00
parent cd27e24b6f
commit 298b51e16d
6 changed files with 146 additions and 109 deletions

View File

@ -8,12 +8,6 @@ skins = {}
skins.modpath = minetest.get_modpath(minetest.get_current_modname()) skins.modpath = minetest.get_modpath(minetest.get_current_modname())
skins.default = "character" 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.."/skin_meta_api.lua")
dofile(skins.modpath.."/api.lua") dofile(skins.modpath.."/api.lua")
dofile(skins.modpath.."/skinlist.lua") dofile(skins.modpath.."/skinlist.lua")

View File

@ -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 _ _,.

View File

@ -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 --- @param path Path to the "textures" directory, without tailing slash.
local name, sort_id, is_preview, playername --- @param filename Current file name, such as "player.groot.17.png".
local nameparts = string.gsub(fn, "[.]", skins.fsep):split(skins.fsep) local function process_skin_texture(path, filename)
-- See "textures/readme.txt" for allowed formats
-- check allowed prefix and file extension local prefix, sep, middlepart, extension = filename:match("^(%w+)([_.])(.*)%.(%w+)$")
if (nameparts[1] == 'player' or nameparts[1] == 'character') and --[[
nameparts[#nameparts]:lower() == 'png' then 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 -- Filter out files that do not match the allowed patterns
table.remove(nameparts, #nameparts) 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 local preview_suffix = sep .. "preview"
if nameparts[#nameparts] == 'preview' then if middlepart:sub(-#preview_suffix) == preview_suffix then
is_preview = true -- skip preview textures
table.remove(nameparts, #nameparts) -- 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
end
-- Build technically skin name do
name = table.concat(nameparts, '_') -- Optional preview texture
local preview_name = filename_noext .. sep .. "preview.png"
-- Handle metadata from file name local fh = io.open(path .. "/" .. preview_name)
if not is_preview then if fh then
-- Get player name dbgprint("Found preview", preview_name)
if nameparts[1] == "player" then skin_obj:set_preview(preview_name)
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
end end
end
end
local skin_obj = skins.get(name) or skins.new(name) do
if is_preview then -- Load skins from the current mod directory
skin_obj:set_preview(fn) local skins_path = skins.modpath.."/textures"
else local skins_dir_list = minetest.get_dir_list(skins_path)
skin_obj:set_texture(fn)
skin_obj:set_meta("_sort_id", sort_id) for _, fn in pairs(skins_dir_list) do
if playername then process_skin_texture(skins_path, fn)
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
end end
end end

View File

@ -50,9 +50,9 @@ end
local root_url = "http://skinsdb.terraqueststudios.net" local root_url = "http://skinsdb.terraqueststudios.net"
local page_url = root_url .. "/api/v1/content?client=mod&page=%i" -- [1] = Page# local page_url = root_url .. "/api/v1/content?client=mod&page=%i" -- [1] = Page#
local mod_path = skins.modpath local download_path = skins.modpath
local meta_path = mod_path .. "/meta/" local meta_path = download_path .. "/meta/"
local skins_path = mod_path .. "/textures/" local skins_path = download_path .. "/textures/"
-- Fancy debug wrapper to download an URL -- Fancy debug wrapper to download an URL
local function fetch_url(url, callback) local function fetch_url(url, callback)
@ -80,14 +80,22 @@ local function unsafe_file_write(path, contents)
end end
-- Takes a valid skin table from the Skins Database and saves it -- 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 = { local meta = {
skin.name, skin.name,
skin.author, skin.author,
skin.license 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 -- core.safe_file_write does not work here
unsafe_file_write( unsafe_file_write(
@ -128,7 +136,7 @@ internal.fetch_function = function(pages_total, start_page, len)
assert(skin.id ~= "") assert(skin.id ~= "")
if skin.id ~= 1 then -- Skin 1 is bundled with skinsdb if skin.id ~= 1 then -- Skin 1 is bundled with skinsdb
safe_single_skin(skin) save_single_skin(skin)
end end
end end

View File

@ -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: List of accepted texture names
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
Public skin available for all users: Public skin available for all users:
character_[number-or-name].png character.[number or name].png
One or multiple private skins for player "nick": One or multiple private skins for player "[nick]":
player_[nick].png or player.[nick].png
player_[nick]_[number-or-name].png player.[nick].[number or name].png
Preview files for public and private skins. Skin previews for public and private skins:
Optional, overrides the generated preview character.[number or name].preview.png
character_*_preview.png or player.[nick].preview.png
player_*_*_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`.

View File

@ -1,9 +1,4 @@
import sys, requests, base64 import os.path, sys, requests, base64
# filename seperator to use, either default "-" or ".". see skinsdb/textures/readme.txt
#fsep = "_"
fsep = "."
print("Downloading skins from skinsdb.terraqueststudio.net ...") print("Downloading skins from skinsdb.terraqueststudio.net ...")
@ -22,21 +17,27 @@ print("Writing skins")
for json in data["skins"]: for json in data["skins"]:
id = str(json["id"]) 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 # Texture file
raw_data = base64.b64decode(json["img"]) 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.write(bytearray(raw_data))
file.close() file.close()
# Meta file # Meta file
name = str(json["name"]) meta_name = str(json["name"])
author = str(json["author"]) meta_author = str(json["author"])
license = str(json["license"]) meta_license = str(json["license"])
file = open("../meta/character_" + id + ".txt", "w") file = open("../meta/" + name + ".txt", "w")
file.write(name + "\n" + author + "\n" + license + "\n") file.write(meta_name + "\n" + meta_author + "\n" + meta_license + "\n")
file.close() 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 count += 1
print("Fetched " + str(count) + " skins!") print("Fetched " + str(count) + " skins!")