1252 lines
No EOL
53 KiB
Lua
1252 lines
No EOL
53 KiB
Lua
libskinupload = {
|
|
notifications = {},
|
|
reviewers = {},
|
|
enabled = {}
|
|
}
|
|
|
|
local mcl = not not minetest.get_modpath("mcl_skins")
|
|
local it = not not minetest.get_modpath("itbase")
|
|
|
|
if mcl then
|
|
--Override mcl_skins to degrade gracefully instead of giving an unknown item
|
|
local _get_node_id_by_player = mcl_skins.get_node_id_by_player
|
|
function mcl_skins.get_node_id_by_player(p)
|
|
local out = _get_node_id_by_player(p)
|
|
if not minetest.registered_items["mcl_meshhand:"..out] then
|
|
return "mcl_skins_character_1_female_surv"
|
|
else
|
|
return out
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Shim for 3d_armor
|
|
if minetest.get_modpath("3d_armor") then
|
|
local _get_player_skin = armor.get_player_skin
|
|
function armor.get_player_skin(self, name)
|
|
local id = libskinupload.get_skin_id(name)
|
|
if id == "" then
|
|
return _get_player_skin(self, name)
|
|
end
|
|
return "libskinupload_uploaded_skin_"..id..".png"
|
|
end
|
|
end
|
|
|
|
-- Shim for u_skins
|
|
if minetest.get_modpath("u_skins") then
|
|
local gt = u_skins.get_type
|
|
u_skins.get_type = function(tx)
|
|
local out = gt(tx)
|
|
if out then return out else return u_skins.type.MODEL end
|
|
end
|
|
end
|
|
|
|
-- Polyfill for older versions
|
|
if not minetest.hypertext_escape then
|
|
local hypertext_escapes = {
|
|
["\\"] = "\\\\",
|
|
["<"] = "\\<",
|
|
[">"] = "\\>",
|
|
}
|
|
function core.hypertext_escape(text)
|
|
return text and text:gsub("[\\<>]", hypertext_escapes)
|
|
end
|
|
minetest.hypertext_escape = core.hypertext_escape
|
|
end
|
|
|
|
local function player_model(p, tx, slim_arms)
|
|
if mcl then
|
|
return (slim_arms and "mcl_armor_character_female.b3d" or "mcl_armor_character.b3d")..";"..tx..",blank.png"
|
|
elseif it then
|
|
return "player.b3d;"..tx
|
|
end
|
|
return "character.b3d;"..tx..",blank.png"
|
|
end
|
|
|
|
local db = minetest.get_mod_storage()
|
|
local world_dir = minetest.get_worldpath()
|
|
local meta_file = world_dir.."/libskinupload_meta.json"
|
|
local storage_dir = world_dir.."/libskinupload_skins/"
|
|
local storage_dir_meta = world_dir.."/libskinupload_meta/"
|
|
local optimize_media = minetest.settings:get_bool("libskinupload.optimize_media", true)
|
|
local use_fragmented_meta = minetest.settings:get_bool("libskinupload.fragmented_meta", false)
|
|
local allow_search = minetest.settings:get_bool("libskinupload.allow_search", true) and not use_fragmented_meta
|
|
minetest.mkdir(storage_dir)
|
|
if use_fragmented_meta then
|
|
minetest.mkdir(storage_dir_meta)
|
|
else
|
|
local m = io.open(meta_file)
|
|
local dl = minetest.get_dir_list(storage_dir_meta)
|
|
if m then
|
|
m:close()
|
|
elseif #dl > 0 then
|
|
minetest.log("action", "[ libskinupload ] Fragmented meta is disabled, but no master meta file was detected and a fragmented meta file is present. Automatically migrating to consolidated meta (this will not remove the libskinupload_meta folder)...")
|
|
local out = "{"
|
|
local i = 0
|
|
for _, x in pairs(dl) do
|
|
minetest.log("action", "Merging file "..x.."...")
|
|
local id = x:match "libskinupload_uploaded_skin_(%d*).json"
|
|
local file = io.open(storage_dir_meta..x)
|
|
local data = file:read("a")
|
|
file:close()
|
|
out = out..(i == 0 and ("\""..id.."\":") or (",\""..id.."\":"))..data
|
|
i = i +1
|
|
end
|
|
local f = io.open(meta_file, "w+")
|
|
f:write(out.."}")
|
|
f:flush()
|
|
f:close()
|
|
end
|
|
end
|
|
|
|
|
|
function libskinupload.add_skin_media(id, fname, cb)
|
|
local x
|
|
if not fname then
|
|
id = tostring(id)
|
|
x = "libskinupload_uploaded_skin_"..id..".png"
|
|
else
|
|
x = id
|
|
id = id:match "libskinupload_uploaded_skin_([^.]+).png"
|
|
end
|
|
if libskinupload.enabled[id] then return end
|
|
libskinupload.enabled[id] = true
|
|
if fname then
|
|
minetest.dynamic_add_media{
|
|
filepath = storage_dir..x,
|
|
filename = x
|
|
}
|
|
else
|
|
minetest.dynamic_add_media({
|
|
filepath = storage_dir..x,
|
|
filename = x
|
|
}, cb or function() end)
|
|
end
|
|
if mcl then
|
|
local meta = {}
|
|
if use_fragmented_meta then
|
|
local f = io.open(storage_dir_meta..string.gsub(x, ".png", ".json"))
|
|
if f then
|
|
meta = minetest.parse_json(f:read("a"))
|
|
f:close()
|
|
end
|
|
else
|
|
meta = libskinupload.get_skin_meta(x)
|
|
end
|
|
mcl_skins.register_simple_skin{texture = x, slim_arms = meta.msa or false}
|
|
end
|
|
end
|
|
|
|
if not optimize_media then
|
|
for _, x in ipairs(minetest.get_dir_list(storage_dir, false)) do
|
|
libskinupload.add_skin_media(x, true)
|
|
end
|
|
end
|
|
|
|
minetest.register_privilege("skin_review", {
|
|
on_grant = function(name)
|
|
libskinupload.reviewers[name] = true
|
|
db:set_string("reviewers", minetest.serialize(libskinupload.reviewers))
|
|
end,
|
|
on_revoke = function(name)
|
|
libskinupload.reviewers[name] = nil
|
|
db:set_string("reviewers", minetest.serialize(libskinupload.reviewers))
|
|
end,
|
|
give_to_singleplayer = false,
|
|
give_to_admin = false
|
|
})
|
|
minetest.register_privilege("no_skin_upload", {
|
|
give_to_singleplayer = false,
|
|
give_to_admin = false
|
|
})
|
|
|
|
libskinupload.notifications = minetest.deserialize(db:get_string("notifications")) or {}
|
|
libskinupload.reviewers = minetest.deserialize(db:get_string("reviewers")) or {}
|
|
libskinupload.requests = minetest.deserialize(db:get_string("requests")) or {}
|
|
|
|
local function markdown(str)
|
|
local out = minetest.hypertext_escape(str)
|
|
:gsub("__(.+)__", "<i>%1</i>")
|
|
:gsub("%*%*(.+)%*%*", "<b>%1</b>")
|
|
:gsub("%^%^(.+)%^%^", "<u>%1</u>")
|
|
:gsub("``(.+)``", "<mono>%1</mono>")
|
|
return out
|
|
end
|
|
|
|
local function listify(name)
|
|
local out = {}
|
|
for _, x in pairs(libskinupload.requests[name]) do
|
|
out[#out +1] = x
|
|
end
|
|
libskinupload.requests[name] = out
|
|
end
|
|
|
|
function libskinupload.notify(name, msg)
|
|
if name == ":reviewers" then
|
|
for n, _ in pairs(libskinupload.reviewers) do
|
|
if minetest.get_player_by_name(n) then
|
|
minetest.chat_send_player(n, msg)
|
|
end
|
|
end
|
|
else
|
|
if minetest.get_player_by_name(name) then
|
|
minetest.chat_send_player(name, msg)
|
|
else
|
|
libskinupload.notifications[name] = msg
|
|
db:set_string("notifications", minetest.serialize(libskinupload.notifications))
|
|
end
|
|
end
|
|
end
|
|
|
|
function libskinupload.get_skin_data(fname)
|
|
local f = io.open(storage_dir..fname)
|
|
local out = minetest.encode_base64(f:read("a"))
|
|
f:close()
|
|
return out
|
|
end
|
|
|
|
minetest.register_on_joinplayer(function(p)
|
|
if db:contains("skin:"..p:get_player_name()) then
|
|
local skin = db:get_string("skin:"..p:get_player_name())
|
|
libskinupload.set_skin(p, skin)
|
|
end
|
|
local msg = libskinupload.notifications[p:get_player_name()]
|
|
if msg then
|
|
minetest.chat_send_player(p:get_player_name(), msg)
|
|
libskinupload.notifications[p:get_player_name()] = nil
|
|
db:set_string("notifications", minetest.serialize(libskinupload.notifications))
|
|
end
|
|
if minetest.check_player_privs(p, {skin_review = true}) then
|
|
local newskins = db:get_int("newrequests")
|
|
if newskins > 0 then minetest.chat_send_player(p:get_player_name(), minetest.colorize("#579a1e", "[libskinupload] "..newskins.." new skin request"..(newskins == 1 and " has" or "s have").." been submitted. Run /skinreview to see "..(newskins == 1 and "it" or "them")..".")) end
|
|
end
|
|
end)
|
|
|
|
function libskinupload.queue(p, req)
|
|
local data = req.data
|
|
if minetest.check_player_privs(p, {no_skin_upload = true}) then
|
|
minetest.chat_send_player(p:get_player_name(), "Insufficient permissions.")
|
|
return "No permission."
|
|
end
|
|
if not minetest.decode_base64(data) then
|
|
return "Invalid base64 data."
|
|
end
|
|
if #data < 5 or string.sub(data, 1, 5) ~= "iVBOR" then
|
|
return "Invalid PNG file."
|
|
end
|
|
local def = {name = req.name or "", desc = req.desc or "", data = data, uploader = p:get_player_name(), tags = req.tags}
|
|
if mcl then
|
|
def.mcl_slim_arms = req.mcl_slim_arms
|
|
end
|
|
def.private = req.private
|
|
local name = p:get_player_name()
|
|
local rq = libskinupload.requests[name]
|
|
if not rq then
|
|
libskinupload.requests[name] = {}
|
|
rq = libskinupload.requests[name]
|
|
end
|
|
local max = db:get_int("reqmax:"..name)
|
|
local num = #rq +1
|
|
if max < 1 then max = 1 end
|
|
if num <= max then
|
|
rq[num] = "skinreq:"..name..num
|
|
db:set_string("requests", minetest.serialize(libskinupload.requests))
|
|
db:set_string("skinreq:"..name..num, minetest.serialize(def))
|
|
db:set_int("newrequests", db:get_int("newrequests") +1)
|
|
else
|
|
num = max
|
|
rq[num] = "skinreq:"..name..num
|
|
db:set_string("requests", minetest.serialize(libskinupload.requests))
|
|
db:set_string("skinreq:"..name..num, minetest.serialize(def))
|
|
end
|
|
if name == "singleplayer" then
|
|
libskinupload.accept(p, name.."@"..num)
|
|
return false
|
|
end
|
|
libskinupload.notify(":reviewers", minetest.colorize("#579a1e", "[libskinupload] A new skin request has been submitted. Run /skinreview to see it."))
|
|
return false
|
|
end
|
|
|
|
function libskinupload.deny(by, rq, msg)
|
|
if not minetest.check_player_privs(by, {skin_review = true}) then
|
|
minetest.chat_send_player(by:get_player_name(), "Insufficient permissions.")
|
|
return true
|
|
end
|
|
local num = rq:match "@(.+)"
|
|
rq = rq:gsub("@", "")
|
|
local name = minetest.deserialize(db:get_string("skinreq:"..rq)).uploader
|
|
minetest.log("action", by:get_player_name().." denied a skin upload request from "..name..". Reason: "..(msg or ""))
|
|
db:set_string("skinreq:"..rq, "")
|
|
libskinupload.requests[name][num] = nil
|
|
db:set_string("requests", minetest.serialize(libskinupload.requests))
|
|
listify(name)
|
|
db:set_int("newrequests", db:get_int("newrequests") -1)
|
|
libskinupload.notify(name, minetest.colorize("#dc4207", "[libskinupload] Your skin request was denied. "..(msg and msg ~= "" and "Reason: "..msg or "No reason was provided.")))
|
|
end
|
|
|
|
local function get_next_id()
|
|
local f, err = io.open(world_dir.."/libskinupload_nextid.txt", "r")
|
|
local out = 0
|
|
if f then
|
|
out = tonumber(f:read("a"))
|
|
f:close()
|
|
end
|
|
f, err = io.open(world_dir.."/libskinupload_nextid.txt", "w")
|
|
local out2 = db:get_int("_nextid")
|
|
if out2 > out then out = out2 end
|
|
db:set_int("_nextid", out +1)
|
|
if f then
|
|
f:write(out +1)
|
|
f:flush()
|
|
f:close()
|
|
else
|
|
minetest.log("Notice: libskinupload failed to access nextid file: "..err.." Falling back to mod storage.")
|
|
end
|
|
return out
|
|
end
|
|
function libskinupload.accept(by, rq)
|
|
if not minetest.check_player_privs(by, {skin_review = true}) then
|
|
minetest.chat_send_player(by:get_player_name(), "Insufficient permissions.")
|
|
return true
|
|
end
|
|
local num = rq:match "@(.+)"
|
|
rq = rq:gsub("@", "")
|
|
local req = minetest.deserialize(db:get_string("skinreq:"..rq))
|
|
local name = req.uploader
|
|
if name then
|
|
minetest.log("action", by:get_player_name().." accepted a skin upload request from "..name..".")
|
|
libskinupload.requests[name][num] = nil
|
|
db:set_string("requests", minetest.serialize(libskinupload.requests))
|
|
listify(name)
|
|
end
|
|
local id = get_next_id()
|
|
local fp = storage_dir.."/libskinupload_uploaded_skin_"..id..".png"
|
|
if not minetest.safe_file_write(fp, minetest.decode_base64(req.data)) then minetest.log("error", "Failed to write skin file.") end
|
|
|
|
if use_fragmented_meta then
|
|
local fpm = storage_dir_meta.."/libskinupload_uploaded_skin_"..id..".json"
|
|
local fm = io.open(fpm, "w+")
|
|
if not fm:write(minetest.write_json{n = req.name, d = req.desc, c = req.uploader or "<Unknown>", p = req.private or false, t = req.tags, msa = req.mcl_slim_arms or false}) then minetest.log("error", "Failed to write skin meta.") end
|
|
fm:flush()
|
|
fm:close()
|
|
else
|
|
local fm = io.open(meta_file, "r+")
|
|
if not fm then
|
|
io.open(meta_file, "w+"):close()
|
|
fm = io.open(meta_file, "r+")
|
|
end
|
|
if not fm:seek("end", -1) then
|
|
fm:write("{")
|
|
else
|
|
fm:write(",")
|
|
end
|
|
if not fm:write("\""..id.."\":"..minetest.write_json{n = req.name, d = req.desc, c = req.uploader or "<Unknown>", p = req.private or false, t = req.tags, msa = req.mcl_slim_arms or false}.."}") then minetest.log("error", "Failed to write skin meta.") end
|
|
fm:flush()
|
|
fm:close()
|
|
end
|
|
db:set_string("skinreq:"..rq, "")
|
|
db:set_int("newrequests", db:get_int("newrequests") -1)
|
|
if name then libskinupload.notify(name, minetest.colorize("#579a1e", "[libskinupload] Your skin request was accepted.")) end
|
|
end
|
|
|
|
local upload_state = {}
|
|
function libskinupload.show_upload_dialog(name, args)
|
|
if not args or type(args) == "string" then args = {err = ""} end
|
|
local p = minetest.get_player_by_name(name)
|
|
if not upload_state[name] then upload_state[name] = {} end
|
|
local m = upload_state[name]
|
|
local size = (minetest.get_player_window_information(name) or {}).max_formspec_size or {x = 20, y = 11.5}
|
|
local w = size.x /2 -1
|
|
local w2 = size.x /2 -1.5
|
|
local tags = ""
|
|
local x = 0
|
|
local y = 0
|
|
if m.tags then
|
|
for _, tag in ipairs(m.tags) do
|
|
local width = math.max(#tag *0.2, 1.5)
|
|
if x +width > w then
|
|
x = 0
|
|
y = y +0.6
|
|
end
|
|
tags = tags.."button["..x..","..y..";"..width..",0.5;remove_tag;"..tag.."]\
|
|
"
|
|
x = x +width +0.05
|
|
end
|
|
end
|
|
local start = math.max(y +1.35, (size.y -7) /2)
|
|
minetest.show_formspec(name, "libskinupload:upload", "formspec_version[7]\
|
|
size["..size.x..","..size.y.."]\
|
|
\
|
|
"..((args.err and args.err ~= "") and "hypertext[0.5,"..(size.y -2)..";"..(size.x /3)..",3;;<tag name=clr color=#b31706><clr>Error: "..args.err.."</clr>]" or "").."\
|
|
container[0.5,0.5]\
|
|
style[add_tag;bgcolor=#0000;border=false;bgimg=]\
|
|
style[remove_tag;border=false;bgimg=libskinupload_tag_bg.png;bgimg_middle=8;padding=-8]\
|
|
style[remove_tag:hovered;border=false;bgimg=libskinupload_tag_bg_hovered.png;bgimg_middle=8;padding=-8]\
|
|
"..tags.."\
|
|
"..(args.add_tag and "set_focus[new_tag,true]\
|
|
field_close_on_enter[new_tag;false]\
|
|
field["..x..","..y..";2,0.5;new_tag;;]" or "button["..x..","..y..";0.5,0.5;add_tag;+]").."\
|
|
container_end[]\
|
|
container[0.5,"..start.."]\
|
|
field[0.5,0;"..w..",0.5;skin;Data;"..minetest.formspec_escape(m.data or "").."]\
|
|
field[0.5,1;"..w..",0.5;skinname;Name;"..minetest.formspec_escape(m.name or "").."]\
|
|
textarea[0.5,2;"..w..",2;skindesc;Description;"..minetest.formspec_escape(m.desc or "").."]\
|
|
checkbox[0.5,4.5;private;Personal Skin;"..(not not m.private and "true" or "false").."]\
|
|
"..
|
|
(mcl and "checkbox[0.5,5;mcl_slim_arms;Slim Arms;"..(m.mcl_slim_arms and "true" or "false").."]" or "")
|
|
.."\
|
|
button[0.5,6;"..w..",1;confirm;Upload Skin]\
|
|
container_end[]\
|
|
"..(m.data and m.data ~= "" and #m.data > 5 and string.sub(m.data, 1, 5) == "iVBOR" and minetest.decode_base64(m.data) and "model["..(size.x /2 +1)..",0.5;"..w2..","..(size.y -2)..";preview;"..player_model(p, m.data and minetest.formspec_escape("[png:"..m.data) or "blank.png", m.mcl_slim_arms)..";0,210;;true;;]" or "button["..(size.x /2 +1 +(w2 /4))..","..(size.y /2)..";"..(w2 /2)..",1;eh;Show Preview]\
|
|
").."\
|
|
button["..(size.x /2 +1)..","..(size.y -1.5)..";"..w2..",1;help;Show Help]\
|
|
"..(m.help and "box[0,0;"..size.x..","..size.y..";#000a]\
|
|
style[help;bgcolor=#0000;border=false;bgimg=]\
|
|
style[help:hovered;bgcolor=#0000;border=false;bgimg=]\
|
|
style[help:pressed;bgcolor=#0000;border=false;bgimg=]\
|
|
button[0,0;"..size.x..","..size.y..";help;]\
|
|
hypertext["..(size.x /4)..","..(size.y /4)..";"..(size.x /2)..","..(size.y /2)..";;<tag name=clr color=#ffbc5b>"..minetest.formspec_escape("To upload a skin, paste the base64-encoded image into the Data field below. This can be obtained from a terminal by running <clr><b>`base64 -i <filepath>`</b></clr> (e.g. <clr><b>`base64 -i ~/Desktop/skins/my_skin.png`</b></clr>) and copying the output. Alternatively, you can use a web tool (search <clr>'base64-encode an image'</clr> or similar). Once a request is sbmitted, it will need to be reviewed. You will be notified when your request is accepted or denied. Note that you can only have one skin request pending approval at a time; if you send another request before the previous one is approved or rejected, the later request will overwrite the previous one.").."]" or ""))
|
|
end
|
|
minetest.register_chatcommand("skinupload", {
|
|
description = "Show the skin upload dialog.",
|
|
func = libskinupload.show_upload_dialog,
|
|
privs = {no_skin_upload = false}
|
|
})
|
|
|
|
local review_state = {}
|
|
function libskinupload.show_review_dialog(name, args)
|
|
local p = minetest.get_player_by_name(name)
|
|
local fs = ""
|
|
local fsp = [=[formspec_version[7]
|
|
size[12,10]]=]
|
|
if args == "" then
|
|
fs = "scroll_container[0.5,0.5;11,9;na;vertical;]"
|
|
|
|
local i = 0
|
|
for n, l in pairs(libskinupload.requests) do
|
|
for k, v in ipairs(l) do
|
|
local req = minetest.deserialize(db:get_string(v))
|
|
if not req then goto continue end
|
|
if req.data and #req.data > 10923 then req.data = "" end
|
|
local pn = n.."@"..k
|
|
fs = fs.."style[view"..pn..";bgcolor=#0000;border=false]\
|
|
style[view"..pn..":hovered;bgcolor=#0001;border=false]\
|
|
style[view"..pn..":focused;bgcolor=#0001;border=false]\
|
|
style[view"..pn..":pressed;bgcolor=#0001;border=false]\
|
|
model["..((i %4) *2)..","..4 *math.floor(i /4)..";2,4;pre;"..player_model(p, "\\[png:"..minetest.formspec_escape(req.data), req.mcl_slim_arms or false)..";0,210;;false;0,0;]\
|
|
button["..((i %4) *2)..","..4 *math.floor(i /4)..";2,4;view"..pn..";]"
|
|
i = i + 1
|
|
::continue::
|
|
end
|
|
end
|
|
if i == 0 then
|
|
db:set_int("newrequests", 0)
|
|
fs = fs.."\
|
|
hypertext[-0.5,-0.5;12,10;;<global valign=middle halign=center size=32 color=#888><b>There are no pending skin requests.</b>]"
|
|
end
|
|
fs = fsp..(i > 8 and "\
|
|
scrollbaroptions[min=0;max="..(50 *(math.ceil(i /4) - 2))..";thumbsize=10]\
|
|
scrollbar[10.8,0.5;0.2,9;vertical;na;0]\
|
|
" or "")..fs..[=[
|
|
scroll_container_end[]
|
|
]=]
|
|
else
|
|
local m = review_state[name]
|
|
local req = m.req
|
|
if not req then
|
|
m.req = minetest.deserialize(db:get_string(libskinupload.requests[args:match "^([^@]+)@"][tonumber(args:match "@(.+)")]))
|
|
req = m.req
|
|
end
|
|
local tags = "style[remove_tag;border=false;bgimg=libskinupload_tag_bg.png;bgimg_middle=8;padding=-8]\
|
|
style[remove_tag:hovered;border=false;bgimg=libskinupload_tag_bg_hovered.png;bgimg_middle=8;padding=-8]\
|
|
"
|
|
local x = 0
|
|
local y = 0
|
|
if req.tags then
|
|
for _, tag in ipairs(req.tags) do
|
|
local width = math.max(#tag *0.2, 1.5)
|
|
-- if x +width > 10 then
|
|
-- x = 0
|
|
-- y = y +0.6
|
|
-- end
|
|
tags = tags.."button["..x..","..y..";"..width..",0.5;remove_tag;"..tag.."]\
|
|
"
|
|
x = x +width +0.05
|
|
end
|
|
end
|
|
fs = "formspec_version[7]\
|
|
size[12,12]\
|
|
model[0.5,0.5;8,7;pre;"..player_model(p, "\\[png:"..minetest.formspec_escape(req.data), req.mcl_slim_arms or false)..";0,210;;true;0,0;]\
|
|
button[8,1;3,1;accept;Accept]\
|
|
button[8,2;3,1;showdeny;Deny]\
|
|
label[8,3.5;Creator\\: "..(req.uploader or "N/A").."]\
|
|
hypertext[8,4;3,2;;Name\\: "..minetest.formspec_escape(minetest.hypertext_escape(req.name or "N/A")).."]\
|
|
hypertext[0.5,8.5;8,3;;"..minetest.formspec_escape(markdown(req.desc) or "N/A").."]\
|
|
button[8,6;3,1;back;Back]\
|
|
scroll_container[0.5,7.5;11,1;tagscroll;horizontal;;0]\
|
|
scrollbar[0,-800;8,0;horizontal;tagscroll;0]\
|
|
"..tags.."\
|
|
"..(m.add_tag and "set_focus[new_tag,true]\
|
|
field_close_on_enter[new_tag;false]\
|
|
field["..x..",0;2,0.5;new_tag;;]" or "button["..x..",0;0.5,0.5;add_tag;+]").."\
|
|
scroll_container_end[]"
|
|
if m.reason then
|
|
fs = fs.."\
|
|
box[0,0;12,12;#000a]\
|
|
style[closedeny;bgcolor=#0000;border=false;bgimg=]\
|
|
style[closedeny:hovered;bgcolor=#0000;border=false;bgimg=]\
|
|
style[closedeny:pressed;bgcolor=#0000;border=false;bgimg=]\
|
|
button[0,0;12,12;closedeny;]\
|
|
textarea[3,2.5;6,5;reason;Reason;"..m.reason.."]\
|
|
button[3,7.5;3,1;closedeny2;Cancel]\
|
|
button[6,7.5;3,1;deny;Deny]\
|
|
"
|
|
end
|
|
end
|
|
minetest.show_formspec(name, "libskinupload:review", fs)
|
|
end
|
|
minetest.register_chatcommand("skinreview", {
|
|
description = "Show the skin review dialog.",
|
|
func = function(name, args)
|
|
review_state[name] = {}
|
|
libskinupload.show_review_dialog(name, "")
|
|
end,
|
|
privs = {skin_review = true}
|
|
})
|
|
|
|
local function pattern_escape(str)
|
|
return str:gsub("([]%)([.^$*+-])", "%%%1")
|
|
end
|
|
|
|
local choose_state = {}
|
|
function libskinupload.show_choose_dialog(name)
|
|
local p = minetest.get_player_by_name(name)
|
|
local m = choose_state[name]
|
|
local target = m.current
|
|
local search = m.search
|
|
local fs = ""
|
|
local fsp = [=[formspec_version[7]
|
|
size[12,10]]=]
|
|
|
|
if not choose_state["@dirlist"] or not #choose_state["@dirlist"] then
|
|
choose_state["@dirlist"] = minetest.get_dir_list(storage_dir, false)
|
|
choose_state["@max_pages"] = math.floor(#choose_state["@dirlist"] /8)
|
|
end
|
|
if not choose_state["@index"] then
|
|
local f = io.open(meta_file)
|
|
if not f then
|
|
choose_state["@index"] = {}
|
|
else
|
|
local res = f:read("a")
|
|
choose_state["@index"] = minetest.parse_json(res)
|
|
f:close()
|
|
end
|
|
end
|
|
if target == "" or not target then
|
|
fs = fs.."container[2,0.5]"
|
|
|
|
if search and allow_search then
|
|
local out = "\
|
|
"
|
|
|
|
local tags = {}
|
|
for x in search:gmatch("%[([^%]]+)%]") do
|
|
tags[x] = 1
|
|
end
|
|
search = search:gsub("%[[^%]]+%]", ""):gsub("%s+$", "")
|
|
|
|
--minetest.chat_send_player(name, minetest.serialize{search, tags})
|
|
|
|
local i = 0
|
|
local j = 0
|
|
for id, meta in pairs(choose_state["@index"]) do
|
|
if not (next(tags) and search == "") and not meta.n:lower():find(search:lower(), nil, true) or meta.p and meta.c ~= name then
|
|
goto continue
|
|
else
|
|
if next(tags) then
|
|
if not meta.t then goto continue end
|
|
for _, x in pairs(meta.t) do
|
|
if tags[x] then
|
|
tags[x] = 2
|
|
end
|
|
end
|
|
for _, x in pairs(tags) do
|
|
if x < 2 then goto continue end
|
|
end
|
|
end
|
|
if j < m.page *8 or j >= m.page *8 +8 then
|
|
j = j +1
|
|
goto continue
|
|
end
|
|
end
|
|
local pn = "libskinupload_uploaded_skin_"..id..".png"
|
|
local private = meta.p and meta.c ~= name
|
|
out = out.."style[view"..pn..";bgcolor=#0000;border=false]\
|
|
style[view"..pn..":hovered;bgcolor=#0001;border=false]\
|
|
style[view"..pn..":focused;bgcolor=#0001;border=false]\
|
|
style[view"..pn..":pressed;bgcolor=#0001;border=false]\
|
|
"..(private and
|
|
"model["..((i %4) *2)..","..4 *math.floor(i / 4)..";2,4;pre;"..player_model(p, "[fill:1x1:#777", not not meta.msa)..";0,210;;false;0,0;]\
|
|
image["..((i %4) *2 +0.75)..","..(4 *math.floor(i / 4) +1.75)..";0.5,0.5;libskinupload_locked.png;]" or
|
|
"model["..((i %4) *2)..","..4 *math.floor(i / 4)..";2,4;pre;"..player_model(p, optimize_media and "\\[png:"..libskinupload.get_skin_data(pn) or pn, not not meta.msa)..";0,210;;false;0,0;]\
|
|
button["..((i %4) *2)..","..4 *math.floor(i / 4)..";2,4;view"..pn..";]")
|
|
i = i +1
|
|
j = j +1
|
|
::continue::
|
|
end
|
|
if i == 0 then
|
|
out = out.."\
|
|
hypertext[-2,-0.5;12,8;;<global valign=middle halign=center size=32 color=#888><b>No matches found.</b>]"
|
|
end
|
|
m.max_pages = math.ceil(j /8) -1
|
|
fs = fsp..fs..out.."\
|
|
container_end[]\
|
|
label[0.5,8.5;Page "..m.page.."]\
|
|
"..(allow_search and "style[searchfor;noclip=true]\
|
|
style[search;noclip=true;border=false]\
|
|
image_button[1,-1;1,1;libskinupload_search.png;search;]\
|
|
field[2,-0.75;8,0.5;searchfor;;"..minetest.formspec_escape(m.search or "").."]\
|
|
tooltip[search;Search;#444;#aaa]\
|
|
field_close_on_enter[searchfor;false]\
|
|
" or "").."\
|
|
button[0.5,9;1,0.5;prev;<<]\
|
|
scrollbaroptions[min=0;max="..m.max_pages..";smallstep=1;largestep=10;arrows=hide]\
|
|
scrollbar[1.5,9;9,0.5;horizontal;page;"..m.page.."]\
|
|
button[10.5,9;1,0.5;next;>>]\
|
|
"
|
|
minetest.show_formspec(name, "libskinupload:choose", fs)
|
|
return
|
|
-- fs = fs.."\
|
|
-- hypertext[-2,-0.5;12,8;;<global valign=middle halign=center size=32 color=#888><b>Searching...</b>]"
|
|
else
|
|
local i = 0
|
|
while true do
|
|
local pn = choose_state["@dirlist"][i +(m.page *8) +1]
|
|
if not pn then break end
|
|
local meta = libskinupload.get_skin_meta(pn) or {p = false, msa = false, c = ""}
|
|
--if not meta then break end
|
|
local private = meta.p and meta.c ~= name
|
|
fs = fs.."style[view"..pn..";bgcolor=#0000;border=false]\
|
|
style[view"..pn..":hovered;bgcolor=#0001;border=false]\
|
|
style[view"..pn..":focused;bgcolor=#0001;border=false]\
|
|
style[view"..pn..":pressed;bgcolor=#0001;border=false]\
|
|
"..(private and
|
|
"model["..((i %4) *2)..","..4 *math.floor(i / 4)..";2,4;pre;"..player_model(p, "[fill:1x1:#777", not not meta.msa)..";0,210;;false;0,0;]\
|
|
image["..((i %4) *2 +0.75)..","..(4 *math.floor(i / 4) +1.75)..";0.5,0.5;libskinupload_locked.png;]" or
|
|
"model["..((i %4) *2)..","..4 *math.floor(i / 4)..";2,4;pre;"..player_model(p, optimize_media and "\\[png:"..libskinupload.get_skin_data(pn) or pn, not not meta.msa)..";0,210;;false;0,0;]\
|
|
button["..((i %4) *2)..","..4 *math.floor(i / 4)..";2,4;view"..pn..";]")
|
|
i = i +1
|
|
if i >= 8 then break end
|
|
end
|
|
if i == 0 then
|
|
fs = fs.."\
|
|
hypertext[-2,-0.5;12,8;;<global valign=middle halign=center size=32 color=#888><b>No skins have been uploaded yet.</b>]"
|
|
end
|
|
end
|
|
|
|
fs = fsp..fs.."\
|
|
container_end[]\
|
|
label[0.5,8.5;Page "..m.page.."]\
|
|
"..(allow_search and "style[searchfor;noclip=true]\
|
|
style[search;noclip=true;border=false]\
|
|
image_button[1,-1;1,1;libskinupload_search.png;search;]\
|
|
field[2,-0.75;8,0.5;searchfor;;"..minetest.formspec_escape(m.search or "").."]\
|
|
tooltip[search;Search;#444;#aaa]\
|
|
field_close_on_enter[searchfor;false]\
|
|
" or "").."\
|
|
button[0.5,9;1,0.5;prev;<<]\
|
|
scrollbaroptions[min=0;max="..choose_state["@max_pages"]..";smallstep=1;largestep=10;arrows=hide]\
|
|
scrollbar[1.5,9;9,0.5;horizontal;page;"..m.page.."]\
|
|
button[10.5,9;1,0.5;next;>>]\
|
|
"
|
|
else
|
|
local id = string.gsub(string.sub(target, string.len("libskinupload_uploaded_skin_."), -1), ".png", "")
|
|
local meta = choose_state["@index"][id] --libskinupload.get_skin_meta(target)
|
|
if not meta then
|
|
meta = {p = false}
|
|
end
|
|
if meta.p and meta.c ~= name then
|
|
fs = fs.."label[0.5,0.5;Insufficient privileges.]"
|
|
return
|
|
end
|
|
local tags = "style[remove_tag;border=false;bgimg=libskinupload_tag_bg.png;bgimg_middle=8;padding=-8]\
|
|
style[remove_tag:hovered;border=false;bgimg=libskinupload_tag_bg_hovered.png;bgimg_middle=8;padding=-8]\
|
|
"
|
|
local x = 0
|
|
local y = 0
|
|
if meta.t then
|
|
for _, tag in ipairs(meta.t) do
|
|
local width = math.max(#tag *0.2, 1.5)
|
|
-- if x +width > 10 then
|
|
-- x = 0
|
|
-- y = y +0.6
|
|
-- end
|
|
tags = tags.."button["..x..","..y..";"..width..",0.5;remove_tag;"..tag.."]\
|
|
"
|
|
x = x +width +0.05
|
|
end
|
|
end
|
|
fs = fsp.."model[0.5,0.5;8,7;pre;"..player_model(p, optimize_media and "\\[png:"..libskinupload.get_skin_data(target) or target, not not meta.msa)..";0,210;;true;0,0;]\
|
|
label[8,1;"..minetest.formspec_escape("ID: "..string.gsub(string.sub(target, string.len("libskinupload_uploaded_skin_."), -1), ".png", "")).."]\
|
|
button[8,2;3,1;confirm;Set Skin]\
|
|
hypertext[8,3.5;3,2;;"..minetest.formspec_escape(minetest.hypertext_escape(meta.n or "N/A")).."]\
|
|
hypertext[0.5,8;8,2;;"..minetest.formspec_escape(markdown(meta.d or "N/A")).."]\
|
|
scroll_container[0.5,7.5;11,1;tagscroll;horizontal;;0]\
|
|
scrollbar[0,-800;8,0;horizontal;tagscroll;0]\
|
|
"..tags.."\
|
|
scroll_container_end[]\
|
|
button[8,6;3,1;back;Back]"
|
|
end
|
|
minetest.show_formspec(name, "libskinupload:choose", fs)
|
|
end
|
|
minetest.register_chatcommand("skinchoose", {
|
|
description = "Show the skin selection dialog.",
|
|
func = function(name, args)
|
|
choose_state[name] = {page = 0}
|
|
libskinupload.show_choose_dialog(name, "")
|
|
end
|
|
})
|
|
|
|
minetest.register_chatcommand("skinchange", {
|
|
params = "<id>",
|
|
description = "Set your skin directly by ID, bypassing the skin selection dialog.",
|
|
func = function(name, args)
|
|
local f = io.open(storage_dir.."libskinupload_uploaded_skin_"..args..".png")
|
|
if not f then
|
|
minetest.chat_send_player(name, "Invalid identifier.")
|
|
return
|
|
else
|
|
f:close()
|
|
end
|
|
local meta = libskinupload.get_skin_meta("libskinupload_uploaded_skin_"..args..".png")
|
|
if meta.p then
|
|
minetest.chat_send_player(name, "Insufficient privileges.")
|
|
return
|
|
end
|
|
libskinupload.set_skin(minetest.get_player_by_name(name), args)
|
|
end
|
|
})
|
|
|
|
minetest.register_chatcommand("skindelete", {
|
|
params = "<id>",
|
|
description = "Remove the target skin from the filesystem.",
|
|
func = function(name, id)
|
|
local meta = {}
|
|
if use_fragmented_meta then
|
|
local f = io.open(storage_dir_meta.."libskinupload_uploaded_skin_"..id..".json")
|
|
if not f then
|
|
minetest.chat_send_player(name, "Invalid identifier.")
|
|
return
|
|
end
|
|
meta = minetest.parse_json(f:read("a")) or {}
|
|
f:close()
|
|
os.remove(storage_dir_meta.."libskinupload_uploaded_skin_"..id..".json")
|
|
else
|
|
local f = io.open(meta_file)
|
|
res = minetest.parse_json(f:read("a"))
|
|
f:close()
|
|
meta = res[tostring(id)]
|
|
res[tostring(id)] = nil
|
|
f = io.open(meta_file, "w+")
|
|
f:write(minetest.write_json(res))
|
|
f:flush()
|
|
f:close()
|
|
end
|
|
os.remove(storage_dir.."libskinupload_uploaded_skin_"..id..".png")
|
|
minetest.chat_send_player(name, "Successfully deleted skin '"..(meta.n or "#"..id).."' from "..(meta.c or "[unknown]")..".")
|
|
end,
|
|
privs = {skin_review = true}
|
|
})
|
|
|
|
minetest.register_chatcommand("skinlimit", {
|
|
privs = {skin_review = true},
|
|
params = "<name> <limit>",
|
|
description = "Set the maximum concurrent skin requests allowed for player <name> to <limit>.",
|
|
func = function(name, args)
|
|
local pn, num = args:match "(%w+)%s+(%d+)"
|
|
num = tonumber(num)
|
|
if pn and pn ~= "" and num and num > 0 then
|
|
db:set_int("reqmax:"..pn, num)
|
|
minetest.chat_send_player(name, "Set "..pn.."'s max concurrent requests to "..num..".")
|
|
else
|
|
minetest.chat_send_player(name, "Invalid parameters.")
|
|
end
|
|
end
|
|
})
|
|
|
|
minetest.register_chatcommand("skinforget", {
|
|
description = "Forget your curent skin, to prevent conflicts when you change your skin to one provided by another mod.",
|
|
func = function(name, args)
|
|
libskinupload.forget_skin(name)
|
|
end
|
|
})
|
|
|
|
function libskinupload.print_skin_info(name, skin)
|
|
local m = libskinupload.get_skin_meta(skin)
|
|
if not m then
|
|
minetest.chat_send_player(name, "Skin `"..skin.."` does not have metadata!")
|
|
return
|
|
end
|
|
minetest.chat_send_player(name, "Info for skin "..skin..":\
|
|
Name: "..m.n.."\
|
|
Description: "..m.d.."\
|
|
Created by: "..m.c.."\
|
|
Tags: "..minetest.write_json(m.t))
|
|
end
|
|
|
|
minetest.register_chatcommand("skinmanage", {
|
|
privs = {skin_review = true},
|
|
params = "cull | list | (meta <id>) | alter <id> <newkey>=<newvalue>[,<newkey2>=<newvalue2>]*",
|
|
func = function(name, args)
|
|
if args == "cull" then
|
|
local i = 0
|
|
for _, x in pairs(minetest.get_dir_list(storage_dir)) do
|
|
if not libskinupload.get_skin_meta(x) then
|
|
os.remove(storage_dir..x)
|
|
minetest.chat_send_player(name, "The skin `"..x.."` was removed because it did not have a matching meta file.")
|
|
i = i +1
|
|
end
|
|
end
|
|
elseif args:match "^meta" then
|
|
libskinupload.print_skin_info(name, "libskinupload_uploaded_skin_"..args:gsub("^meta%s", "")..".png")
|
|
elseif args:match "^alter" then
|
|
local id, changes = args:match "^alter%s+(%d+)%s+(.+)"
|
|
local fname = "libskinupload_uploaded_skin_"..id..".png"
|
|
local m = libskinupload.get_skin_meta(fname)
|
|
for k, v in changes:gmatch "(%w+)%s*=%s*([^,=]+)" do
|
|
if k == "private" or k == "p" then
|
|
k = "p"
|
|
v = v == "true"
|
|
elseif k == "creator" then
|
|
k = "c"
|
|
elseif k == "description" then
|
|
k = "d"
|
|
elseif k == "name" then
|
|
k = "n"
|
|
elseif k == "t" or k == "tags" then
|
|
k = "t"
|
|
v = v:split(",")
|
|
end
|
|
m[k] = v
|
|
end
|
|
libskinupload.set_skin_meta(fname, m)
|
|
elseif args:match "^tag" then
|
|
local id, tags = args:match "^tag%s+(%d+)%s+(.+)"
|
|
local fname = "libskinupload_uploaded_skin_"..id..".png"
|
|
local m = libskinupload.get_skin_meta(fname)
|
|
if not m.t then m.t = {} end
|
|
tags = tags:split(",")
|
|
for _, x in pairs(tags) do
|
|
table.insert(m.t, x)
|
|
end
|
|
libskinupload.set_skin_meta(fname, m)
|
|
elseif args:match "^untag" then
|
|
local id, tags = args:match "^untag%s+(%d+)%s+(.+)"
|
|
local fname = "libskinupload_uploaded_skin_"..id..".png"
|
|
local m = libskinupload.get_skin_meta(fname)
|
|
if not m.t then return end
|
|
tags = tags:split(",")
|
|
local itags = {}
|
|
for _, x in pairs(tags) do
|
|
itags[x] = true
|
|
end
|
|
local rm = {}
|
|
for i, x in pairs(m.t) do
|
|
if itags[x] then rm[i] = true end
|
|
end
|
|
for k in pairs(rm) do
|
|
table.remove(m.t, k)
|
|
end
|
|
libskinupload.set_skin_meta(fname, m)
|
|
elseif args:match "^list" then
|
|
for _, x in pairs(minetest.get_dir_list(storage_dir)) do
|
|
libskinupload.print_skin_info(name, x)
|
|
end
|
|
elseif args:match "^remove_fragmented_meta" then
|
|
minetest.rmdir(storage_dir_meta, true)
|
|
minetest.chat_send_player(name, "Removed all fragmented meta.")
|
|
else
|
|
minetest.chat_send_player(name, "Invalid arguments, see /help skinmanage for accepted subcommands.")
|
|
end
|
|
end,
|
|
description = "Print debug information about libskinupload. /skinmanage meta <id>: Print metadata for the specified skin. Does not validate the provided ID! /skinmanage alter: Change the metadata of a skin. /skinmanage cull: Remove unmatched skin or meta files."
|
|
})
|
|
|
|
local function apply_skin(p, tx, id)
|
|
p:set_properties({
|
|
textures = tx,
|
|
})
|
|
if minetest.get_modpath("u_skins") then
|
|
u_skins.u_skins[p:get_player_name()] = "libskinupload_uploaded_skin_"..id
|
|
u_skins.save()
|
|
elseif mcl then
|
|
mcl_player.player_set_skin(p, "libskinupload_uploaded_skin_"..id..".png")
|
|
mcl_skins.save(p)
|
|
end
|
|
if minetest.get_modpath("3d_armor") then
|
|
armor.textures[p:get_player_name()].skin = "libskinupload_uploaded_skin_"..id..".png"
|
|
end
|
|
end
|
|
|
|
function libskinupload.set_skin(p, id)
|
|
local fname = "libskinupload_uploaded_skin_"..id..".png"
|
|
local meta = libskinupload.get_skin_meta(fname)
|
|
if not meta then
|
|
libskinupload.forget_skin(p)
|
|
minetest.chat_send_player(p:get_player_name(), "Your skin does not exist anymore.")
|
|
return
|
|
end
|
|
if meta.p and meta.c ~= p:get_player_name() then
|
|
--minetest.chat_send_player(p:get_player_name(), "Insufficient permissions.")
|
|
return
|
|
end
|
|
local m = p:get_meta()
|
|
local tx = p:get_properties().textures
|
|
tx[1] = fname..m:get_string("texmod")
|
|
if not libskinupload.enabled[id] then
|
|
libskinupload.add_skin_media(id, false, function(name)
|
|
-- 5s delay to dodge a potential race condition that happens for unknown reasons.
|
|
minetest.after(5, function()
|
|
apply_skin(p, tx, id)
|
|
end)
|
|
end)
|
|
else
|
|
minetest.after(0, function()
|
|
apply_skin(p, tx, id)
|
|
end)
|
|
end
|
|
|
|
db:set_string("skin:"..p:get_player_name(), id)
|
|
end
|
|
|
|
function libskinupload.forget_skin(p)
|
|
db:set_string("skin:"..(type(p) == "string" and p or p:get_player_name()), "")
|
|
end
|
|
|
|
function libskinupload.get_skin_id(name)
|
|
return db:get_string("skin:"..name)
|
|
end
|
|
|
|
function libskinupload.get_skin(name)
|
|
return "libskinupload_uploaded_skin_"..db:get_string("skin:"..name)..".png"
|
|
end
|
|
|
|
function libskinupload.get_skin_meta(skin)
|
|
if use_fragmented_meta then
|
|
local f = io.open(storage_dir_meta..string.gsub(skin, ".png", ".json"))
|
|
local meta
|
|
if f then
|
|
meta = minetest.parse_json(f:read("a"))
|
|
f:close()
|
|
return meta
|
|
else
|
|
return false
|
|
end
|
|
else
|
|
if choose_state["@index"] then
|
|
return choose_state["@index"][string.gsub(string.sub(skin, string.len("libskinupload_uploaded_skin_."), -1), ".png", "")] or {n = "Unnamed", d = "Nondescript", c = "<Unknown>"}
|
|
end
|
|
local f = io.open(meta_file)
|
|
if not f then
|
|
return false
|
|
else
|
|
local res = minetest.parse_json(f:read("a"))
|
|
f:close()
|
|
return res[string.gsub(string.sub(skin, string.len("libskinupload_uploaded_skin_."), -1), ".png", "")] or {n = "Unnamed", d = "Nondescript", c = "<Unknown>"}
|
|
end
|
|
end
|
|
end
|
|
|
|
function libskinupload.set_skin_meta(skin, meta)
|
|
if use_fragmented_meta then
|
|
local f = io.open(storage_dir_meta..string.gsub(skin, ".png", ".json"), "w")
|
|
f:write(minetest.write_json(meta))
|
|
f:close()
|
|
else
|
|
local f = io.open(meta_file)
|
|
if not f then
|
|
return false
|
|
else
|
|
local res = minetest.parse_json(f:read("a"))
|
|
f:close()
|
|
res[string.gsub(string.sub(skin, string.len("libskinupload_uploaded_skin_."), -1), ".png", "")] = meta
|
|
f = io.open(meta_file, "w+")
|
|
f:write(minetest.write_json(res))
|
|
f:flush()
|
|
f:close()
|
|
end
|
|
end
|
|
end
|
|
|
|
minetest.register_chatcommand("skinget", {
|
|
params = "<name>",
|
|
description = "Print the ID of the skin currently worn by the target player.",
|
|
func = function(name, args)
|
|
if args == "" then args = name end
|
|
local p = minetest.get_player_by_name(args)
|
|
if p then
|
|
local id = libskinupload.get_skin_id(args)
|
|
if id == "" then id = "an external skin." else id = "skin #"..id.."." end
|
|
minetest.chat_send_player(name, "Player `"..args.."` is wearing "..id)
|
|
end
|
|
end
|
|
})
|
|
|
|
--Compat
|
|
if minetest.get_modpath("unified_inventory") then
|
|
unified_inventory.register_page("libskinupload", {
|
|
get_formspec = function(p)
|
|
local name = p:get_player_name()
|
|
local tx = p:get_properties().textures[1]
|
|
local fs = (unified_inventory.style_full and unified_inventory.style_full.standard_inv_bg or "background[0.06,0.99;7.92,7.52;ui_misc_form.png]").."\
|
|
model[0.5,0.75;1,2;pre;character.b3d;"..tx..";0,210;;false;0,0;]"
|
|
local meta = {}
|
|
if string.find(tx, "libskinupload_uploaded_skin_", 1, true) then
|
|
if use_fragmented_media then
|
|
local id = string.gsub(string.sub(tx, string.len("libskinupload_uploaded_skin_."), -1), ".png", "")
|
|
local f = io.open(storage_dir_meta.."libskinupload_uploaded_skin_"..id..".json")
|
|
if f then
|
|
meta = minetest.parse_json(f:read("a"))
|
|
f:close()
|
|
end
|
|
else
|
|
meta = libskinupload.get_skin_meta(tx)
|
|
end
|
|
end
|
|
fs = fs .. "label[2,.5;Name: "..minetest.formspec_escape(meta.n or "N/A").."]\
|
|
textarea[2,1;6,2;;;"..minetest.formspec_escape(meta.d or "N/A").."]"
|
|
|
|
fs = fs.."button[.75,3;4.5,.5;libskinupload_chooser;Change]\
|
|
button[5.25,3;2,.5;libskinupload_upload_trigger;Upload Skin]"
|
|
return {formspec = fs}
|
|
end,
|
|
})
|
|
|
|
unified_inventory.register_button("libskinupload", {
|
|
type = "image",
|
|
image = "libskinupload_icon.png",
|
|
tooltip = "Choose Player-Submitted Skin",
|
|
})
|
|
end
|
|
|
|
minetest.register_on_player_receive_fields(function(p, name, data)
|
|
if name == "libskinupload:upload" then
|
|
if minetest.check_player_privs(p, {no_skin_upload = true}) then
|
|
minetest.chat_send_player(p:get_player_name(), "Insufficient permissions.")
|
|
minetest.close_formspec(p:get_player_name(), name)
|
|
return true
|
|
end
|
|
local m = upload_state[p:get_player_name()]
|
|
if data.skin then
|
|
if #data.skin >= 10923 then data.skin = string.sub(data.skin, 1, 10923) end
|
|
m.data = data.skin:gsub("^data:image/png;base64,", ""):gsub("[^A-Za-z0-9+/=]", "")
|
|
end
|
|
if data.key_enter_field == "new_tag" then
|
|
if not m.tags then m.tags = {} end
|
|
if #m.tags >= 50 then
|
|
libskinupload.show_upload_dialog(p:get_player_name(), {err = "Skins may not have more than 50 tags."})
|
|
return true
|
|
end
|
|
if #data.new_tag >= 51 then data.new_tag = string.sub(data.new_tag, 1, 51) end
|
|
m.tags[#m.tags +1] = data.new_tag:lower():gsub("[^a-z_0-9$-]", "")
|
|
libskinupload.show_upload_dialog(p:get_player_name())
|
|
return true
|
|
end
|
|
if data.remove_tag and m.tags then
|
|
local i = 0
|
|
for k, v in pairs(m.tags) do
|
|
if v == data.remove_tag then
|
|
i = k
|
|
break
|
|
end
|
|
end
|
|
table.remove(m.tags, i)
|
|
libskinupload.show_upload_dialog(p:get_player_name())
|
|
return true
|
|
end
|
|
if data.key_enter_field or data.confirm then
|
|
if mcl then
|
|
data.mcl_slim_arms = m.mcl_slim_arms or false
|
|
end
|
|
data.private = m.private or false
|
|
local x = libskinupload.queue(p, m)
|
|
if x then
|
|
libskinupload.show_upload_dialog(p:get_player_name(), {err = x})
|
|
return true
|
|
end
|
|
upload_state[p:get_player_name()] = nil
|
|
minetest.close_formspec(p:get_player_name(), name)
|
|
return true
|
|
end
|
|
if data.quit then
|
|
upload_state[p:get_player_name()] = nil
|
|
return true
|
|
end
|
|
if data.private then
|
|
m.private = data.private
|
|
end
|
|
if data.mcl_slim_arms then
|
|
m.mcl_slim_arms = data.mcl_slim_arms == "true"
|
|
end
|
|
if data.help then
|
|
m.help = not m.help
|
|
end
|
|
m.name = data.skinname
|
|
m.desc = data.skindesc
|
|
if data.add_tag then
|
|
libskinupload.show_upload_dialog(p:get_player_name(), {add_tag = true})
|
|
return true
|
|
end
|
|
libskinupload.show_upload_dialog(p:get_player_name())
|
|
return true
|
|
elseif name == "libskinupload:review" then
|
|
local name = p:get_player_name()
|
|
if not minetest.check_player_privs(p, {skin_review = true}) then
|
|
minetest.chat_send_player(name, "Insufficient permissions.")
|
|
return true
|
|
end
|
|
if data.quit then
|
|
review_state[name] = nil
|
|
return true
|
|
end
|
|
local m = review_state[name]
|
|
if data.back then
|
|
m.req = nil
|
|
m.current = nil
|
|
m.add_tag = nil
|
|
libskinupload.show_review_dialog(name, "")
|
|
return true
|
|
elseif data.showdeny then
|
|
review_state[name].reason = ""
|
|
libskinupload.show_review_dialog(name, review_state[name].current)
|
|
elseif data.closedeny or data.closedeny2 then
|
|
review_state[name].reason = nil
|
|
libskinupload.show_review_dialog(name, review_state[name].current)
|
|
elseif data.deny then
|
|
libskinupload.deny(p, review_state[name].current, data.reason)
|
|
libskinupload.show_review_dialog(name, "")
|
|
return true
|
|
elseif data.accept then
|
|
db:set_string("skinreq:"..m.current:gsub("@", ""), minetest.serialize(m.req))
|
|
libskinupload.accept(p, review_state[name].current)
|
|
libskinupload.show_review_dialog(name, "")
|
|
return true
|
|
end
|
|
if data.key_enter_field == "new_tag" then
|
|
if not m.req.tags then m.req.tags = {} end
|
|
if #m.req.tags >= 50 then
|
|
return true
|
|
end
|
|
if #data.new_tag >= 51 then data.new_tag = string.sub(data.new_tag, 1, 51) end
|
|
m.req.tags[#m.req.tags +1] = data.new_tag:lower():gsub("[^a-z_0-9$-]", "")
|
|
m.add_tag = nil
|
|
libskinupload.show_review_dialog(name, review_state[name].current)
|
|
return true
|
|
end
|
|
if data.remove_tag then
|
|
if not m.req.tags then m.req.tags = {} end
|
|
if #m.req.tags < 1 then
|
|
return true
|
|
end
|
|
local idx = 0
|
|
for i, x in pairs(m.req.tags) do
|
|
if x == data.remove_tag then
|
|
idx = i
|
|
break
|
|
end
|
|
end
|
|
table.remove(m.req.tags, idx)
|
|
m.add_tag = nil
|
|
libskinupload.show_review_dialog(name, review_state[name].current)
|
|
return true
|
|
end
|
|
if data.add_tag then
|
|
m.add_tag = true
|
|
libskinupload.show_review_dialog(name, review_state[name].current)
|
|
return true
|
|
end
|
|
for k, v in pairs(data) do
|
|
if string.find(k, "view", 1, true) then
|
|
review_state[p:get_player_name()].current = string.gsub(k, "^view", "")
|
|
libskinupload.show_review_dialog(name, review_state[name].current)
|
|
return true
|
|
end
|
|
end
|
|
return true
|
|
elseif name == "libskinupload:choose" then
|
|
if data.quit then
|
|
choose_state[p:get_player_name()] = nil
|
|
choose_state["@dirlist"] = nil
|
|
choose_state["@index"] = nil
|
|
return true
|
|
end
|
|
local m = choose_state[p:get_player_name()]
|
|
if data.back then
|
|
m.current = ""
|
|
libskinupload.show_choose_dialog(p:get_player_name())
|
|
return true
|
|
elseif data.confirm then
|
|
libskinupload.set_skin(p, string.gsub(string.sub(m.current, string.len("libskinupload_uploaded_skin_."), -1), ".png", ""))
|
|
minetest.close_formspec(p:get_player_name(), name)
|
|
return true
|
|
end
|
|
if allow_search and (data.search or data.key_enter_field == "searchfor") then
|
|
if #data.searchfor > 2 then
|
|
m.search = data.searchfor
|
|
else
|
|
m.search = nil
|
|
m.max_pages = nil
|
|
end
|
|
m.page = 0
|
|
libskinupload.show_choose_dialog(p:get_player_name())
|
|
return true
|
|
end
|
|
for k, v in pairs(data) do
|
|
if string.find(k, "view", 1, true) then
|
|
m.current = string.gsub(k, "^view", "")
|
|
libskinupload.show_choose_dialog(p:get_player_name())
|
|
return true
|
|
end
|
|
end
|
|
if data.page then
|
|
local ev = minetest.explode_scrollbar_event(data.page)
|
|
if ev.type == "CHG" then
|
|
m.page = ev.value
|
|
libskinupload.show_choose_dialog(p:get_player_name())
|
|
end
|
|
end
|
|
if data.prev then
|
|
m.page = m.page -1
|
|
if m.page < 0 then
|
|
m.page = m.max_pages or choose_state["@max_pages"]
|
|
end
|
|
libskinupload.show_choose_dialog(p:get_player_name())
|
|
elseif data.next then
|
|
m.page = m.page +1
|
|
if m.page > (m.max_pages or choose_state["@max_pages"]) then
|
|
m.page = 0
|
|
end
|
|
libskinupload.show_choose_dialog(p:get_player_name())
|
|
end
|
|
return true
|
|
elseif data.libskinupload_chooser then
|
|
choose_state[p:get_player_name()] = {page = 0}
|
|
libskinupload.show_choose_dialog(p:get_player_name())
|
|
elseif data.libskinupload_upload_trigger then
|
|
libskinupload.show_upload_dialog(p:get_player_name())
|
|
end
|
|
end)
|
|
|
|
|
|
--[[
|
|
minetest.register_on_joinplayer(function(p)
|
|
for i = 1, 100 do
|
|
local rd = ""
|
|
for i = 1,10 do
|
|
rd = rd..string.char(math.floor(math.random() *2) +97)
|
|
end
|
|
db:set_string("skinreq:a"..i, minetest.serialize{name = "",desc = rd, data = "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"})
|
|
libskinupload.accept(p, "a"..i)
|
|
end
|
|
end)
|
|
--]] |