Add /npc skin --file

This commit is contained in:
fullwall 2023-03-29 01:28:08 +08:00
parent c171e3f29b
commit f29780874d
5 changed files with 142 additions and 69 deletions

View File

@ -415,6 +415,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
return new ShopTrait(shops); return new ShopTrait(shops);
})); }));
selector = new NPCSelector(this); selector = new NPCSelector(this);
Bukkit.getPluginManager().registerEvents(new EventListen(storedRegistries), this); Bukkit.getPluginManager().registerEvents(new EventListen(storedRegistries), this);
Bukkit.getPluginManager().registerEvents(new Placeholders(), this); Bukkit.getPluginManager().registerEvents(new Placeholders(), this);
Placeholders.registerNPCPlaceholder(Pattern.compile("command_[a-zA-Z_0-9]+"), (npc, sender, input) -> { Placeholders.registerNPCPlaceholder(Pattern.compile("command_[a-zA-Z_0-9]+"), (npc, sender, input) -> {

View File

@ -1,12 +1,7 @@
package net.citizensnpcs.commands; package net.citizensnpcs.commands;
import java.io.BufferedReader; import java.io.File;
import java.io.DataOutputStream; import java.nio.file.Files;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -43,7 +38,6 @@ import org.bukkit.entity.Zombie;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
@ -148,6 +142,7 @@ import net.citizensnpcs.trait.WolfModifiers;
import net.citizensnpcs.trait.waypoint.Waypoints; import net.citizensnpcs.trait.waypoint.Waypoints;
import net.citizensnpcs.util.Anchor; import net.citizensnpcs.util.Anchor;
import net.citizensnpcs.util.Messages; import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.MojangSkinGenerator;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.PlayerAnimation; import net.citizensnpcs.util.PlayerAnimation;
import net.citizensnpcs.util.StringHelper; import net.citizensnpcs.util.StringHelper;
@ -2609,7 +2604,7 @@ public class NPCCommands {
@Command( @Command(
aliases = { "npc" }, aliases = { "npc" },
usage = "skin (-c(lear) -l(atest)) [name] (or --url [url] or -t [uuid/name] [data] [signature])", usage = "skin (-c(lear) -l(atest)) [name] (or --url [url] --file [file] or -t [uuid/name] [data] [signature])",
desc = "Sets an NPC's skin name. Use -l to set the skin to always update to the latest", desc = "Sets an NPC's skin name. Use -l to set the skin to always update to the latest",
modifiers = { "skin" }, modifiers = { "skin" },
min = 1, min = 1,
@ -2617,73 +2612,48 @@ public class NPCCommands {
flags = "ctl", flags = "ctl",
permission = "citizens.npc.skin") permission = "citizens.npc.skin")
@Requirements(types = EntityType.PLAYER, selected = true, ownership = true) @Requirements(types = EntityType.PLAYER, selected = true, ownership = true)
public void skin(final CommandContext args, final CommandSender sender, final NPC npc, @Flag("url") String url) public void skin(final CommandContext args, final CommandSender sender, final NPC npc, @Flag("url") String url,
throws CommandException { @Flag("file") String file) throws CommandException {
String skinName = npc.getName(); String skinName = npc.getName();
final SkinTrait trait = npc.getOrAddTrait(SkinTrait.class); final SkinTrait trait = npc.getOrAddTrait(SkinTrait.class);
if (args.hasFlag('c')) { if (args.hasFlag('c')) {
trait.clearTexture(); trait.clearTexture();
} else if (url != null) { } else if (url != null || file != null) {
Bukkit.getScheduler().runTaskAsynchronously(CitizensAPI.getPlugin(), new Runnable() { Messaging.sendErrorTr(sender, Messages.FETCHING_SKIN, file);
@Override Bukkit.getScheduler().runTaskAsynchronously(CitizensAPI.getPlugin(), () -> {
public void run() { try {
DataOutputStream out = null; JSONObject data = null;
BufferedReader reader = null; if (file != null) {
try { File skin = new File(new File(CitizensAPI.getDataFolder(), "skins"), file);
URL target = new URL("https://api.mineskin.org/generate/url"); if (!skin.exists()
HttpURLConnection con = (HttpURLConnection) target.openConnection(); || !skin.getParentFile().equals(new File(CitizensAPI.getDataFolder(), "skins"))) {
con.setRequestMethod("POST"); Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(),
con.setDoOutput(true); () -> Messaging.sendErrorTr(sender, Messages.INVALID_SKIN_FILE, file));
con.setConnectTimeout(1000); return;
con.setReadTimeout(30000);
out = new DataOutputStream(con.getOutputStream());
out.writeBytes("url=" + URLEncoder.encode(url, "UTF-8"));
out.close();
reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
JSONObject output = (JSONObject) new JSONParser().parse(reader);
JSONObject data = (JSONObject) output.get("data");
String uuid = (String) data.get("uuid");
JSONObject texture = (JSONObject) data.get("texture");
String textureEncoded = (String) texture.get("value");
String signature = (String) texture.get("signature");
con.disconnect();
Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(), new Runnable() {
@Override
public void run() {
try {
trait.setSkinPersistent(uuid, signature, textureEncoded);
Messaging.sendTr(sender, Messages.SKIN_URL_SET, npc.getName(), url);
} catch (IllegalArgumentException e) {
Messaging.sendErrorTr(sender, Messages.ERROR_SETTING_SKIN_URL, url);
}
}
});
} catch (Throwable t) {
if (Messaging.isDebugging()) {
t.printStackTrace();
}
Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(), new Runnable() {
@Override
public void run() {
Messaging.sendErrorTr(sender, Messages.ERROR_SETTING_SKIN_URL, url);
}
});
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
} }
data = MojangSkinGenerator.generateFromPNG(Files.readAllBytes(skin.toPath()));
} else {
MojangSkinGenerator.generateFromURL(url);
} }
String uuid = (String) data.get("uuid");
JSONObject texture = (JSONObject) data.get("texture");
String textureEncoded = (String) texture.get("value");
String signature = (String) texture.get("signature");
Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(), () -> {
try {
trait.setSkinPersistent(uuid, signature, textureEncoded);
Messaging.sendTr(sender, Messages.SKIN_URL_SET, npc.getName(), url);
} catch (IllegalArgumentException e) {
Messaging.sendErrorTr(sender, Messages.ERROR_SETTING_SKIN_URL, url);
}
});
} catch (Throwable t) {
if (Messaging.isDebugging()) {
t.printStackTrace();
}
Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(),
() -> Messaging.sendErrorTr(sender, Messages.ERROR_SETTING_SKIN_URL, url));
} }
}); });
return; return;
} else if (args.hasFlag('t')) { } else if (args.hasFlag('t')) {

View File

@ -114,6 +114,7 @@ public class Messages {
public static final String FAILED_LOAD_SAVES = "citizens.saves.load-failed"; public static final String FAILED_LOAD_SAVES = "citizens.saves.load-failed";
public static final String FAILED_TO_MOUNT_NPC = "citizens.commands.npc.mount.failed"; public static final String FAILED_TO_MOUNT_NPC = "citizens.commands.npc.mount.failed";
public static final String FAILED_TO_REMOVE = "citizens.commands.trait.failed-to-remove"; public static final String FAILED_TO_REMOVE = "citizens.commands.trait.failed-to-remove";
public static final String FETCHING_SKIN = "citizens.commands.npc.skin.fetching";
public static final String FLYABLE_SET = "citizens.commands.npc.flyable.set"; public static final String FLYABLE_SET = "citizens.commands.npc.flyable.set";
public static final String FLYABLE_UNSET = "citizens.commands.npc.flyable.unset"; public static final String FLYABLE_UNSET = "citizens.commands.npc.flyable.unset";
public static final String FOLLOW_PLAYER_NOT_INGAME = "citizens.commands.npc.follow.player-not-ingame"; public static final String FOLLOW_PLAYER_NOT_INGAME = "citizens.commands.npc.follow.player-not-ingame";
@ -189,6 +190,7 @@ public class Messages {
public static final String INVALID_SHEEP_COLOR = "citizens.commands.npc.sheep.invalid-color"; public static final String INVALID_SHEEP_COLOR = "citizens.commands.npc.sheep.invalid-color";
public static final String INVALID_SHULKER_COLOR = "citizens.commands.npc.shulker.invalid-color"; public static final String INVALID_SHULKER_COLOR = "citizens.commands.npc.shulker.invalid-color";
public static final String INVALID_SKELETON_TYPE = "citizens.commands.npc.skeletontype.invalid-type"; public static final String INVALID_SKELETON_TYPE = "citizens.commands.npc.skeletontype.invalid-type";
public static final String INVALID_SKIN_FILE = "citizens.commands.npc.skin.invalid-file";
public static final String INVALID_SOUND = "citizens.commands.npc.sound.invalid-sound"; public static final String INVALID_SOUND = "citizens.commands.npc.sound.invalid-sound";
public static final String INVALID_SPAWN_LOCATION = "citizens.commands.npc.create.invalid-location"; public static final String INVALID_SPAWN_LOCATION = "citizens.commands.npc.create.invalid-location";
public static final String INVALID_TRIGGER_TELEPORT_FORMAT = "citizens.editors.waypoints.triggers.teleport.invalid-format"; public static final String INVALID_TRIGGER_TELEPORT_FORMAT = "citizens.editors.waypoints.triggers.teleport.invalid-format";

View File

@ -0,0 +1,98 @@
package net.citizensnpcs.util;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
public class MojangSkinGenerator {
public static JSONObject generateFromPNG(final byte[] png) throws InterruptedException, ExecutionException {
return EXECUTOR.submit(() -> {
DataOutputStream out = null;
BufferedReader reader = null;
try {
URL target = new URL("https://api.mineskin.org/generate/upload");
HttpURLConnection con = (HttpURLConnection) target.openConnection();
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setRequestProperty("Cache-Control", "no-cache");
con.setRequestProperty("Content-Type", "multipart/form-data;boundary=*****");
con.setConnectTimeout(1000);
con.setReadTimeout(30000);
out = new DataOutputStream(con.getOutputStream());
out.writeBytes("--*****\r\n");
out.writeBytes("Content-Disposition: form-data; name=\"skin.png\";filename=\"skin.png\"\r\n\r\n");
out.write(png);
out.writeBytes("\r\n");
out.writeBytes("--*****--\r\n");
out.flush();
out.close();
reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
JSONObject output = (JSONObject) new JSONParser().parse(reader);
JSONObject data = (JSONObject) output.get("data");
con.disconnect();
return data;
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
}).get();
}
public static JSONObject generateFromURL(final String url) throws InterruptedException, ExecutionException {
return EXECUTOR.submit(() -> {
DataOutputStream out = null;
BufferedReader reader = null;
try {
URL target = new URL("https://api.mineskin.org/generate/url");
HttpURLConnection con = (HttpURLConnection) target.openConnection();
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setConnectTimeout(1000);
con.setReadTimeout(30000);
out = new DataOutputStream(con.getOutputStream());
out.writeBytes("url=" + URLEncoder.encode(url, "UTF-8"));
out.close();
reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
JSONObject output = (JSONObject) new JSONParser().parse(reader);
JSONObject data = (JSONObject) output.get("data");
con.disconnect();
return data;
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
}).get();
}
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
}

View File

@ -276,6 +276,8 @@ citizens.commands.npc.skin.error-setting-url=Error downloading skin texture from
citizens.commands.npc.skin.skin-url-set=Downloaded [[{0}]]''s skin from [[{1}]]. citizens.commands.npc.skin.skin-url-set=Downloaded [[{0}]]''s skin from [[{1}]].
citizens.commands.npc.skin.set=[[{0}]]''s skin name set to [[{1}]]. citizens.commands.npc.skin.set=[[{0}]]''s skin name set to [[{1}]].
citizens.commands.npc.skin.missing-skin=A skin name is required. citizens.commands.npc.skin.missing-skin=A skin name is required.
citizens.commands.npc.skin.fetching=Attempting to generate skin using https://mineskin.org
citizens.commands.npc.skin.invalid-file=Skin file [[{0}]] not found. Must be a file under plugins/Citizens2/skins/<file.png>
citizens.commands.npc.skin.cleared=[[{0}]]''s skin name was cleared. citizens.commands.npc.skin.cleared=[[{0}]]''s skin name was cleared.
citizens.commands.npc.skin.layers-set=[[{0}]]''s skin layers: cape - [[{1}]], hat - [[{2}]], jacket - [[{3}]], sleeves - [[{4}]], pants - [[{5}]]. citizens.commands.npc.skin.layers-set=[[{0}]]''s skin layers: cape - [[{1}]], hat - [[{2}]], jacket - [[{3}]], sleeves - [[{4}]], pants - [[{5}]].
citizens.commands.npc.size.description=[[{0}]]''s size is [[{1}]]. citizens.commands.npc.size.description=[[{0}]]''s size is [[{1}]].