4 Commits

Author SHA1 Message Date
dd396bfad2 Upload C# source code, raw 2015-09-25 22:46:42 +02:00
f389e6bd13 More efficient python script
- Fixed pipelining
- Cleaning everything up
- Don't re-download saved characters
- Add the media to .gitignore

About pipelining:

According to python:
1) you send a request
2) you MUST get response headers for (1) (THIS IS MANDATORY)
3) you send another request
4) you get response body for (2)
5) response headers for (3)
6) response body for (5)

Only two requests can be pipelined. Surely this is an unavoidable, wait no it's just written into the code to error out if you don't do it that way.

according to reality:
1) you send a request
2) you do not get response headers for (1)
3) you repeat steps 1-2 until enough responses are queued
4) you receive those responses as header,body,header,body...

they even name it with a __ so to make it hard to override, but the state can safely go to Idle after a request has sent, whether or not response headers have come in. Sure the connection might close, but then you adjust to not pipeline, and re-send the rest of your requests over a new connection.
2015-09-25 22:43:54 +02:00
e762283dec Use simple_skins if default selected 2015-04-11 15:27:41 +02:00
5cb484e251 Add tooltips 2014-11-23 20:00:52 +01:00
168 changed files with 492 additions and 288 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
character_*.png
character_*.txt

200
MT_skins_updater.cs Normal file
View File

@ -0,0 +1,200 @@
using System;
//Json.NET library (http://json.codeplex.com/)
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net;
using System.IO;
// MT skins updater for the u_skins mod
// Creator: Krock
// License: zlib (http://www.zlib.net/zlib_license.html)
namespace MT_skins_updater {
class Program {
static void Main(string[] args) {
Console.WriteLine("Welcome to the MT skins updater!");
Console.WriteLine("# Created by: Krock (2014-07-10)");
Engine e = new Engine();
Console.WriteLine(@"Path to the u_skins mod: (ex. 'E:\Minetest\mods\u_skinsdb\u_skins\')");
string path = Console.ReadLine();
Console.WriteLine("Start updating at page: ('0' to update everything)");
int page = getInt(Console.ReadLine());
e.Start(path, page);
Console.WriteLine("Press any key to exit.");
Console.ReadKey(false);
}
public static int getInt(string i) {
int ret = 0;
int.TryParse(i, out ret);
return (ret > 0)? ret : 0;
}
}
class Engine {
string root = "http://minetest.fensta.bplaced.net";
bool alternate = true; //should it use the special version of medadata saving?
public void Start(string path, int page) {
if (path.Length < 5) {
Console.WriteLine("Too short path. STOP.");
return;
}
if (path[path.Length - 1] != '\\') {
path += '\\';
}
if(!Directory.Exists(path + "meta")){
Console.WriteLine("Folder 'meta' not found. STOP.");
return;
}
if(!Directory.Exists(path + "textures")){
Console.WriteLine("Folder 'textures' not found. STOP.");
return;
}
WebClient cli = new WebClient();
//add useragent to identify
cli.Headers.Add("User-Agent", "MT_skin_grabber 1.1");
bool firstSkin = true;
List<string> skin_local = new List<string>();
int pages = page,
updated = 0;
for (; page <= pages; page++) {
string contents = "";
try {
contents = cli.DownloadString(root + "/api/get.json.php?getlist&page=" + page);
} catch(WebException e) {
Console.WriteLine("Whoops! Error at page ID: " + page + ". WebClient sais: " + e.Message);
Console.WriteLine("Press any key to skip this page.");
Console.ReadKey(false);
continue;
}
Data o = JsonConvert.DeserializeObject<Data>(contents);
if (o.pages != pages) {
pages = o.pages;
}
Console.WriteLine("# Page " + page + " (" + o.per_page + " skins)");
for (int i = 0; i < o.skins.Length; i++) {
int id = o.skins[i].id;
if(o.skins[i].type != "image/png"){
Console.WriteLine("Image type '" + o.skins[i].type + "' not supported at skin ID: " + id);
Console.WriteLine("Press any key to continue.");
Console.ReadKey(false);
continue;
}
//eliminate special chars!
o.skins[i].name = WebUtility.HtmlDecode(o.skins[i].name);
o.skins[i].author = WebUtility.HtmlDecode(o.skins[i].author);
//to delete old, removed skins
if (firstSkin) {
firstSkin = false;
string[] files = Directory.GetFiles(path + "textures\\");
for (int f = 0; f < files.Length; f++) {
string[] filePath = stringSplitLast(files[f], '\\'),
fileName = stringSplitLast(filePath[1], '.'),
fileVer = stringSplitLast(fileName[0], '_');
if (fileVer[1] == "" || fileVer[0] != "character") continue;
int skinNr = Program.getInt(fileVer[1]);
if (skinNr <= id) continue;
skin_local.Add(fileName[0]);
}
} else skin_local.Remove("character_" + id);
//get file size, only override changed
FileInfo localImg = new FileInfo(path + "textures\\character_" + id + ".png");
byte[] imageData = Convert.FromBase64String(o.skins[i].img);
bool isDif = true;
if (localImg.Exists) isDif = (Math.Abs(imageData.Length - localImg.Length) >= 3);
if (isDif) {
File.WriteAllBytes(localImg.FullName, imageData);
imageData = null;
//previews
try {
cli.DownloadFile(root + "/skins/1/" + id + ".png", path + "textures\\character_" + id + "_preview.png");
} catch (WebException e) {
Console.WriteLine("Whoops! Error at skin ID: " + id + ". WebClient sais: " + e.Message);
Console.WriteLine("Press any key to continue.");
Console.ReadKey(false);
}
} else {
Console.WriteLine("[SKIP] character_" + id);
continue;
}
string meta = "";
if (!alternate) {
meta = "name = \"" + o.skins[i].name + "\",\n";
meta += "author = \"" + o.skins[i].author + "\",\n";
meta += "comment = \"" + o.skins[i].license + '"';
} else {
meta = o.skins[i].name + '\n' + o.skins[i].author + '\n' + o.skins[i].license;
}
File.WriteAllText(path + "meta\\character_" + id + ".txt", meta);
updated++;
Console.WriteLine("[" + id + "] " + shorten(o.skins[i].name, 20) + "\t by: " + o.skins[i].author + "\t (" + o.skins[i].license + ")");
}
}
foreach (string fileName in skin_local) {
if(File.Exists(path + "textures\\" + fileName + ".png")) {
File.Delete(path + "textures\\" + fileName + ".png");
}
if(File.Exists(path + "textures\\" + fileName + "_preview.png")) {
File.Delete(path + "textures\\" + fileName + "_preview.png");
}
if(File.Exists(path + "meta\\" + fileName + ".txt")) {
File.Delete(path + "meta\\" + fileName + ".txt");
}
Console.WriteLine("[DEL] " + fileName + " (deleted skin)");
}
Console.WriteLine("Done. Updated " + updated + " skins!");
}
string shorten(string inp, int len) {
char[] shr = new char[len];
for (int i = 0; i < len; i++) {
if (i < inp.Length) {
shr[i] = inp[i];
} else shr[i] = ' ';
}
return new string(shr);
}
string[] stringSplitLast(string path, char limiter) {
int found = 0;
int totalLen = path.Length - 1;
for (int i = totalLen; i >= 0; i--) {
if (path[i] == limiter) {
found = i;
break;
}
}
if (found == 0) {
return new string[] { "", "" };
}
int len = totalLen - found;
char[] str_1 = new char[found],
str_2 = new char[len];
for (int i = 0; i < path.Length; i++) {
if (i == found) continue;
if (i < found) {
str_1[i] = path[i];
} else {
str_2[i - found - 1] = path[i];
}
}
return new string[] { new string(str_1), new string(str_2) };
}
}
class Data {
public Skins_data[] skins;
public int page, pages, per_page;
}
class Skins_data {
public string name, author, uploaded, type, license, img;
public int id, license_id;
}
}

BIN
MT_skins_updater.exe Normal file

Binary file not shown.

BIN
Newtonsoft.Json.dll Normal file

Binary file not shown.

0
README Executable file → Normal file
View File

View File

@ -1,85 +0,0 @@
#!/bin/env python2
from __future__ import print_function
import sys, os, subprocess
from os import listdir
from os.path import isfile, join, sep
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def which(program):
import os
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
try:
from PIL import Image
except ImportError:
eprint("Could not import PIL, is it installed ?")
sys.exit(1)
uskins_path = "u_skins"
meta_path = join(uskins_path, "meta")
textures_path = join(uskins_path, "textures")
pngcrush = which('pngcrush')
optipng = which('optipng')
def process_copies(src, dst, copies):
for copy in copies:
srcrect = copy[0]
dstrect = copy[1]
flip = copy[2] if len(copy) > 2 else None
region = src.crop(srcrect)
if flip is not None:
region = region.transpose(flip)
dst.paste(region, dstrect)
def make_preview(src, dst):
skin = Image.open(src)
preview = Image.new('RGBA', (16, 32))
process_copies(skin, preview, [
[(8, 8, 16, 16), (4, 0)], # Head
[(44, 20, 48, 32), (0, 8)], # Left arm
[(44, 20, 48, 32), (12, 8), Image.FLIP_LEFT_RIGHT], # Right arm
[(20, 20, 28, 32), (4, 8)], # Body
[(4, 20, 8, 32), (4, 20)], # Left leg
[(4, 20, 8, 32), (8, 20), Image.FLIP_LEFT_RIGHT], # Right leg
])
overlay = Image.new('RGBA', (16, 32))
process_copies(skin, overlay, [
[(40, 8, 48, 16), (4, 0)], # Head
])
preview = Image.alpha_composite(preview, overlay)
preview.save(dst)
if optipng is not None:
p = subprocess.Popen([optipng, '-o7', '-quiet', dst])
p.wait()
elif pngcrush is not None:
p = subprocess.Popen([pngcrush, '-ow', '-s', dst])
p.wait()
if __name__ == '__main__':
metas = [f for f in listdir(meta_path) if
isfile(join(meta_path, f)) and
f.endswith(".txt")]
for meta in metas:
f = open(join(meta_path, meta), 'r')
metadata = f.read().splitlines()
f.close()
skin = meta[:-4]
print("Processing {} \"{}\" by {} ({})...".format(skin, metadata[0], metadata[1], metadata[2]))
make_preview(join(textures_path, skin + ".png"), join(textures_path, skin + "_preview.png"))

0
modpack.txt Executable file → Normal file
View File

1
u_skins/depends.txt Executable file → Normal file
View File

@ -1,2 +1,3 @@
unified_inventory
default
simple_skins?

21
u_skins/init.lua Executable file → Normal file
View File

@ -9,6 +9,7 @@ u_skins.default = "character_1"
u_skins.pages = {}
u_skins.u_skins = {}
u_skins.file_save = false
u_skins.simple_skins = false
-- ( Deprecated
u_skins.type = { SPRITE=0, MODEL=1, ERROR=99 }
@ -33,15 +34,22 @@ end
dofile(u_skins.modpath.."/skinlist.lua")
dofile(u_skins.modpath.."/players.lua")
if rawget(_G, "skins") then
u_skins.simple_skins = true
end
u_skins.update_player_skin = function(player)
local name = player:get_player_name()
if u_skins.simple_skins and u_skins.u_skins[name] == u_skins.default then
return
end
if not u_skins.is_skin(u_skins.u_skins[name]) then
u_skins.u_skins[name] = u_skins.default
end
player:set_properties({
textures = {u_skins.u_skins[name]..".png"},
})
u_skins.file_save = true
end
-- Display Current Skin
@ -51,12 +59,12 @@ unified_inventory.register_page("u_skins", {
if not u_skins.is_skin(u_skins.u_skins[name]) then
u_skins.u_skins[name] = u_skins.default
end
local formspec = ("background[0.06,0.99;7.92,7.52;ui_misc_form.png]"
.."image[0,.75;1,2;"..u_skins.u_skins[name].."_preview.png]"
.."label[6,.5;Raw texture:]"
.."image[6,1;2,1;"..u_skins.u_skins[name]..".png]")
local meta = u_skins.meta[u_skins.u_skins[name]]
if meta then
if meta.name ~= "" then
@ -84,8 +92,6 @@ unified_inventory.register_page("u_skins", {
unified_inventory.register_button("u_skins", {
type = "image",
image = "u_skins_button.png",
tooltip = "Skin inventory",
show_with = false, -- modif MFF (Crabman 30/06/2015)
})
-- Create all of the skin-picker pages.
@ -127,7 +133,7 @@ u_skins.generate_pages = function(texture)
.."button[0,3.8;1,.5;u_skins_page$"..page_prev..";<<]"
.."button[.75,3.8;6.5,.5;u_skins_null;Page "..page.."/"..total_pages.."]"
.."button[7,3.8;1,.5;u_skins_page$"..page_next..";>>]")
unified_inventory.register_page("u_skins_page$"..(page - 1), {
get_formspec = function(player)
return {formspec=formspec}
@ -147,6 +153,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if current[1] == "u_skins_set" then
u_skins.u_skins[player:get_player_name()] = u_skins.list[tonumber(current[2])]
u_skins.update_player_skin(player)
u_skins.file_save = true
unified_inventory.set_inventory_formspec(player, "u_skins")
elseif current[1] == "u_skins_page" then
u_skins.pages[player:get_player_name()] = current[2]
@ -165,4 +172,4 @@ minetest.register_on_joinplayer(function(player)
end)
u_skins.generate_pages()
u_skins.load_players()
u_skins.load_players()

View File

@ -1,15 +0,0 @@
u_skins.meta = {}
for _, i in ipairs(u_skins.list) do
u_skins.meta[i] = {}
local f = io.open(u_skins.modpath.."/meta/"..i..".txt")
local data = nil
if f then
data = minetest.deserialize("return {"..f:read('*all').."}")
f:close()
end
data = data or {}
u_skins.meta[i].name = data.name or ""
u_skins.meta[i].author = data.author or ""
u_skins.meta[i].description = data.description or nil
u_skins.meta[i].comment = data.comment or nil
end

4
u_skins/meta/character_1.txt Executable file → Normal file
View File

@ -1,3 +1,3 @@
Sam II
Sam 0
Jordach
CC BY-SA 3.0
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
lordphoenixmh
lordphoenixmh
CC BY 4.0

View File

@ -1,3 +0,0 @@
Ladyvioletkitty
lordphoenixmh
CC BY 4.0

View File

@ -1,3 +0,0 @@
Jaded Bow
jadedtest
CC BY 4.0

View File

@ -1,3 +0,0 @@
Trevor
Ferdi Napoli
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
ranta mk 2
ranta
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Mammu
hansuke123
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Sasuke
Bajanhgk
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
Hunky Simon with Jacket
Andromeda
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
Jayne
Andromeda
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
Red-brown-shirt-dude
Krock
CC BY-SA 4.0

6
u_skins/meta/character_2.txt Executable file → Normal file
View File

@ -1,3 +1,3 @@
Azou
Azeddine
CC BY-SA 3.0
Sam I
Jordach
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
ColerArt26
Colerart_26
CC BY-SA 4.0

View File

@ -1,3 +0,0 @@
BrightGirl
Malarif
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
take bake the night studant
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
dwarf from lottmob
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Pirate girl
Misty
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
elf from lottmob
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
gondor guard from lottmob
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
hobbit from lottmob
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
rohan guard from lottmob
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
npc trader from mobf
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Older Man Sam
philipbenr
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Adventer girl
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Adventurer
XSuperSaintX
CC BY 3.0

View File

@ -1,3 +0,0 @@
Builder
Jyrgenf
CC BY 3.0

View File

@ -1,3 +0,0 @@
Orange
Wuzzy
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
thewillyrex
edwar masterchieft
CC BY-SA 4.0

View File

@ -1,3 +0,0 @@
war-sloop
ange_black69
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
test
addi
CC 0 (1.0)

View File

@ -1,3 +0,0 @@
Tree
Evergreen
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
jojoa1997 2
jojoa1997
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
RockerLuke skin
RockerLuke
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Summer Sam
philipbenr
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Tails
Ferdi Napoli
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
Adventer girl
lovehart
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Samantha I
philipbenr
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Summer
lizzie
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
lisa
hansuke123
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
Hobo/Homeless person
Minetestian
CC BY-SA 3.0

View File

@ -1,3 +0,0 @@
manoel1500
manoel1500
CC BY-NC-SA 3.0

View File

@ -1,3 +0,0 @@
aquaman
GPL3

View File

@ -1,3 +0,0 @@
Matrix
Obani
GPL3

View File

@ -1,3 +0,0 @@
Cyclède
GPL3

View File

@ -1,3 +0,0 @@
mystic
GPL3

View File

@ -1,3 +0,0 @@
Obani
Obani
GPL3

View File

@ -1,3 +0,0 @@
strangekiller
GPL3

View File

@ -1,3 +0,0 @@
walkingdead
GPL3

View File

@ -1,3 +0,0 @@
Cyberpangolin official
Cyberpangolin
??

View File

@ -1,3 +0,0 @@
Greyscale Anhedonia
Mg
WTFPL

View File

@ -0,0 +1 @@
Please run the update_from_db.py script to update the skins.

13
u_skins/players.lua Executable file → Normal file
View File

@ -10,12 +10,15 @@ u_skins.load_players = function()
end
u_skins.load_players()
local function tick()
minetest.after(120, tick) --every 2min'
local ttime = 0
minetest.register_globalstep(function(t)
ttime = ttime + t
if ttime < 360 then --every 6min'
return
end
ttime = 0
u_skins.save()
end
minetest.after(120, tick)
end)
minetest.register_on_shutdown(function() u_skins.save() end)

30
u_skins/skinlist.lua Executable file → Normal file
View File

@ -10,41 +10,17 @@ while fetched_skip < 40 do
if file then
local data = string.split(file:read("*all"), "\n", 3)
file:close()
u_skins.list[internal_id] = name
u_skins.meta[name] = {}
u_skins.meta[name].name = data[1]
u_skins.meta[name].author = data[2]
u_skins.meta[name].license = data[3]
u_skins.meta[name].description = "" --what's that??
fetched_skip = 0
internal_id = internal_id + 1
end
fetched_skip = fetched_skip + 1
id = id + 1
end
-- MODIFICATION MADE FOR MFF
id = 1
fetched_skip = 0
while fetched_skip < 40 do
local name = "mff_character_"..id
local file = io.open(u_skins.modpath.."/meta/"..name..".txt", "r")
if file then
local data = string.split(file:read("*all"), "\n", 3)
file:close()
u_skins.list[internal_id] = name
u_skins.meta[name] = {}
u_skins.meta[name].name = data[1]
u_skins.meta[name].author = data[2]
u_skins.meta[name].license = data[3] or ""
u_skins.meta[name].description = ""
fetched_skip = 0
internal_id = internal_id + 1
end
fetched_skip = fetched_skip + 1
id = id + 1
end
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 800 B

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 B

Some files were not shown because too many files have changed in this diff Show More