upstream #1

Closed
bridge wants to merge 65 commits from feat/upstream into 2.X
4 changed files with 110 additions and 3 deletions
Showing only changes of commit d1227500ce - Show all commits

View file

@ -93,7 +93,7 @@ public class ZNpcsPlus {
packetEvents.load(); packetEvents.load();
configManager = new ConfigManager(getDataFolder()); configManager = new ConfigManager(getDataFolder());
skinCache = new MojangSkinCache(configManager); skinCache = new MojangSkinCache(configManager, new File(getDataFolder(), "skins"));
propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager); propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
NpcPropertyRegistryProvider.register(propertyRegistry); NpcPropertyRegistryProvider.register(propertyRegistry);

View file

@ -17,10 +17,14 @@ import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class SkinCommand implements CommandHandler { public class SkinCommand implements CommandHandler {
private final MojangSkinCache skinCache; private final MojangSkinCache skinCache;
@ -90,6 +94,30 @@ public class SkinCommand implements CommandHandler {
context.send(Component.text("Invalid url!", NamedTextColor.RED)); context.send(Component.text("Invalid url!", NamedTextColor.RED));
} }
return; return;
} else if (type.equalsIgnoreCase("file")) {
context.ensureArgsNotEmpty();
String path = context.dumpAllArgs();
context.send(Component.text("Fetching skin from file \"" + path + "\"...", NamedTextColor.GREEN));
PrefetchedDescriptor.fromFile(skinCache, path).exceptionally(e -> {
if (e instanceof FileNotFoundException || e.getCause() instanceof FileNotFoundException) {
context.send(Component.text("A file at the specified path could not be found!", NamedTextColor.RED));
} else {
context.send(Component.text("An error occurred while fetching the skin from file! Check the console for more details.", NamedTextColor.RED));
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
return null;
}).thenAccept(skin -> {
if (skin == null) return;
if (skin.getSkin() == null) {
context.send(Component.text("Failed to fetch skin, are you sure the file path is valid?", NamedTextColor.RED));
return;
}
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), skin);
npc.respawn();
context.send(Component.text("The NPC's skin has been set.", NamedTextColor.GREEN));
});
return;
} }
context.send(Component.text("Unknown skin type! Please use one of the following: mirror, static, dynamic, url")); context.send(Component.text("Unknown skin type! Please use one of the following: mirror, static, dynamic, url"));
} }
@ -97,11 +125,19 @@ public class SkinCommand implements CommandHandler {
@Override @Override
public List<String> suggest(CommandContext context) throws CommandExecutionException { public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds()); if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url"); if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url", "file");
if (context.matchSuggestion("*", "static")) return context.suggestPlayers(); if (context.matchSuggestion("*", "static")) return context.suggestPlayers();
if (context.argSize() == 3 && context.matchSuggestion("*", "url")) { if (context.argSize() == 3 && context.matchSuggestion("*", "url")) {
return context.suggestLiteral("slim", "classic"); return context.suggestLiteral("slim", "classic");
} }
if (context.argSize() == 3 && context.matchSuggestion("*", "file")) {
if (skinCache.getSkinsFolder().exists()) {
File[] files = skinCache.getSkinsFolder().listFiles();
if (files != null) {
return Arrays.stream(files).map(File::getName).collect(Collectors.toList());
}
}
}
return Collections.emptyList(); return Collections.emptyList();
} }
} }

View file

@ -15,6 +15,7 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -27,9 +28,12 @@ public class MojangSkinCache {
private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>(); private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>();
private final Map<String, CachedId> idCache = new ConcurrentHashMap<>(); private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
private final File skinsFolder;
public MojangSkinCache(ConfigManager configManager) { public MojangSkinCache(ConfigManager configManager, File skinsFolder) {
this.configManager = configManager; this.configManager = configManager;
this.skinsFolder = skinsFolder;
if (!skinsFolder.exists()) skinsFolder.mkdirs();
} }
public void cleanCache() { public void cleanCache() {
@ -143,6 +147,58 @@ public class MojangSkinCache {
}); });
} }
public CompletableFuture<SkinImpl> fetchFromFile(String path) throws FileNotFoundException {
File file = new File(skinsFolder, path);
if (!file.exists()) throw new FileNotFoundException("File not found: " + path);
return CompletableFuture.supplyAsync(() -> {
URL apiUrl = parseUrl("https://api.mineskin.org/generate/upload");
HttpURLConnection connection = null;
try {
String boundary = "*****";
String CRLF = "\r\n";
connection = (HttpURLConnection) apiUrl.openConnection();
connection.setRequestMethod("POST");
connection.setReadTimeout(10000);
connection.setConnectTimeout(15000);
connection.setUseCaches(false);
connection.setRequestProperty("Cache-Control", "no-cache");
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
connection.setDoInput(true);
connection.setDoOutput(true);
OutputStream outputStream = connection.getOutputStream();
DataOutputStream out = new DataOutputStream(outputStream);
out.writeBytes("--" + boundary + CRLF);
out.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + file.getName() + "\"" + CRLF);
out.writeBytes("Content-Type: image/png" + CRLF);
out.writeBytes(CRLF);
out.write(Files.readAllBytes(file.toPath()));
out.writeBytes(CRLF);
out.writeBytes("--" + boundary + "--" + CRLF);
out.flush();
out.close();
outputStream.close();
try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject();
if (obj.has("error")) return null;
if (!obj.has("data")) return null;
JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject();
return new SkinImpl(texture.get("value").getAsString(), texture.get("signature").getAsString());
}
} catch (IOException exception) {
if (!configManager.getConfig().disableSkinFetcherWarnings()) {
logger.warning("Failed to get skin from file:");
exception.printStackTrace();
}
} finally {
if (connection != null) connection.disconnect();
}
return null;
});
}
public boolean isNameFullyCached(String s) { public boolean isNameFullyCached(String s) {
String name = s.toLowerCase(); String name = s.toLowerCase();
if (!idCache.containsKey(name)) return false; if (!idCache.containsKey(name)) return false;
@ -214,4 +270,8 @@ public class MojangSkinCache {
throw new RuntimeException(exception); throw new RuntimeException(exception);
} }
} }
public File getSkinsFolder() {
return skinsFolder;
}
} }

View file

@ -8,6 +8,7 @@ import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.util.FutureUtil; import lol.pyr.znpcsplus.util.FutureUtil;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.io.FileNotFoundException;
import java.net.URL; import java.net.URL;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -26,6 +27,16 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
return FutureUtil.exceptionPrintingSupplyAsync(() -> new PrefetchedDescriptor(cache.fetchByUrl(url, variant).join())); return FutureUtil.exceptionPrintingSupplyAsync(() -> new PrefetchedDescriptor(cache.fetchByUrl(url, variant).join()));
} }
public static CompletableFuture<PrefetchedDescriptor> fromFile(MojangSkinCache cache, String path) {
return CompletableFuture.supplyAsync(() -> {
try {
return new PrefetchedDescriptor(cache.fetchFromFile(path).join());
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
});
}
@Override @Override
public CompletableFuture<SkinImpl> fetch(Player player) { public CompletableFuture<SkinImpl> fetch(Player player) {
return CompletableFuture.completedFuture(skin); return CompletableFuture.completedFuture(skin);