diff --git a/Bukkit/build.gradle b/Bukkit/build.gradle index 906940308..3bc057a45 100644 --- a/Bukkit/build.gradle +++ b/Bukkit/build.gradle @@ -15,7 +15,7 @@ dependencies { compile("net.milkbowl.vault:VaultAPI:1.7") { exclude module: 'bukkit' } - compileOnly 'org.projectlombok:lombok:1.18.4' + compile 'org.spigotmc:spigot-api:1.13.2-R0.1-SNAPSHOT' } sourceCompatibility = 1.8 diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/BukkitMain.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/BukkitMain.java index 420cbf0ff..6f7f83946 100644 --- a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/BukkitMain.java +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/BukkitMain.java @@ -151,6 +151,10 @@ public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain return this.version; } + @Override public String getServerImplementation() { + return Bukkit.getVersion(); + } + @Override public void onEnable() { if (pluginMap != null) { pluginMap.put("PlotMe-DefaultGenerator", this); diff --git a/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/IPlotMain.java b/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/IPlotMain.java index aeac6d560..a22166498 100644 --- a/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/IPlotMain.java +++ b/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/IPlotMain.java @@ -85,6 +85,11 @@ public interface IPlotMain extends ILogger { */ int[] getServerVersion(); + /** + * Get the server implementation name and version + */ + String getServerImplementation(); + /** * Get the NMS package prefix. * diff --git a/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugPaste.java b/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugPaste.java index bb06abe8a..1738ce0e6 100644 --- a/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugPaste.java +++ b/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugPaste.java @@ -5,13 +5,21 @@ import com.github.intellectualsites.plotsquared.plot.PlotSquared; import com.github.intellectualsites.plotsquared.plot.config.C; import com.github.intellectualsites.plotsquared.plot.config.Settings; import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; -import com.github.intellectualsites.plotsquared.plot.util.HastebinUtility; +import com.github.intellectualsites.plotsquared.plot.util.IncendoPaster; import com.github.intellectualsites.plotsquared.plot.util.MainUtil; import com.github.intellectualsites.plotsquared.plot.util.TaskManager; import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.NonNull; +import java.io.BufferedReader; import java.io.File; +import java.io.FileReader; import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; @CommandDeclaration(command = "debugpaste", aliases = "dp", usage = "/plot debugpaste", description = "Upload settings.yml, worlds.yml, commands.yml and latest.log to www.hastebin.com", @@ -19,71 +27,100 @@ import java.io.IOException; extends SubCommand { @Override public boolean onCommand(final PlotPlayer player, String[] args) { - TaskManager.runTaskAsync(new Runnable() { - @Override public void run() { - try { - String settingsYML = HastebinUtility.upload(PlotSquared.get().configFile); - String worldsYML = HastebinUtility.upload(PlotSquared.get().worldsFile); - String commandsYML = HastebinUtility.upload(PlotSquared.get().commandsFile); - String latestLOG; - try { - latestLOG = HastebinUtility.upload( - new File(PlotSquared.get().IMP.getDirectory(), - "../../logs/latest.log")); - } catch (IOException ignored) { - MainUtil.sendMessage(player, - "&clatest.log is too big to be pasted, will ignore"); - latestLOG = "too big :("; - } - StringBuilder b = new StringBuilder(); - b.append( - "# Welcome to this paste\n# It is meant to provide us at IntellectualSites with better information about your " - + "problem\n\n# We will start with some informational files\n"); - b.append("links.settings_yml: ").append(settingsYML).append('\n'); - b.append("links.worlds_yml: ").append(worldsYML).append('\n'); - b.append("links.commands_yml: ").append(commandsYML).append('\n'); - b.append("links.latest_log: ").append(latestLOG).append('\n'); - b.append("\n# Server Information\n"); - int[] sVersion = PlotSquared.get().IMP.getServerVersion(); - b.append("version.server: ").append(sVersion[0]).append('.').append(sVersion[1]) - .append('.').append(sVersion[2]).append('\n'); - b.append("online_mode: ").append(UUIDHandler.getUUIDWrapper()).append(';') - .append(!Settings.UUID.OFFLINE).append('\n'); - b.append("plugins:"); - for (String id : PlotSquared.get().IMP.getPluginIds()) { - String[] split = id.split(":"); - String[] split2 = split[0].split(";"); - String enabled = split.length == 2 ? split[1] : "unknown"; - String name = split2[0]; - String version = split2.length == 2 ? split2[1] : "unknown"; - b.append("\n ").append(name).append(":\n ").append("version: '") - .append(version).append('\'').append("\n enabled: ").append(enabled); - } - b.append("\n\n# YAY! Now, let's see what we can find in your JVM\n"); - Runtime runtime = Runtime.getRuntime(); - b.append("memory.free: ").append(runtime.freeMemory()).append('\n'); - b.append("memory.max: ").append(runtime.maxMemory()).append('\n'); - b.append("java.specification.version: '") - .append(System.getProperty("java.specification.version")).append("'\n"); - b.append("java.vendor: '").append(System.getProperty("java.vendor")) - .append("'\n"); - b.append("java.version: '").append(System.getProperty("java.version")) - .append("'\n"); - b.append("os.arch: '").append(System.getProperty("os.arch")).append("'\n"); - b.append("os.name: '").append(System.getProperty("os.name")).append("'\n"); - b.append("os.version: '").append(System.getProperty("os.version")) - .append("'\n\n"); - b.append("# Okay :D Great. You are now ready to create your bug report!"); - b.append( - "\n# You can do so at https://github.com/IntellectualSites/PlotSquared/issues"); + TaskManager.runTaskAsync(() -> { + try { + final IncendoPaster incendoPaster = new IncendoPaster("plotsquared"); - String link = HastebinUtility.upload(b.toString()); - player.sendMessage(C.DEBUG_REPORT_CREATED.s().replace("%url%", link)); - } catch (IOException e) { - e.printStackTrace(); + StringBuilder b = new StringBuilder(); + b.append( + "# Welcome to this paste\n# It is meant to provide us at IntellectualSites with better information about your " + + "problem\n\n"); + b.append("# Server Information\n"); + b.append("server.version: ").append(PlotSquared.get().IMP.getServerImplementation()).append("\n"); + b.append("online_mode: ").append(UUIDHandler.getUUIDWrapper()).append(';') + .append(!Settings.UUID.OFFLINE).append('\n'); + b.append("plugins:"); + for (String id : PlotSquared.get().IMP.getPluginIds()) { + String[] split = id.split(":"); + String[] split2 = split[0].split(";"); + String enabled = split.length == 2 ? split[1] : "unknown"; + String name = split2[0]; + String version = split2.length == 2 ? split2[1] : "unknown"; + b.append("\n ").append(name).append(":\n ").append("version: '") + .append(version).append('\'').append("\n enabled: ").append(enabled); } + b.append("\n\n# YAY! Now, let's see what we can find in your JVM\n"); + Runtime runtime = Runtime.getRuntime(); + b.append("memory.free: ").append(runtime.freeMemory()).append('\n'); + b.append("memory.max: ").append(runtime.maxMemory()).append('\n'); + b.append("java.specification.version: '") + .append(System.getProperty("java.specification.version")).append("'\n"); + b.append("java.vendor: '").append(System.getProperty("java.vendor")) + .append("'\n"); + b.append("java.version: '").append(System.getProperty("java.version")) + .append("'\n"); + b.append("os.arch: '").append(System.getProperty("os.arch")).append("'\n"); + b.append("os.name: '").append(System.getProperty("os.name")).append("'\n"); + b.append("os.version: '").append(System.getProperty("os.version")) + .append("'\n\n"); + b.append("# Okay :D Great. You are now ready to create your bug report!"); + b.append( + "\n# You can do so at https://github.com/IntellectualSites/PlotSquared/issues"); + b.append("\n# or via our Discord at https://discord.gg/ngZCzbU"); + + incendoPaster.addFile(new IncendoPaster.PasteFile("information", b.toString())); + + try { + final File logFile = new File(PlotSquared.get().IMP.getDirectory(), + "../../logs/latest.log"); + if (Files.size(logFile.toPath()) > 14_000_000) { + throw new IOException("Too big..."); + } + incendoPaster.addFile(new IncendoPaster.PasteFile("latest.log", readFile(logFile))); + } catch (IOException ignored) { + MainUtil.sendMessage(player, + "&clatest.log is too big to be pasted, will ignore"); + } + + incendoPaster.addFile(new IncendoPaster.PasteFile("settings.yml", readFile(PlotSquared.get().configFile))); + incendoPaster.addFile(new IncendoPaster.PasteFile("worlds.yml", readFile(PlotSquared.get().worldsFile))); + incendoPaster.addFile(new IncendoPaster.PasteFile("PlotSquared.use_THIS.yml", readFile(PlotSquared.get().translationFile))); + try { + final String rawResponse = incendoPaster.upload(); + final JsonObject jsonObject = new JsonParser().parse(rawResponse).getAsJsonObject(); + + if (jsonObject.has("created")) { + final String pasteId = jsonObject.get("paste_id").getAsString(); + final String link = String.format("https://incendo.org/paste/view/%s", pasteId); + player.sendMessage( + C.DEBUG_REPORT_CREATED.s().replace("%url%", link)); + } else { + final String responseMessage = jsonObject.get("response").getAsString(); + MainUtil.sendMessage(player, String.format("&cFailed to create the debug paste: %s", responseMessage)); + } + } catch (final Throwable throwable) { + throwable.printStackTrace(); + MainUtil.sendMessage(player, "&cFailed to create the debug paste: " + throwable.getMessage()); + } + } catch (IOException e) { + e.printStackTrace(); } }); return true; } + + private static String readFile(@NonNull final File file) throws IOException { + final StringBuilder content = new StringBuilder(); + final List lines = new ArrayList<>(); + try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } + for (int i = Math.max(0, lines.size() - 1000); i < lines.size(); i++) { + content.append(lines.get(i)).append("\n"); + } + return content.toString(); + } } diff --git a/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/util/IncendoPaster.java b/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/util/IncendoPaster.java new file mode 100644 index 000000000..0a83301bb --- /dev/null +++ b/Core/src/main/java/com/github/intellectualsites/plotsquared/plot/util/IncendoPaster.java @@ -0,0 +1,186 @@ +package com.github.intellectualsites.plotsquared.plot.util; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.google.common.base.Charsets; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.*; + +/** + * Single class paster for the Incendo paste service + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public final class IncendoPaster { + + /** + * Upload service URL + */ + public static final String UPLOAD_PATH = "https://incendo.org/paste/upload"; + /** + * Valid paste applications + */ + public static final Collection + VALID_APPLICATIONS = Arrays + .asList("plotsquared", "fastasyncworldedit", "incendopermissions", "kvantum"); + + private final Collection files = new ArrayList<>(); + private final String pasteApplication; + + /** + * Construct a new paster + * + * @param pasteApplication The application that is sending the paste + */ + public IncendoPaster(final String pasteApplication) { + if (pasteApplication == null || pasteApplication.isEmpty()) { + throw new IllegalArgumentException("paste application cannot be null, nor empty"); + } + if (!VALID_APPLICATIONS.contains(pasteApplication.toLowerCase(Locale.ENGLISH))) { + throw new IllegalArgumentException(String.format("Unknown application name: %s", pasteApplication)); + } + this.pasteApplication = pasteApplication; + } + + /** + * Get an immutable collection containing all the files that have been added to this paster + * + * @return Unmodifiable collection + */ + public final Collection getFiles() { + return Collections.unmodifiableCollection(this.files); + } + + /** + * Add a file to the paster + * + * @param file File to paste + */ + public void addFile(final PasteFile file) { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + // Check to see that no duplicate files are submitted + for (final PasteFile pasteFile : this.files) { + if (pasteFile.fileName.equalsIgnoreCase(file.getFileName())) { + throw new IllegalArgumentException(String.format("Found duplicate file with name %s", + file.getFileName())); + } + } + this.files.add(file); + } + + /** + * Create a JSON string from the submitted information + * + * @return compiled JSON string + */ + private String toJsonString() { + final StringBuilder builder = new StringBuilder("{\n"); + builder.append("\"paste_application\": \"").append(this.pasteApplication).append("\",\n\"files\": \""); + Iterator fileIterator = this.files.iterator(); + while (fileIterator.hasNext()) { + final PasteFile file = fileIterator.next(); + builder.append(file.getFileName()); + if (fileIterator.hasNext()) { + builder.append(","); + } + } + builder.append("\",\n"); + fileIterator = this.files.iterator(); + while (fileIterator.hasNext()) { + final PasteFile file = fileIterator.next(); + builder.append("\"file-").append(file.getFileName()).append("\": \"") + .append(file.getContent().replaceAll("\"", "\\\\\"")).append("\""); + if (fileIterator.hasNext()) { + builder.append(",\n"); + } + } + builder.append("\n}"); + return builder.toString(); + } + + /** + * Upload the paste and return the status message + * + * @return Status message + * @throws Throwable any and all exceptions + */ + public final String upload() throws Throwable { + final URL url = new URL(UPLOAD_PATH); + final URLConnection connection = url.openConnection(); + final HttpURLConnection httpURLConnection = (HttpURLConnection) connection; + httpURLConnection.setRequestMethod("POST"); + httpURLConnection.setDoOutput(true); + final byte[] content = toJsonString().getBytes(Charsets.UTF_8); + httpURLConnection.setFixedLengthStreamingMode(content.length); + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + httpURLConnection.setRequestProperty("Accept", "*/*"); + httpURLConnection.connect(); + try (final OutputStream stream = httpURLConnection.getOutputStream()) { + stream.write(content); + } + if (!httpURLConnection.getResponseMessage().contains("OK")) { + if (httpURLConnection.getResponseCode() == 413) { + final long size = content.length; + PlotSquared.debug(String.format("Paste Too Big > Size: %dMB", size / 1_000_000)); + } + throw new IllegalStateException(String.format("Server returned status: %d %s", + httpURLConnection.getResponseCode(), httpURLConnection.getResponseMessage())); + } + final StringBuilder input = new StringBuilder(); + try (final BufferedReader inputStream = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()))) { + String line; + while ((line = inputStream.readLine()) != null) { + input.append(line).append("\n"); + } + } + return input.toString(); + } + + /** + * Simple class that represents a paste file + */ + public static class PasteFile { + + private final String fileName; + private final String content; + + /** + * Construct a new paste file + * + * @param fileName File name, cannot be empty, nor null + * @param content File content, cannot be empty, nor null + */ + public PasteFile(final String fileName, final String content) { + if (fileName == null || fileName.isEmpty()) { + throw new IllegalArgumentException("file name cannot be null, nor empty"); + } + if (content == null || content.isEmpty()) { + throw new IllegalArgumentException("content cannot be null, nor empty"); + } + this.fileName = fileName; + this.content = content; + } + + /** + * Get the file name + * + * @return File name + */ + public String getFileName() { + return this.fileName; + } + + /** + * Get the file content as a single string + * + * @return File content + */ + public String getContent() { + return this.content; + } + } + +}