From d14a656efc8a63721c8fbbd101df25f10f27aa1b Mon Sep 17 00:00:00 2001 From: Vinnie Date: Mon, 27 Oct 2014 06:19:51 -0400 Subject: [PATCH] Ported to Rainbow API Ported to Rainbow API. Removed buy command. Removed buy GUI. --- .../45.2/PluginReference-45.2.jar | Bin 0 -> 86637 bytes plugin.yml | 12 - pom.xml | 17 +- src/RainbowBuycraft/MyPlugin.java | 301 ++++++++++ .../buycraft => RainbowBuycraft}/api/Api.java | 41 +- src/RainbowBuycraft/api/ApiTask.java | 50 ++ .../commands/BuycraftCommand.java | 143 +++++ .../tasks/AuthenticateTask.java | 56 ++ .../tasks/CommandDeleteTask.java | 23 +- .../tasks/CommandExecuteTask.java | 75 +-- .../tasks/CommandFetchTask.java | 34 +- .../tasks/PendingPlayerCheckerTask.java | 140 +++++ .../tasks/RecentPaymentsTask.java | 20 +- src/RainbowBuycraft/tasks/ReportTask.java | 228 ++++++++ .../util/Chat.java | 4 +- src/RainbowBuycraft/util/Language.java | 78 +++ .../util/PackageCommand.java | 20 +- src/RainbowBuycraft/util/Settings.java | 109 ++++ src/net/buycraft/ChatManager.java | 40 -- src/net/buycraft/PlayerListener.java | 38 -- src/net/buycraft/Plugin.java | 352 ------------ src/net/buycraft/api/ApiTask.java | 59 -- src/net/buycraft/commands/BuyCommand.java | 78 --- .../buycraft/commands/BuycraftCommand.java | 170 ------ .../buycraft/commands/EnableChatCommand.java | 41 -- src/net/buycraft/heads/Head.java | 70 --- src/net/buycraft/heads/HeadFile.java | 119 ---- src/net/buycraft/heads/HeadListener.java | 185 ------- src/net/buycraft/heads/HeadSign.java | 89 --- src/net/buycraft/heads/HeadThread.java | 106 ---- src/net/buycraft/packages/ItemParser.java | 162 ------ .../buycraft/packages/PackageCategory.java | 54 -- src/net/buycraft/packages/PackageManager.java | 87 --- src/net/buycraft/packages/PackageModal.java | 56 -- .../buycraft/tasks/ReloadPackagesTask.java | 65 --- src/net/buycraft/tasks/VisitLinkTask.java | 71 --- src/net/buycraft/ui/AbstractBuyUI.java | 44 -- src/net/buycraft/ui/BuyChatUI.java | 81 --- src/net/buycraft/ui/BuyInterface.java | 15 - src/net/buycraft/ui/BuyInventoryUI.java | 280 ---------- .../util/BuycraftInventoryCreator.java | 154 ------ src/net/buycraft/util/Updater.java | 514 ------------------ 42 files changed, 1225 insertions(+), 3056 deletions(-) create mode 100644 lib/org/rbow/PluginReference/45.2/PluginReference-45.2.jar delete mode 100755 plugin.yml create mode 100644 src/RainbowBuycraft/MyPlugin.java rename src/{net/buycraft => RainbowBuycraft}/api/Api.java (79%) mode change 100755 => 100644 create mode 100644 src/RainbowBuycraft/api/ApiTask.java create mode 100644 src/RainbowBuycraft/commands/BuycraftCommand.java create mode 100644 src/RainbowBuycraft/tasks/AuthenticateTask.java rename src/{net/buycraft => RainbowBuycraft}/tasks/CommandDeleteTask.java (77%) rename src/{net/buycraft => RainbowBuycraft}/tasks/CommandExecuteTask.java (70%) rename src/{net/buycraft => RainbowBuycraft}/tasks/CommandFetchTask.java (72%) create mode 100644 src/RainbowBuycraft/tasks/PendingPlayerCheckerTask.java rename src/{net/buycraft => RainbowBuycraft}/tasks/RecentPaymentsTask.java (84%) create mode 100644 src/RainbowBuycraft/tasks/ReportTask.java rename src/{net/buycraft => RainbowBuycraft}/util/Chat.java (90%) mode change 100755 => 100644 create mode 100644 src/RainbowBuycraft/util/Language.java rename src/{net/buycraft => RainbowBuycraft}/util/PackageCommand.java (84%) create mode 100644 src/RainbowBuycraft/util/Settings.java delete mode 100755 src/net/buycraft/ChatManager.java delete mode 100755 src/net/buycraft/PlayerListener.java delete mode 100644 src/net/buycraft/Plugin.java delete mode 100644 src/net/buycraft/api/ApiTask.java delete mode 100755 src/net/buycraft/commands/BuyCommand.java delete mode 100755 src/net/buycraft/commands/BuycraftCommand.java delete mode 100755 src/net/buycraft/commands/EnableChatCommand.java delete mode 100644 src/net/buycraft/heads/Head.java delete mode 100644 src/net/buycraft/heads/HeadFile.java delete mode 100644 src/net/buycraft/heads/HeadListener.java delete mode 100644 src/net/buycraft/heads/HeadSign.java delete mode 100644 src/net/buycraft/heads/HeadThread.java delete mode 100644 src/net/buycraft/packages/ItemParser.java delete mode 100644 src/net/buycraft/packages/PackageCategory.java delete mode 100755 src/net/buycraft/packages/PackageManager.java delete mode 100644 src/net/buycraft/packages/PackageModal.java delete mode 100644 src/net/buycraft/tasks/ReloadPackagesTask.java delete mode 100644 src/net/buycraft/tasks/VisitLinkTask.java delete mode 100644 src/net/buycraft/ui/AbstractBuyUI.java delete mode 100644 src/net/buycraft/ui/BuyChatUI.java delete mode 100644 src/net/buycraft/ui/BuyInterface.java delete mode 100644 src/net/buycraft/ui/BuyInventoryUI.java delete mode 100644 src/net/buycraft/util/BuycraftInventoryCreator.java delete mode 100644 src/net/buycraft/util/Updater.java diff --git a/lib/org/rbow/PluginReference/45.2/PluginReference-45.2.jar b/lib/org/rbow/PluginReference/45.2/PluginReference-45.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..ef450dd131d46aed665dddfb57df5991a9326680 GIT binary patch literal 86637 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 kLzvYhnjU=8%|^3 z%J@z4U6NP&mnio&-G=>3J$Lwy^gL4Plrzm&>BehLljk)s2l&f$FR+h9?jTn&Udul; ze2fICz?I~6`By2r-vw>^%jk|L=O~Ut=i;xej~E_a9a(tRT}7CKW!8*WXEsz+ z>Q&m+>D9VvvMr8lu`SzafNO-yTUmR6FHVi#z^f1oM4}%0N)N?&Q+x}1n#;oYm~d83}4pQc-avg?Fo*f zwl-+y+z>HcxN?$(S8Zt!F@<87tr~A@MlV>;an}1K_RUpQFW}EH)kj`M>2?hnFKcEk zu+H&Z3xxNtnLZh1RXta&T7Ui)fic2h?C%&<)5*KN7?ZweZ9ay|&1^M{U)kokytvlXU+wKUZV>OpW}@t9XQb?OW-{%VWiajZGGMyX-hDm# zlrTE-veRPmyY;P~p`0}El>dxb#2Eik^^67G}CeS0e>Wc?mWv|D6 zC5?xAb-qVKwTTC2CEP`(>G0)qkJ`>t23g7t0zbVS5C5lili(eKn#8-Og`B%wO)Bsk ztAVUR;>+Yp)jJI4UsVAFURfANd#WAA>cOpD#;!U)V}%Uc{2d zUV`fOUWT}xhYf$(3TkRMh?KTz(;OYt^DHmeyj`BL;pHz~wybZ`CRv_3A#`Zs=d+Sk*J_cYR=5UOK^b)oD0eJV!{+P?(6!(3nWeP+^bA&`}uv z#*i{p4`Mbb#3vp3#-!2zjcuW4jQ&$*hNe9?f5`@_t7|9Aak}5bViSUV)g`K)@jk(0 zq^<Ț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 ISUSS|KQn$+Z~y=R literal 0 HcmV?d00001 diff --git a/plugin.yml b/plugin.yml deleted file mode 100755 index cc06cb3..0000000 --- a/plugin.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Buycraft -main: net.buycraft.Plugin -version: 6.7 -author: Tebex Limited -website: http://buycraft.net -commands: - ec: - usage: / - buycraft: - usage: / > - buysign: - usage: / \ No newline at end of file diff --git a/pom.xml b/pom.xml index f763b9e..02ee41a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,8 +1,8 @@ 4.0.0 Buycraft-Plugin - Buycraft - 6.7 + RainbowBuycraft + 1.0 src ${project.artifactId}_${project.version} @@ -10,24 +10,23 @@ - plugin.yml README.md - + - bukkit-repo - http://repo.bukkit.org/content/groups/public/ + libraries + file://${project.basedir}/lib - org.bukkit - bukkit - LATEST + org.rbow + PluginReference + 45.2 diff --git a/src/RainbowBuycraft/MyPlugin.java b/src/RainbowBuycraft/MyPlugin.java new file mode 100644 index 0000000..5354dee --- /dev/null +++ b/src/RainbowBuycraft/MyPlugin.java @@ -0,0 +1,301 @@ +package RainbowBuycraft; + +import PluginReference.*; +import RainbowBuycraft.api.Api; +import RainbowBuycraft.api.ApiTask; +import RainbowBuycraft.commands.BuycraftCommand; +import RainbowBuycraft.tasks.AuthenticateTask; +import RainbowBuycraft.tasks.CommandDeleteTask; +import RainbowBuycraft.tasks.CommandExecuteTask; +import RainbowBuycraft.tasks.PendingPlayerCheckerTask; +import RainbowBuycraft.tasks.ReportTask; +import RainbowBuycraft.util.Chat; +import RainbowBuycraft.util.Language; +import RainbowBuycraft.util.Settings; + +import java.io.File; +import java.io.FileWriter; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +public class MyPlugin extends PluginBase { + private static Logger logger = Logger.getLogger("Buycraft"); + + private static MC_Server server; + + private static MyPlugin instance; + + private String version; + + private Settings settings; + private Language language; + + private Api api; + + private int serverID = 0; + private String serverCurrency = ""; + private String serverStore = ""; + + private CommandExecuteTask commandExecutor; + private CommandDeleteTask commandDeleteTask; + private PendingPlayerCheckerTask pendingPlayerCheckerTask; + + public Timer pendingPlayerCheckerTaskExecutor; + + private boolean authenticated = false; + private int authenticatedCode = 1; + + private String folderPath; + + private ExecutorService executors = null; + + private boolean enabled = false; + + public MyPlugin() { + instance = this; + } + + public void addTask(ApiTask task) { + executors.submit(task); + } + + public void onStartup(MC_Server argServer) { + logger.info("============ RainbowBuycraft ============"); + logger.info("Buycraft is strating up..."); + server = argServer; + + // thread pool model + executors = Executors.newFixedThreadPool(5); + folderPath = "plugins_mod" + File.separator + "RainbowBuycraft" + File.separator; + + checkDirectory(); + + moveFileFromJar("README.md", getFolderPath() + "/README.txt", true); + + version = "1.0"; + + settings = new Settings(); + language = new Language(); + + api = new Api(); + + commandExecutor = new CommandExecuteTask(); + commandDeleteTask = new CommandDeleteTask(); + pendingPlayerCheckerTask = new PendingPlayerCheckerTask(); + + server.registerCommand(new BuycraftCommand()); + + enabled = true; + } + + public void onServerFullyLoaded() { + logger.info("Authenticating Buycraft..."); + AuthenticateTask.call(); + } + + public PluginInfo getPluginInfo() + { + PluginInfo info = new PluginInfo(); + info.description = "Buycraft Port By CoolV1994"; + info.eventSortOrder = 1000.0f; // call me much later on than default. + return info; + } + + public void onPlayerJoin(MC_Player plr) { + if (plr.getName().equalsIgnoreCase("Buycraft")) { + plr.kick("This user has been disabled due to security reasons."); + return; + } + pendingPlayerCheckerTask.onPlayerJoin(plr); + } + + public void onShutdown() { + logger.info("Buycraft is shutting down..."); + enabled = false; + + // Make sure any commands which have been run are deleted + commandDeleteTask.runNow(); + + executors.shutdown(); + while (!executors.isTerminated()) { + } + getLogger().info("Plugin has been disabled."); + } + + private void checkDirectory() { + File directory = new File(getFolderPath()); + + if (!directory.exists()) { + directory.mkdir(); + } + } + + public void moveFileFromJar(String jarFileName, String targetLocation, Boolean overwrite) { + try { + File targetFile = new File(targetLocation); + + if (overwrite || targetFile.exists() == false || targetFile.length() == 0) { + InputStream inFile = MyPlugin.class.getClassLoader().getResourceAsStream(jarFileName); + FileWriter outFile = new FileWriter(targetFile); + + int c; + + while ((c = inFile.read()) != -1) { + outFile.write(c); + } + + inFile.close(); + outFile.close(); + } + } catch (NullPointerException e) { + getLogger().info("Failed to create " + jarFileName + "."); + ReportTask.setLastException(e); + } catch (Exception e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } + } + + public Boolean isAuthenticated(MC_Player player) { + if (!authenticated) { + if (player != null) { + player.sendMessage(Chat.header()); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.RED + "Buycraft has failed to startup."); + player.sendMessage(Chat.seperator()); + if(authenticatedCode == 101) { + player.sendMessage(Chat.seperator() + ChatColor.RED + "This is because of an invalid secret key,"); + player.sendMessage(Chat.seperator() + ChatColor.RED + "please enter the Secret key into the settings.conf"); + player.sendMessage(Chat.seperator() + ChatColor.RED + "file, and reload your server."); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.RED + "You can find your secret key from the control panel."); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.RED + "If it did not resolve the issue, restart your server"); + player.sendMessage(Chat.seperator() + ChatColor.RED + "a couple of times."); + player.sendMessage(Chat.seperator()); + } else { + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.RED + "Please execute the '/buycraft report' command and"); + player.sendMessage(Chat.seperator() + ChatColor.RED + "then send the generated report.txt file to"); + player.sendMessage(Chat.seperator() + ChatColor.RED + "support@buycraft.net. We will be happy to help."); + player.sendMessage(Chat.seperator()); + } + player.sendMessage(Chat.footer()); + } + + return false; + } else { + return true; + } + } + + public void setAuthenticated(Boolean value) { + authenticated = value; + } + + public void setAuthenticatedCode(Integer value) { + authenticatedCode = value; + } + + public Integer getAuthenticatedCode() { + return authenticatedCode; + } + + public static MyPlugin getInstance() { + return instance; + } + + public Api getApi() { + return api; + } + + public void setServerID(Integer value) { + serverID = value; + } + + public void setServerCurrency(String value) { + serverCurrency = value; + } + + public void setServerStore(String value) { + serverStore = value; + } + + public void setPendingPlayerCheckerInterval(int interval) { + if (pendingPlayerCheckerTaskExecutor != null) { + pendingPlayerCheckerTaskExecutor.cancel(); + pendingPlayerCheckerTaskExecutor = null; + } + + // Convert seconds to ticks + interval *= 20; + + if (getSettings().getBoolean("commandChecker")) { + pendingPlayerCheckerTaskExecutor = new Timer(); + pendingPlayerCheckerTaskExecutor.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + pendingPlayerCheckerTask.call(false); + } + }, 20, interval); + } + + } + + public Integer getServerID() { + return serverID; + } + + public String getServerStore() { + return serverStore; + } + + public CommandExecuteTask getCommandExecutor() { + return commandExecutor; + } + + public CommandDeleteTask getCommandDeleteTask() { + return commandDeleteTask; + } + + public PendingPlayerCheckerTask getPendingPlayerCheckerTask() { + return pendingPlayerCheckerTask; + } + + public String getServerCurrency() { + return serverCurrency; + } + + public String getVersion() { + return version; + } + + public Settings getSettings() { + return settings; + } + + public Language getLanguage() { + return language; + } + + public String getFolderPath() { + return folderPath; + } + + public Logger getLogger() { + return logger; + } + + public MC_Server getServer() { + return server; + } + + public boolean isEnabled() { + return enabled; + } +} diff --git a/src/net/buycraft/api/Api.java b/src/RainbowBuycraft/api/Api.java old mode 100755 new mode 100644 similarity index 79% rename from src/net/buycraft/api/Api.java rename to src/RainbowBuycraft/api/Api.java index a2cc2af..76f4656 --- a/src/net/buycraft/api/Api.java +++ b/src/RainbowBuycraft/api/Api.java @@ -1,9 +1,8 @@ -package net.buycraft.api; +package RainbowBuycraft.api; -import net.buycraft.Plugin; -import net.buycraft.tasks.ReportTask; +import RainbowBuycraft.MyPlugin; +import RainbowBuycraft.tasks.ReportTask; -import org.bukkit.Bukkit; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -17,13 +16,13 @@ import java.util.Map; public class Api { - private Plugin plugin; + private MyPlugin plugin; private String apiUrl; private String apiKey; public Api() { - this.plugin = Plugin.getInstance(); + this.plugin = MyPlugin.getInstance(); this.apiKey = plugin.getSettings().getString("secret"); if (plugin.getSettings().getBoolean("https")) { @@ -38,9 +37,9 @@ public JSONObject authenticateAction() { apiCallParams.put("action", "info"); - apiCallParams.put("serverPort", String.valueOf(Bukkit.getPort())); - apiCallParams.put("onlineMode", String.valueOf(Bukkit.getOnlineMode())); - apiCallParams.put("playersMax", String.valueOf(Bukkit.getMaxPlayers())); + apiCallParams.put("serverPort", String.valueOf(plugin.getServer().getServerPort())); + apiCallParams.put("onlineMode", String.valueOf(plugin.getServer().getOnlineMode())); + apiCallParams.put("playersMax", String.valueOf(9000)); // No current API support apiCallParams.put("version", plugin.getVersion()); return call(apiCallParams); @@ -122,7 +121,7 @@ private JSONObject call(HashMap apiCallParams) { } apiCallParams.put("secret", apiKey); - apiCallParams.put("playersOnline", String.valueOf(Bukkit.getOnlinePlayers().length)); + apiCallParams.put("playersOnline", String.valueOf(plugin.getServer().getPlayers().size())); String url = apiUrl + generateUrlQueryString(apiCallParams); @@ -147,9 +146,9 @@ private JSONObject call(HashMap apiCallParams) { public static String HttpRequest(String url) { try { - if(Plugin.getInstance().getSettings().getBoolean("debug")) { - Plugin.getInstance().getLogger().info("---------------------------------------------------"); - Plugin.getInstance().getLogger().info("Request URL: " + url); + if(MyPlugin.getInstance().getSettings().getBoolean("debug")) { + MyPlugin.getInstance().getLogger().info("---------------------------------------------------"); + MyPlugin.getInstance().getLogger().info("Request URL: " + url); } String content = ""; @@ -176,26 +175,26 @@ public static String HttpRequest(String url) { in.close(); - if(Plugin.getInstance().getSettings().getBoolean("debug")) { - Plugin.getInstance().getLogger().info("JSON Response: " + content); - Plugin.getInstance().getLogger().info("---------------------------------------------------"); + if(MyPlugin.getInstance().getSettings().getBoolean("debug")) { + MyPlugin.getInstance().getLogger().info("JSON Response: " + content); + MyPlugin.getInstance().getLogger().info("---------------------------------------------------"); } return content; } catch (ConnectException e) { - Plugin.getInstance().getLogger().severe("HTTP request failed due to connection error."); + MyPlugin.getInstance().getLogger().severe("HTTP request failed due to connection error."); ReportTask.setLastException(e); } catch (SocketTimeoutException e) { - Plugin.getInstance().getLogger().severe("HTTP request failed due to timeout error."); + MyPlugin.getInstance().getLogger().severe("HTTP request failed due to timeout error."); ReportTask.setLastException(e); } catch (FileNotFoundException e) { - Plugin.getInstance().getLogger().severe("HTTP request failed due to file not found."); + MyPlugin.getInstance().getLogger().severe("HTTP request failed due to file not found."); ReportTask.setLastException(e); } catch (UnknownHostException e) { - Plugin.getInstance().getLogger().severe("HTTP request failed due to unknown host."); + MyPlugin.getInstance().getLogger().severe("HTTP request failed due to unknown host."); ReportTask.setLastException(e); } catch (IOException e) { - Plugin.getInstance().getLogger().severe(e.getMessage()); + MyPlugin.getInstance().getLogger().severe(e.getMessage()); ReportTask.setLastException(e); } catch (Exception e) { e.printStackTrace(); diff --git a/src/RainbowBuycraft/api/ApiTask.java b/src/RainbowBuycraft/api/ApiTask.java new file mode 100644 index 0000000..cfb0d96 --- /dev/null +++ b/src/RainbowBuycraft/api/ApiTask.java @@ -0,0 +1,50 @@ +package RainbowBuycraft.api; + +import RainbowBuycraft.MyPlugin; +import RainbowBuycraft.util.Language; + +import java.util.TimerTask; +import java.util.logging.Logger; + +public abstract class ApiTask implements Runnable { + + public TimerTask sync(TimerTask task) { + getPlugin().pendingPlayerCheckerTaskExecutor.schedule(task, 0); + return task; + } + + public TimerTask syncTimer(final Runnable r, long delay, long period) { + if (getPlugin().isEnabled()) { + TimerTask task = new TimerTask() { + @Override + public void run() { + r.run(); + } + }; + getPlugin().pendingPlayerCheckerTaskExecutor.schedule(task, delay, period); + return task; + } + return null; + } + + public void addTask(ApiTask task) { + MyPlugin.getInstance().addTask(task); + } + + public MyPlugin getPlugin() { + return MyPlugin.getInstance(); + } + + public Language getLanguage() { + return MyPlugin.getInstance().getLanguage(); + } + + public Api getApi() { + return MyPlugin.getInstance().getApi(); + } + + public Logger getLogger() { + return MyPlugin.getInstance().getLogger(); + } + +} diff --git a/src/RainbowBuycraft/commands/BuycraftCommand.java b/src/RainbowBuycraft/commands/BuycraftCommand.java new file mode 100644 index 0000000..0a803df --- /dev/null +++ b/src/RainbowBuycraft/commands/BuycraftCommand.java @@ -0,0 +1,143 @@ +package RainbowBuycraft.commands; + +import PluginReference.ChatColor; +import PluginReference.MC_Command; +import PluginReference.MC_Player; +import RainbowBuycraft.MyPlugin; +import RainbowBuycraft.tasks.AuthenticateTask; +import RainbowBuycraft.tasks.RecentPaymentsTask; +import RainbowBuycraft.tasks.ReportTask; +import RainbowBuycraft.util.Chat; + +import java.util.List; + +public class BuycraftCommand implements MC_Command { + MyPlugin plugin = MyPlugin.getInstance(); + + @Override + public String getCommandName() { + return "buycraft"; + } + + @Override + public List getAliases() { + return null; + } + + @Override + public String getHelpLine(MC_Player player) { + MyPlugin plugin = MyPlugin.getInstance(); + if (plugin.isAuthenticated(player)) { + player.sendMessage(Chat.header()); + player.sendMessage(Chat.seperator()); + + if (player.hasPermission("buycraft.admin")) { + player.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft forcecheck:" + ChatColor.GREEN + " Check for pending commands"); + player.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft secret :" + ChatColor.GREEN + " Set the Secret key"); + player.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft payments :" + ChatColor.GREEN + " Get recent payments of a user"); + player.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft report:" + ChatColor.GREEN + " Generate an error report"); + } + + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "Server ID: " + ChatColor.GREEN + String.valueOf(plugin.getServerID())); + player.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "Server URL: " + ChatColor.GREEN + String.valueOf(plugin.getServerStore())); + player.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "Version: " + ChatColor.GREEN + String.valueOf(plugin.getVersion()) + " CoolV1994's Port."); + player.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "Website: " + ChatColor.GREEN + "http://www.project-rainbow.org/site/plugin-releases/rainbowbuycraft/"); + player.sendMessage(Chat.footer()); + } + return null; + } + + @Override + public void handleCommand(MC_Player player, String[] args) { + if (args == null || args.length == 0) { + getHelpLine(player); + return; + } + + if(args[0].equalsIgnoreCase("payments")) { + String playerLookup = ""; + + if(args.length == 2) { + playerLookup = args[1]; + } + + RecentPaymentsTask.call(player, playerLookup); + + return; + } + + if (args[0].equalsIgnoreCase("report")) { + // Call the report task, if it fails we don't send the following messages to the player + if (ReportTask.call(player)) { + player.sendMessage(Chat.header()); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.GREEN + "Beginning generation of report"); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.footer()); + } + return; + } + + if (args[0].equalsIgnoreCase("secret")) { + if(plugin.getSettings().getBoolean("disable-secret-command") == false) { + if (args.length == 2) { + String secretKey = args[1]; + + player.sendMessage(Chat.header()); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.GREEN + "Server authenticated. Type /buycraft for confirmation."); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.footer()); + + plugin.getSettings().setString("secret", secretKey); + plugin.getApi().setApiKey(secretKey); + + AuthenticateTask.call(); + + return; + } else { + player.sendMessage(Chat.header()); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.RED + "Please enter a valid secret key."); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.footer()); + + return; + } + } else { + player.sendMessage(Chat.header()); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.RED + "Please change the key in settings.conf."); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.footer()); + + return; + } + } + + if (plugin.isAuthenticated(player)) { + if (args[0].equalsIgnoreCase("forcecheck")) { + plugin.getPendingPlayerCheckerTask().call(true); + + player.sendMessage(Chat.header()); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.seperator() + ChatColor.GREEN + "Force check successfully executed."); + player.sendMessage(Chat.seperator()); + player.sendMessage(Chat.footer()); + + return; + } + } + } + + @Override + public boolean hasPermissionToUse(MC_Player player) { + return player.hasPermission("buycraft.admin") || player.isOp(); + } + + @Override + public List getTabCompletionList(MC_Player mc_player, String[] strings) { + return null; + } +} diff --git a/src/RainbowBuycraft/tasks/AuthenticateTask.java b/src/RainbowBuycraft/tasks/AuthenticateTask.java new file mode 100644 index 0000000..473786e --- /dev/null +++ b/src/RainbowBuycraft/tasks/AuthenticateTask.java @@ -0,0 +1,56 @@ +package RainbowBuycraft.tasks; + +import RainbowBuycraft.MyPlugin; +import RainbowBuycraft.api.ApiTask; + +import org.json.JSONObject; + +import java.util.TimerTask; + +public class AuthenticateTask extends ApiTask { + private MyPlugin plugin; + + public static void call() { + MyPlugin.getInstance().addTask(new AuthenticateTask()); + } + + private AuthenticateTask() { + this.plugin = MyPlugin.getInstance(); + } + + public void run() { + try { + final JSONObject apiResponse = plugin.getApi().authenticateAction(); + plugin.setAuthenticated(false); + // call sync + if (apiResponse != null) { + try { + plugin.setAuthenticatedCode(apiResponse.getInt("code")); + + if (apiResponse.getInt("code") == 0) { + JSONObject payload = apiResponse.getJSONObject("payload"); + + plugin.setServerID(payload.getInt("serverId")); + plugin.setServerCurrency(payload.getString("serverCurrency")); + plugin.setServerStore(payload.getString("serverStore")); + plugin.setPendingPlayerCheckerInterval(payload.getInt("updateUsernameInterval")); + plugin.setAuthenticated(true); + + plugin.getLogger().info("Authenticated with the specified Secret key."); + plugin.getLogger().info("Plugin is now ready to be used."); + + } else if (apiResponse.getInt("code") == 101) { + plugin.getLogger().severe("The specified Secret key could not be found."); + plugin.getLogger().severe("Type /buycraft for further advice & help."); + } + } catch (Exception e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } + } + } catch (Exception e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } + } +} diff --git a/src/net/buycraft/tasks/CommandDeleteTask.java b/src/RainbowBuycraft/tasks/CommandDeleteTask.java similarity index 77% rename from src/net/buycraft/tasks/CommandDeleteTask.java rename to src/RainbowBuycraft/tasks/CommandDeleteTask.java index 937f2cb..0386928 100644 --- a/src/net/buycraft/tasks/CommandDeleteTask.java +++ b/src/RainbowBuycraft/tasks/CommandDeleteTask.java @@ -1,21 +1,20 @@ -package net.buycraft.tasks; +package RainbowBuycraft.tasks; import java.util.HashSet; +import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; -import org.bukkit.Bukkit; -import org.bukkit.scheduler.BukkitTask; +import RainbowBuycraft.MyPlugin; import org.json.JSONArray; -import net.buycraft.Plugin; -import net.buycraft.api.ApiTask; +import RainbowBuycraft.api.ApiTask; public class CommandDeleteTask extends ApiTask { private final AtomicBoolean scheduled = new AtomicBoolean(false); private final HashSet commandsToDelete = new HashSet(); - private BukkitTask currentTask; + private TimerTask currentTask; public synchronized void deleteCommand(int cid) { commandsToDelete.add(cid); @@ -37,7 +36,7 @@ public synchronized void runNow() { } if (!commandsToDelete.isEmpty()) - Plugin.getInstance().addTask(this); + MyPlugin.getInstance().addTask(this); } public void run() { @@ -56,7 +55,7 @@ public void run() { } catch (Exception e) { - Plugin.getInstance().getLogger().log(Level.SEVERE, "Error occured when deleting commands from the API", e); + MyPlugin.getInstance().getLogger().log(Level.SEVERE, "Error occured when deleting commands from the API", e); ReportTask.setLastException(e); } } @@ -64,12 +63,14 @@ public void run() { private void schedule() { // Delay the task for 10 seconds to allow for more deletions to occur at once if (scheduled.compareAndSet(false, true)) { - currentTask = Bukkit.getScheduler().runTaskLater(Plugin.getInstance(), new Runnable() { + currentTask = new TimerTask() { + @Override public void run() { currentTask = null; - Plugin.getInstance().addTask(CommandDeleteTask.this); + MyPlugin.getInstance().addTask(CommandDeleteTask.this); } - }, 600L); + }; + getPlugin().pendingPlayerCheckerTaskExecutor.schedule(currentTask, 600L); } } private synchronized void removeCommands(Integer[] commandIds) { diff --git a/src/net/buycraft/tasks/CommandExecuteTask.java b/src/RainbowBuycraft/tasks/CommandExecuteTask.java similarity index 70% rename from src/net/buycraft/tasks/CommandExecuteTask.java rename to src/RainbowBuycraft/tasks/CommandExecuteTask.java index 76b5c23..1e88f05 100644 --- a/src/net/buycraft/tasks/CommandExecuteTask.java +++ b/src/RainbowBuycraft/tasks/CommandExecuteTask.java @@ -1,22 +1,21 @@ -package net.buycraft.tasks; +package RainbowBuycraft.tasks; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map.Entry; +import java.util.TimerTask; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import java.util.regex.Pattern; -import net.buycraft.Plugin; -import net.buycraft.api.ApiTask; -import net.buycraft.util.Chat; -import net.buycraft.util.PackageCommand; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; +import PluginReference.ChatColor; +import PluginReference.MC_Player; +import RainbowBuycraft.MyPlugin; +import RainbowBuycraft.api.ApiTask; +import RainbowBuycraft.util.Chat; +import RainbowBuycraft.util.PackageCommand; public class CommandExecuteTask extends ApiTask { private static final Pattern REPLACE_NAME = Pattern.compile("[{\\(<\\[](name|player|username)[}\\)>\\]]", Pattern.CASE_INSENSITIVE); @@ -28,7 +27,7 @@ public class CommandExecuteTask extends ApiTask { */ private final PriorityBlockingQueue commandQueue; private final AtomicBoolean isScheduled; - private BukkitTask task; + private TimerTask task; private final HashMap requiredInventorySlots = new HashMap(); private final HashSet creditedCommands = new HashSet(); @@ -53,16 +52,16 @@ public void queueCommand(int commandId, String command, String username, int del // Convert delay from seconds to ticks delay *= 20; try { - username = Bukkit.getServer().getOfflinePlayer(username).getName(); + username = getPlayer(getPlugin().getServer().getOfflinePlayers(), username).getName(); command = REPLACE_NAME.matcher(command).replaceAll(username); if (command.startsWith("{mcmyadmin}")) { - Plugin.getInstance().getLogger().info("Executing command '" + command + "' on behalf of user '" + username + "'."); + MyPlugin.getInstance().getLogger().info("Executing command '" + command + "' on behalf of user '" + username + "'."); String newCommand = command.replace("{mcmyadmin}", ""); Logger.getLogger("McMyAdmin").info("Buycraft tried command: " + newCommand); } else { PackageCommand pkgCmd = new PackageCommand(commandId, username, command, delay, requiredInventorySlots); - if (!Plugin.getInstance().getCommandDeleteTask().queuedForDeletion(commandId) && !commandQueue.contains(pkgCmd)) { + if (!MyPlugin.getInstance().getCommandDeleteTask().queuedForDeletion(commandId) && !commandQueue.contains(pkgCmd)) { commandQueue.add(pkgCmd); } } @@ -101,7 +100,7 @@ public void run() { // Ignore the command if the player does not have enough free item slots if (pkgcmd.requiresFreeInventorySlots()) { - Player player = Bukkit.getPlayer(pkgcmd.username); + MC_Player player = getPlayer(getPlugin().getServer().getPlayers(), pkgcmd.username); int result = pkgcmd.calculateRequiredInventorySlots(player); if (result > 0) { // Fetch any current amounts @@ -121,11 +120,11 @@ public void run() { } - Plugin.getInstance().getLogger().info("Executing command '" + pkgcmd.command + "' on behalf of user '" + pkgcmd.username + "'."); + MyPlugin.getInstance().getLogger().info("Executing command '" + pkgcmd.command + "' on behalf of user '" + pkgcmd.username + "'."); creditedCommands.add(pkgcmd.username); long cmdStart = System.currentTimeMillis(); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), pkgcmd.command); + getPlugin().getServer().executeCommand(pkgcmd.command); // Check if the command lasted longer than our threshold long cmdDiff = System.currentTimeMillis() - cmdStart; if (cmdDiff >= 10) { @@ -134,7 +133,7 @@ public void run() { } // Queue the command for deletion - Plugin.getInstance().getCommandDeleteTask().deleteCommand(pkgcmd.getId()); + MyPlugin.getInstance().getCommandDeleteTask().deleteCommand(pkgcmd.getId()); } catch (Throwable e) { e.printStackTrace(); } @@ -143,39 +142,39 @@ public void run() { if (commandQueue.isEmpty()) { // Tell users that they need more inventory space for (Entry e : requiredInventorySlots.entrySet()) { - Player p = Bukkit.getPlayerExact(e.getKey()); + MC_Player p = getPlayer(getPlugin().getServer().getPlayers(), e.getKey()); if (p == null) { continue; } - p.sendMessage(new String[] { - Chat.header(), - Chat.seperator(), - Chat.seperator() + ChatColor.RED + String.format(Plugin.getInstance().getLanguage().getString("commandExecuteNotEnoughFreeInventory"), e.getValue()), - Chat.seperator() + ChatColor.RED + Plugin.getInstance().getLanguage().getString("commandExecuteNotEnoughFreeInventory2"), - Chat.seperator(), + p.sendMessage( + Chat.header() + "\n" + + Chat.seperator() + "\n" + + Chat.seperator() + ChatColor.RED + String.format(MyPlugin.getInstance().getLanguage().getString("commandExecuteNotEnoughFreeInventory"), e.getValue()) + "\n" + + Chat.seperator() + ChatColor.RED + MyPlugin.getInstance().getLanguage().getString("commandExecuteNotEnoughFreeInventory2") + "\n" + + Chat.seperator() + "\n" + Chat.footer() - }); + ); } // Clear the map requiredInventorySlots.clear(); for (String name : creditedCommands) { - Player p = Bukkit.getPlayerExact(name); + MC_Player p = getPlayer(getPlugin().getServer().getPlayers(), name); if (p == null) { continue; } - p.sendMessage(new String[] { - Chat.header(), - Chat.seperator(), - Chat.seperator() + ChatColor.GREEN + Plugin.getInstance().getLanguage().getString("commandsExecuted"), - Chat.seperator(), + p.sendMessage( + Chat.header() + "\n" + + Chat.seperator() + "\n" + + Chat.seperator() + ChatColor.GREEN + MyPlugin.getInstance().getLanguage().getString("commandsExecuted") + "\n" + + Chat.seperator() + "\n" + Chat.footer() - }); + ); } // Clear the set creditedCommands.clear(); - BukkitTask task = this.task; + TimerTask task = this.task; // Null the task now so we can't overwrite a new one this.task = null; // Allow a new task to be scheduled @@ -184,4 +183,12 @@ public void run() { task.cancel(); } } + + private MC_Player getPlayer(List players, String name) { + for (MC_Player player : players) { + if (player.getName().equalsIgnoreCase(name)) + return player; + } + return null; + } } diff --git a/src/net/buycraft/tasks/CommandFetchTask.java b/src/RainbowBuycraft/tasks/CommandFetchTask.java similarity index 72% rename from src/net/buycraft/tasks/CommandFetchTask.java rename to src/RainbowBuycraft/tasks/CommandFetchTask.java index 3dd92c3..f3b56d6 100644 --- a/src/net/buycraft/tasks/CommandFetchTask.java +++ b/src/RainbowBuycraft/tasks/CommandFetchTask.java @@ -1,11 +1,11 @@ -package net.buycraft.tasks; +package RainbowBuycraft.tasks; import java.util.ArrayList; -import net.buycraft.Plugin; -import net.buycraft.api.ApiTask; +import PluginReference.MC_Player; +import RainbowBuycraft.MyPlugin; +import RainbowBuycraft.api.ApiTask; -import org.bukkit.entity.Player; import org.json.JSONArray; import org.json.JSONObject; @@ -18,16 +18,16 @@ public static long getLastExecution() { return lastExecution; } - public static void call(boolean offlineCommands, Player ...players) { - Plugin.getInstance().addTask(new CommandFetchTask(offlineCommands, players)); + public static void call(boolean offlineCommands, MC_Player... players) { + MyPlugin.getInstance().addTask(new CommandFetchTask(offlineCommands, players)); } - private final Plugin plugin; + private final MyPlugin plugin; private final boolean offlineCommands; - private final Player[] players; + private final MC_Player[] players; - private CommandFetchTask(boolean offlineCommands, Player[] players) { - this.plugin = Plugin.getInstance(); + private CommandFetchTask(boolean offlineCommands, MC_Player[] players) { + this.plugin = MyPlugin.getInstance(); this.offlineCommands = offlineCommands; this.players = players; } @@ -43,7 +43,7 @@ public void run() { String[] playerNames; if (players.length > 0){ ArrayList tmpPlayerNames = new ArrayList(players.length); - for (Player player : players) { + for (MC_Player player : players) { tmpPlayerNames.add(player.getName()); } playerNames = tmpPlayerNames.toArray(new String[tmpPlayerNames.size()]); @@ -71,22 +71,22 @@ public void run() { int delay = row.getInt("delay"); int requiredInventorySlots = row.getInt("requireInventorySlot"); - Player player = requireOnline ? getPlayer(players, username) : null; + MC_Player player = requireOnline ? getPlayer(players, username) : null; if (requireOnline == false || player != null) { String c = command; String u = username; - Plugin.getInstance().getCommandExecutor().queueCommand(commandId, c, u, delay, requiredInventorySlots); + MyPlugin.getInstance().getCommandExecutor().queueCommand(commandId, c, u, delay, requiredInventorySlots); } } // If the plugin is disabled here our commands won't get executed so we return - if (!Plugin.getInstance().isEnabled()) { + if (!MyPlugin.getInstance().isEnabled()) { return; } - Plugin.getInstance().getCommandExecutor().scheduleExecutor(); + MyPlugin.getInstance().getCommandExecutor().scheduleExecutor(); plugin.getLogger().info("Package checker successfully executed."); } catch (Exception e) { @@ -95,8 +95,8 @@ public void run() { } } - private Player getPlayer(Player[] players, String name) { - for (Player player : players) { + private MC_Player getPlayer(MC_Player[] players, String name) { + for (MC_Player player : players) { if (player.getName().equalsIgnoreCase(name)) return player; } diff --git a/src/RainbowBuycraft/tasks/PendingPlayerCheckerTask.java b/src/RainbowBuycraft/tasks/PendingPlayerCheckerTask.java new file mode 100644 index 0000000..4ea617e --- /dev/null +++ b/src/RainbowBuycraft/tasks/PendingPlayerCheckerTask.java @@ -0,0 +1,140 @@ +package RainbowBuycraft.tasks; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicBoolean; + +import PluginReference.MC_Player; +import RainbowBuycraft.MyPlugin; +import org.json.JSONArray; +import org.json.JSONObject; + +import RainbowBuycraft.api.ApiTask; + +/** + * Fetches an array of players which are waiting for commands to be run. + * + * If any players in the list are online the command fetch task is called + * If a player which is in the pending players set joins the server the command fetch task is called + * + */ +public class PendingPlayerCheckerTask extends ApiTask { + + private final MyPlugin plugin; + private final AtomicBoolean running = new AtomicBoolean(false); + /** Stores players with pending commands in lower case */ + private HashSet pendingPlayers = new HashSet(); + private boolean manualExecution; + private long lastPlayerLogin; + + public PendingPlayerCheckerTask() { + plugin = MyPlugin.getInstance(); + lastPlayerLogin = System.currentTimeMillis(); + } + + public void call(boolean manualExecution) { + if (running.compareAndSet(false, true)) { + this.manualExecution = manualExecution; + addTask(this); + } + } + + public synchronized void onPlayerJoin(MC_Player player) { + // If the player has pending commands we call the package checker + if (pendingPlayers.remove(player.getName().toLowerCase())) { + CommandFetchTask.call(false, player); + } + lastPlayerLogin = System.currentTimeMillis(); + } + + public void run() { + try { + // Don't attempt to run if we are not authenticated + if (!plugin.isAuthenticated(null)) { + return; + } + + // If the command checker is disabled and this was not a manual execution we do nothing + if (!plugin.getSettings().getBoolean("commandChecker") && !manualExecution) { + return; + } + + // Fetch online player list + MC_Player[] onlinePlayers = plugin.getServer().getPlayers().toArray(new MC_Player[plugin.getServer().getPlayers().size()]); + + // If nobody has logged in for over 3 hours do not execute the package checker (Manual execution is an exception) + if (!manualExecution && lastPlayerLogin < (System.currentTimeMillis() - 1080000)) { + return; + } else if (onlinePlayers.length > 0) { + lastPlayerLogin = System.currentTimeMillis(); + } + + // Fetch pending players + JSONObject apiResponse = plugin.getApi().fetchPendingPlayers(); + + if (apiResponse == null || apiResponse.getInt("code") != 0) { + plugin.getLogger().severe("No response/invalid key during pending players check."); + return; + } + + JSONObject apiPayload = apiResponse.getJSONObject("payload"); + + JSONArray pendingPlayers = apiPayload.getJSONArray("pendingPlayers"); + boolean offlineCommands = apiPayload.getBoolean("offlineCommands"); + + // Clear current pending players (Just in case some don't have pending commands anymore) + resetPendingPlayers(); + + ArrayList onlinePendingPlayers = null; + // No point in this if there are no pending players + if (pendingPlayers.length() > 0) { + onlinePendingPlayers = new ArrayList(); + + // Iterate through each pending player + for (int i = 0; i < pendingPlayers.length(); ++i) { + String playerName = pendingPlayers.getString(i).toLowerCase(); + MC_Player player = getPlayer(onlinePlayers, playerName); + + // Check if the player is offline + if (player == null) { + // Add them to the pending players set + addPendingPlayer(playerName); + } else { + // Add the player to this online pending players list + onlinePendingPlayers.add(player); + } + } + } + + // Check if we need to run the command checker + if (offlineCommands || (onlinePendingPlayers != null && !onlinePendingPlayers.isEmpty())) { + // Create the array of players which will need commands to be fetched now + MC_Player[] players = onlinePendingPlayers != null ? onlinePendingPlayers.toArray(new MC_Player[onlinePendingPlayers.size()]) : new MC_Player[] {}; + + // Call the command fetch task + CommandFetchTask.call(offlineCommands, players); + } + } catch (Exception e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } finally { + running.set(false); + } + } + + private synchronized void resetPendingPlayers() { + pendingPlayers.clear(); + } + + private synchronized void addPendingPlayer(String playerName) { + pendingPlayers.add(playerName.toLowerCase()); + } + + private MC_Player getPlayer(MC_Player[] players, String name) { + for (MC_Player player : players) { + if (player.getName().equalsIgnoreCase(name)) + return player; + } + return null; + } +} diff --git a/src/net/buycraft/tasks/RecentPaymentsTask.java b/src/RainbowBuycraft/tasks/RecentPaymentsTask.java similarity index 84% rename from src/net/buycraft/tasks/RecentPaymentsTask.java rename to src/RainbowBuycraft/tasks/RecentPaymentsTask.java index 7cd2f5e..ebf3f6a 100644 --- a/src/net/buycraft/tasks/RecentPaymentsTask.java +++ b/src/RainbowBuycraft/tasks/RecentPaymentsTask.java @@ -1,25 +1,25 @@ -package net.buycraft.tasks; +package RainbowBuycraft.tasks; -import net.buycraft.Plugin; -import net.buycraft.api.ApiTask; -import net.buycraft.util.Chat; +import PluginReference.ChatColor; +import PluginReference.MC_Player; +import RainbowBuycraft.MyPlugin; +import RainbowBuycraft.api.ApiTask; +import RainbowBuycraft.util.Chat; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class RecentPaymentsTask extends ApiTask { - private CommandSender receiver; + private MC_Player receiver; private String playerLookup; - public static void call(CommandSender receiver, String player) { - Plugin.getInstance().addTask(new RecentPaymentsTask(receiver, player)); + public static void call(MC_Player receiver, String player) { + MyPlugin.getInstance().addTask(new RecentPaymentsTask(receiver, player)); } - private RecentPaymentsTask(CommandSender receiver, String playerLookup) { + private RecentPaymentsTask(MC_Player receiver, String playerLookup) { this.receiver = receiver; this.playerLookup = playerLookup; } diff --git a/src/RainbowBuycraft/tasks/ReportTask.java b/src/RainbowBuycraft/tasks/ReportTask.java new file mode 100644 index 0000000..12dd518 --- /dev/null +++ b/src/RainbowBuycraft/tasks/ReportTask.java @@ -0,0 +1,228 @@ +package RainbowBuycraft.tasks; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Date; +import java.util.Scanner; + +import PluginReference.ChatColor; +import PluginReference.MC_Player; +import RainbowBuycraft.MyPlugin; +import RainbowBuycraft.api.ApiTask; +import RainbowBuycraft.util.Chat; + +public class ReportTask extends ApiTask { + + private static final String googleAddress = "www.google.com"; + private static final String apiAddress = "api.buycraft.net"; + private static final String isGdAddress = "www.is.gd"; + private static final String apiAccessPath = "http://" + apiAddress + "/ok.php"; + + private static boolean running = false; + private static Exception lastException = null; + + private final MC_Player sender; + + public static void setLastException(Exception e) { + lastException = e; + } + + public static boolean call(MC_Player sender) { + if (running) { + if (sender instanceof MC_Player) { + sender.sendMessage(Chat.header()); + sender.sendMessage(Chat.seperator()); + sender.sendMessage(Chat.seperator() + ChatColor.RED + "A report is already being generated. Please wait."); + sender.sendMessage(Chat.seperator()); + sender.sendMessage(Chat.footer()); + } else { + MyPlugin.getInstance().getLogger().warning("A report is already being generated. Please wait."); + } + + return false; + } + + MyPlugin.getInstance().addTask(new ReportTask(sender)); + return running = true; + } + + private ReportTask(MC_Player sender) { + this.sender = sender; + } + + public void run() { + try { + MyPlugin.getInstance().getLogger().info("Starting generation of a report"); + + String date = new Date().toString();; + + String os = System.getProperty("os.name") + " | " + System.getProperty("os.version") + " | " + System.getProperty("os.arch"); + String javaVersion = System.getProperty("java.version") + " | " + System.getProperty("java.vendor"); + String serverVersion = "" + getPlugin().getServer().getRainbowVersion(); + String serverName = "Rainbow Server"; + String serverIP = InetAddress.getLocalHost().getHostAddress(); + int serverPort = getPlugin().getServer().getServerPort(); + String buycraftVersion = MyPlugin.getInstance().getVersion(); + + boolean isAuthenticated = MyPlugin.getInstance().isAuthenticated(null); + double lastPackageCheckerExecutionTime = (System.currentTimeMillis() - CommandFetchTask.getLastExecution()) / 1000.0 / 60.0; + String lastPackageCheckerExecution = CommandFetchTask.getLastExecution() != 0 ? lastPackageCheckerExecutionTime + " minutes ago": "Never"; + + + String pingGoogle = pingCheck(googleAddress); + String pingApi = pingCheck(apiAddress); + String isGdCheck = pingCheck(isGdAddress); + + String serviceCheck = checkOkay(); + + writeReport(parseData( + "#### System Information ####", '\n', + "Date: ", date, '\n', + "Operating system: ", os, '\n', + "Java Version: ", javaVersion, '\n', + "Server Version: ", serverVersion, '\n', + "Server Name: ", serverName, "\n", + "Server IP: ", serverIP, ":", serverPort, "\n", + "RainbowBuycraft Version: ", buycraftVersion, '\n', + '\n', + "#### Buycraft Info ####", '\n', + "Store URL: " , MyPlugin.getInstance().getServerStore(), '\n', + "Store ID: " , MyPlugin.getInstance().getServerID(), '\n', + "Buy Command: ", "N/A", '\n', + "Authenticated: ", isAuthenticated, '\n', + "Error code: ", MyPlugin.getInstance().getAuthenticatedCode(), '\n', + "Last Package Checker Execution: ", lastPackageCheckerExecution, '\n', + '\n', + "#### Connection ####", '\n', + "Google Ping Result: ", pingGoogle, '\n', + "Buycraft API Ping Result: ", pingApi, '\n', + "URL Shortener Ping Result: ", isGdCheck, '\n', + "Buycraft API Status Result: ", serviceCheck, '\n', + '\n', + "#### Performance ####", '\n', + "Long Running Command: ", MyPlugin.getInstance().getCommandExecutor().getLastLongRunningCommand(), '\n', + '\n', + "#### Last Exception ####", '\n', + lastException != null ? lastException : "No errors since startup" + )); + } catch (Throwable e) { + if (sender instanceof MC_Player) { + sender.sendMessage(Chat.header()); + sender.sendMessage(Chat.seperator()); + sender.sendMessage(Chat.seperator() + ChatColor.RED + "Error occured when generating the report"); + sender.sendMessage(Chat.seperator() + ChatColor.RED + "See console for more information"); + sender.sendMessage(Chat.seperator()); + sender.sendMessage(Chat.footer()); + } + + MyPlugin.getInstance().getLogger().warning("Error occured when generating the report"); + + e.printStackTrace(); + } finally { + running = false; + } + } + + /** + * Parses data we want to put into the report + *

+ * If an object is null a new line is added.
+ * If an exception is found its error and stack trace are added
+ */ + private String[] parseData(Object ...data) { + ArrayList lines = new ArrayList(data.length); + + for (Object o : data) { + if (o == null) { + lines.add("\n"); + } else if (o instanceof Exception) { + Exception e = (Exception) o; + + lines.add(e.getClass().toString()); + + if (e.getMessage() != null) + lines.add('\n' + e.getMessage()); + lines.add("\nStackTrace:\n"); + + for (StackTraceElement stack : e.getStackTrace()) { + lines.add(stack.toString() + '\n'); + } + } else { + lines.add(o.toString()); + } + } + + return lines.toArray(new String[lines.size()]); + } + + /** + * Writes each string to the report file and notifies the sender of its success/failure + * @throws IOException + */ + private void writeReport(String ...data) throws IOException { + File report = new File(MyPlugin.getInstance().getFolderPath(), "report.txt"); + + FileWriter fw = new FileWriter(report); + BufferedWriter bw = new BufferedWriter(fw); + + try { + for (String str : data) { + bw.write(str); + } + } finally { + bw.close(); + } + + if (sender instanceof MC_Player) { + sender.sendMessage(Chat.header()); + sender.sendMessage(Chat.seperator()); + sender.sendMessage(Chat.seperator() + ChatColor.GREEN + "Report written to " + report.getPath()); + sender.sendMessage(Chat.seperator()); + sender.sendMessage(Chat.footer()); + } + MyPlugin.getInstance().getLogger().info("Report written to " + report.getPath()); + } + + private String pingCheck(String url) { + Socket socket = null; + + try { + socket = new Socket(url, 80); + return "Connected to " + url + " successfully."; + } catch (UnknownHostException e) { + return "Could not resolve host " + url; + } catch (IOException e) { + return "Could not create socket " + url; + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) {} + } + } + } + + private String checkOkay() { + Scanner s = null; + + try { + URL url = new URL(apiAccessPath); + s = new Scanner(url.openStream()); + + return s.hasNextLine() ? "Response from API - " + s.nextLine() : "No response from API"; + } catch (IOException e) { + return "Failed to connect to the API - " + e.getClass().toString() + " | " + e.getMessage(); + } finally { + if (s != null) { + s.close(); + } + } + } +} diff --git a/src/net/buycraft/util/Chat.java b/src/RainbowBuycraft/util/Chat.java old mode 100755 new mode 100644 similarity index 90% rename from src/net/buycraft/util/Chat.java rename to src/RainbowBuycraft/util/Chat.java index 4d128a3..313c190 --- a/src/net/buycraft/util/Chat.java +++ b/src/RainbowBuycraft/util/Chat.java @@ -1,6 +1,6 @@ -package net.buycraft.util; +package RainbowBuycraft.util; -import org.bukkit.ChatColor; +import PluginReference.ChatColor; public class Chat { private static final String header = ChatColor.WHITE + "|----------------------" + ChatColor.LIGHT_PURPLE + " BUYCRAFT " + ChatColor.WHITE + "---------------------"; diff --git a/src/RainbowBuycraft/util/Language.java b/src/RainbowBuycraft/util/Language.java new file mode 100644 index 0000000..e7f8f18 --- /dev/null +++ b/src/RainbowBuycraft/util/Language.java @@ -0,0 +1,78 @@ +package RainbowBuycraft.util; + +import java.io.*; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Properties; + +import RainbowBuycraft.tasks.ReportTask; + +public class Language { + private final String LOCATION = "plugins_mod/RainbowBuycraft/language.conf"; + private File file; + + private HashMap defaultProperties; + private Properties properties; + + public Language() { + this.file = new File(LOCATION); + + this.defaultProperties = new HashMap(); + this.properties = new Properties(); + + load(); + assignDefault(); + } + + private void load() { + try { + if (!file.exists()) { + file.createNewFile(); + } + + properties.load(new FileInputStream(LOCATION)); + } catch (IOException e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } + } + + private void assignDefault() { + Boolean toSave = false; + + defaultProperties.put("commandsExecuted", "Your purchased packages have been credited."); + defaultProperties.put("commandExecuteNotEnoughFreeInventory", "%d free inventory slot(s) are required."); + defaultProperties.put("commandExecuteNotEnoughFreeInventory2", "Please empty your inventory to receive these items."); + + for (Entry entry : defaultProperties.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + if (!properties.containsKey(key)) { + properties.setProperty(key, value); + + toSave = true; + } + } + + if (toSave) { + try { + properties.store(new FileOutputStream(LOCATION), "Buycraft Plugin (English language file)"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } catch (IOException e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } + } + } + + public String getString(String key) { + if (properties.containsKey(key)) { + return properties.getProperty(key); + } else { + throw new RuntimeException("Language key '" + key + "' not found in the language.conf file."); + } + } +} diff --git a/src/net/buycraft/util/PackageCommand.java b/src/RainbowBuycraft/util/PackageCommand.java similarity index 84% rename from src/net/buycraft/util/PackageCommand.java rename to src/RainbowBuycraft/util/PackageCommand.java index 6c1b5f2..bb89927 100644 --- a/src/net/buycraft/util/PackageCommand.java +++ b/src/RainbowBuycraft/util/PackageCommand.java @@ -1,9 +1,9 @@ -package net.buycraft.util; +package RainbowBuycraft.util; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; +import PluginReference.MC_ItemStack; +import PluginReference.MC_Player; + +import java.util.List; public class PackageCommand implements Comparable { @@ -33,7 +33,7 @@ public boolean requiresFreeInventorySlots() { return requiredInventorySlots > 0; } - public int calculateRequiredInventorySlots(Player player) { + public int calculateRequiredInventorySlots(MC_Player player) { if (requiredInventorySlots == 0) { return 0; } @@ -42,12 +42,12 @@ public int calculateRequiredInventorySlots(Player player) { return -1; } - PlayerInventory inv = player.getInventory(); - int size = inv.getSize(); + List inv = player.getInventory(); + int size = inv.size(); int emptyCount = 0; for (int i = 0; i < size; ++i) { - ItemStack item = inv.getItem(i); - if (item == null || item.getType() == Material.AIR) { + MC_ItemStack item = inv.get(i); + if (item == null || item.getId() == 0) { if (++emptyCount == requiredInventorySlots) { return 0; } diff --git a/src/RainbowBuycraft/util/Settings.java b/src/RainbowBuycraft/util/Settings.java new file mode 100644 index 0000000..fa5fdc6 --- /dev/null +++ b/src/RainbowBuycraft/util/Settings.java @@ -0,0 +1,109 @@ +package RainbowBuycraft.util; + +import java.io.*; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Properties; + +import RainbowBuycraft.tasks.ReportTask; + +public class Settings { + private final String LOCATION = "plugins_mod/RainbowBuycraft/settings.conf"; + private File file; + + private HashMap defaultProperties; + private Properties properties; + + public Settings() { + this.file = new File(LOCATION); + + this.defaultProperties = new HashMap(); + this.properties = new Properties(); + + load(); + assignDefault(); + } + + private void load() { + try { + if (!file.exists()) { + file.createNewFile(); + } + + properties.load(new FileInputStream(LOCATION)); + } catch (IOException e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } + } + + private void assignDefault() { + Boolean toSave = false; + + defaultProperties.put("secret", ""); + defaultProperties.put("commandChecker", "true"); + defaultProperties.put("https", "false"); + defaultProperties.put("debug", "false"); + defaultProperties.put("commandThrottleCount", "150"); + defaultProperties.put("disable-secret-command", "false"); + + for (Entry entry : defaultProperties.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + if (!properties.containsKey(key)) { + properties.setProperty(key, value); + + toSave = true; + } + } + + if (toSave) { + saveSettings(); + } + } + + private void saveSettings() { + try { + properties.store(new FileOutputStream(LOCATION), "Buycraft Plugin"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } catch (IOException e) { + e.printStackTrace(); + ReportTask.setLastException(e); + } + } + + public boolean getBoolean(String key) { + if (properties.containsKey(key)) { + return Boolean.valueOf(getString(key)); + } else { + throw new RuntimeException("Settings key '" + key + "' not found in the settings.conf file."); + } + + } + + public int getInt(String key) { + if (properties.containsKey(key)) { + return Integer.valueOf(getString(key)); + } else { + throw new RuntimeException("Settings key '" + key + "' not found in the settings.conf file."); + } + + } + + public String getString(String key) { + if (properties.containsKey(key)) { + return properties.getProperty(key); + } else { + throw new RuntimeException("Settings key '" + key + "' not found in the settings.conf file."); + } + } + + public void setString(String key, String value) { + properties.setProperty(key, value); + + saveSettings(); + } +} diff --git a/src/net/buycraft/ChatManager.java b/src/net/buycraft/ChatManager.java deleted file mode 100755 index 3e72cb1..0000000 --- a/src/net/buycraft/ChatManager.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.buycraft; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import org.bukkit.entity.Player; - -public class ChatManager { - private HashSet disabledChatSet; - - public ChatManager() { - disabledChatSet = new HashSet(); - } - - public synchronized Boolean isDisabled(Player player) { - return disabledChatSet.contains(player.getName()); - } - - public synchronized void enableChat(Player player) { - disabledChatSet.remove(player.getName()); - } - - public synchronized void disableChat(Player player) { - disabledChatSet.add(player.getName()); - } - - /** - * Clears all players from the provided set who have chat disabled - */ - public synchronized void clearPlayerSet(Set players) { - Iterator it = players.iterator(); - - while (it.hasNext()) { - if (disabledChatSet.contains(it.next().getName())) { - it.remove(); - } - } - } -} diff --git a/src/net/buycraft/PlayerListener.java b/src/net/buycraft/PlayerListener.java deleted file mode 100755 index 7fd54ae..0000000 --- a/src/net/buycraft/PlayerListener.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.buycraft; - -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerLoginEvent; -import org.bukkit.event.player.PlayerLoginEvent.Result; -import org.bukkit.event.player.PlayerQuitEvent; - -public class PlayerListener implements Listener { - private Plugin plugin; - - public PlayerListener() { - this.plugin = Plugin.getInstance(); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onPlayerQuit(PlayerQuitEvent event) { - plugin.getChatManager().enableChat(event.getPlayer()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onPlayerChat(AsyncPlayerChatEvent event) { - if (plugin.getChatManager().isDisabled(event.getPlayer())) { - event.setCancelled(true); - } else { - plugin.getChatManager().clearPlayerSet(event.getRecipients()); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onPlayerLogin(PlayerLoginEvent event) { - if (event.getPlayer().getName().equalsIgnoreCase("Buycraft")) { - event.disallow(Result.KICK_OTHER, "This user has been disabled due to security reasons."); - } - } -} diff --git a/src/net/buycraft/Plugin.java b/src/net/buycraft/Plugin.java deleted file mode 100644 index c8bc3a6..0000000 --- a/src/net/buycraft/Plugin.java +++ /dev/null @@ -1,352 +0,0 @@ -package net.buycraft; - -import net.buycraft.api.Api; -import net.buycraft.api.ApiTask; -import net.buycraft.commands.BuyCommand; -import net.buycraft.commands.BuycraftCommand; -import net.buycraft.commands.EnableChatCommand; -import net.buycraft.heads.HeadFile; -import net.buycraft.packages.PackageManager; -import net.buycraft.tasks.AuthenticateTask; -import net.buycraft.tasks.CommandDeleteTask; -import net.buycraft.tasks.CommandExecuteTask; -import net.buycraft.tasks.PendingPlayerCheckerTask; -import net.buycraft.tasks.ReportTask; -import net.buycraft.ui.BuyChatUI; -import net.buycraft.ui.BuyInterface; -import net.buycraft.ui.BuyInventoryUI; -import net.buycraft.util.Chat; -import net.buycraft.util.Language; -import net.buycraft.util.Settings; -import net.buycraft.util.Updater; - -import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitTask; - -import java.io.File; -import java.io.FileWriter; -import java.io.InputStream; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class Plugin extends JavaPlugin implements Listener { - private static Plugin instance; - - private String version; - - private Settings settings; - private Language language; - - private Updater updater = null; - - private Api api; - - private int serverID = 0; - private String serverCurrency = ""; - private String serverStore = ""; - - private PackageManager packageManager; - private ChatManager chatManager; - private CommandExecuteTask commandExecutor; - private CommandDeleteTask commandDeleteTask; - private PendingPlayerCheckerTask pendingPlayerCheckerTask; - - private BukkitTask pendingPlayerCheckerTaskExecutor; - - private boolean authenticated = false; - private int authenticatedCode = 1; - - private String folderPath; - - private BuyInterface buyUi; - private String buyCommand = "buy"; - private String buyCommandSearchString = "/buy"; - - private ExecutorService executors = null; - - private HeadFile headFile = null; - - public Plugin() { - instance = this; - } - - public void addTask(ApiTask task) { - executors.submit(task); - } - - public void onEnable() { - // thread pool model - executors = Executors.newFixedThreadPool(5); - folderPath = getDataFolder().getAbsolutePath(); - - checkDirectory(); - - moveFileFromJar("README.md", getFolderPath() + "/README.txt", true); - - version = getDescription().getVersion(); - - settings = new Settings(); - language = new Language(); - - if (settings.getBoolean("autoUpdate")) { - this.updater = new Updater(this, 31571, this.getFile(), Updater.UpdateType.DEFAULT, true); - } else { - getLogger().info("Ignoring update due to auto update disabled."); - } - - api = new Api(); - - packageManager = new PackageManager(); - chatManager = new ChatManager(); - commandExecutor = new CommandExecuteTask(); - commandDeleteTask = new CommandDeleteTask(); - pendingPlayerCheckerTask = new PendingPlayerCheckerTask(); - - setBuyCommand(getSettings().getString("buyCommand")); - buyUi = getSettings().getBoolean("useBuyGUI") ? new BuyInventoryUI() : new BuyChatUI(); - - headFile = new HeadFile(this); - - AuthenticateTask.call(); - - getServer().getPluginManager().registerEvents(this, this); - getServer().getPluginManager().registerEvents(new PlayerListener(), this); - getServer().getPluginManager().registerEvents(pendingPlayerCheckerTask, this); - } - - public void onDisable() { - // Make sure any commands which have been run are deleted - commandDeleteTask.runNow(); - - // Run BuyUI cleanup - if (buyUi != null) { - buyUi.pluginReloaded(); - } - - executors.shutdown(); - while (!executors.isTerminated()) { - } - getLogger().info("Plugin has been disabled."); - } - - public boolean onCommand(CommandSender commandSender, Command command, String label, String[] args) { - if (label.equalsIgnoreCase("ec")) { - return EnableChatCommand.process(commandSender, args); - } - - if (label.equalsIgnoreCase("buycraft")) { - return BuycraftCommand.process(commandSender, args); - } - - return false; - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void preCommandListener(PlayerCommandPreprocessEvent event) { - String message = event.getMessage().toLowerCase(); - int cmdLength = buyCommandSearchString.length(); - if (message.startsWith(buyCommandSearchString) && (message.length() == cmdLength || message.charAt(cmdLength) == ' ')) { - BuyCommand.process(event.getPlayer(), message.split(" ")); - event.setCancelled(true); - } - } - - private void checkDirectory() { - File directory = new File(getFolderPath()); - - if (!directory.exists()) { - directory.mkdir(); - } - } - - public void moveFileFromJar(String jarFileName, String targetLocation, Boolean overwrite) { - try { - File targetFile = new File(targetLocation); - - if (overwrite || targetFile.exists() == false || targetFile.length() == 0) { - InputStream inFile = getResource(jarFileName); - FileWriter outFile = new FileWriter(targetFile); - - int c; - - while ((c = inFile.read()) != -1) { - outFile.write(c); - } - - inFile.close(); - outFile.close(); - } - } catch (NullPointerException e) { - getLogger().info("Failed to create " + jarFileName + "."); - ReportTask.setLastException(e); - } catch (Exception e) { - e.printStackTrace(); - ReportTask.setLastException(e); - } - } - - public Boolean isAuthenticated(CommandSender commandSender) { - if (!authenticated) { - if (commandSender != null) { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "Buycraft has failed to startup."); - commandSender.sendMessage(Chat.seperator()); - if(authenticatedCode == 101) { - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "This is because of an invalid secret key,"); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "please enter the Secret key into the settings.conf"); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "file, and reload your server."); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "You can find your secret key from the control panel."); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "If it did not resolve the issue, restart your server"); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "a couple of times."); - commandSender.sendMessage(Chat.seperator()); - } else { - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "Please execute the '/buycraft report' command and"); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "then send the generated report.txt file to"); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "support@buycraft.net. We will be happy to help."); - commandSender.sendMessage(Chat.seperator()); - } - commandSender.sendMessage(Chat.footer()); - } - - return false; - } else { - return true; - } - } - - public void setAuthenticated(Boolean value) { - authenticated = value; - } - - public void setAuthenticatedCode(Integer value) { - authenticatedCode = value; - } - - public Integer getAuthenticatedCode() { - return authenticatedCode; - } - - public static Plugin getInstance() { - return instance; - } - - public Api getApi() { - return api; - } - - public void setServerID(Integer value) { - serverID = value; - } - - public void setServerCurrency(String value) { - serverCurrency = value; - } - - public void setBuyCommand(String value) { - buyCommand = value; - buyCommandSearchString = new StringBuilder().append('/').append(buyCommand).toString().toLowerCase(); - } - - public void setServerStore(String value) { - serverStore = value; - } - - public void setPendingPlayerCheckerInterval(int interval) { - if (pendingPlayerCheckerTaskExecutor != null) { - pendingPlayerCheckerTaskExecutor.cancel(); - pendingPlayerCheckerTaskExecutor = null; - } - - // Convert seconds to ticks - interval *= 20; - - if (getSettings().getBoolean("commandChecker")) { - pendingPlayerCheckerTaskExecutor = getServer().getScheduler().runTaskTimerAsynchronously(this, new Runnable() { - public void run() { - pendingPlayerCheckerTask.call(false); - } - }, 20, interval); - } - - } - - public Integer getServerID() { - return serverID; - } - - public String getServerStore() { - return serverStore; - } - - public PackageManager getPackageManager() { - return packageManager; - } - - public CommandExecuteTask getCommandExecutor() { - return commandExecutor; - } - - public CommandDeleteTask getCommandDeleteTask() { - return commandDeleteTask; - } - - public PendingPlayerCheckerTask getPendingPlayerCheckerTask() { - return pendingPlayerCheckerTask; - } - - public String getServerCurrency() { - return serverCurrency; - } - - public String getVersion() { - return version; - } - - public Settings getSettings() { - return settings; - } - - public Language getLanguage() { - return language; - } - - public File getJarFile() { - return getFile(); - } - - public ChatManager getChatManager() { - return chatManager; - } - - public String getFolderPath() { - return folderPath; - } - - public BuyInterface getBuyUi() { - return buyUi; - } - public String getBuyCommand() { - return buyCommand; - } - - public HeadFile getHeadFile() - { - return headFile; - } - - public Updater getUpdater() - { - return updater; - } -} diff --git a/src/net/buycraft/api/ApiTask.java b/src/net/buycraft/api/ApiTask.java deleted file mode 100644 index 82500c3..0000000 --- a/src/net/buycraft/api/ApiTask.java +++ /dev/null @@ -1,59 +0,0 @@ -package net.buycraft.api; - -import net.buycraft.Plugin; -import net.buycraft.util.Language; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; - -import java.util.logging.Logger; - -public abstract class ApiTask implements Runnable { - - public BukkitTask sync(Runnable r) { - if (getPlugin().isEnabled()) { - return Bukkit.getScheduler().runTask(getPlugin(), r); - } - return null; - } - - public BukkitTask syncTimer(Runnable r, long delay, long period) { - if (getPlugin().isEnabled()) { - return Bukkit.getScheduler().runTaskTimer(getPlugin(), r, delay, period); - } - return null; - } - - public void addTask(ApiTask task) { - Plugin.getInstance().addTask(task); - } - - public Plugin getPlugin() { - return Plugin.getInstance(); - } - - public Language getLanguage() { - return Plugin.getInstance().getLanguage(); - } - - public Api getApi() { - return Plugin.getInstance().getApi(); - } - - public Logger getLogger() { - return Plugin.getInstance().getLogger(); - } - - public void disableChat(final String name) { - final Player player = Bukkit.getPlayerExact(name); - if (player != null) { - Bukkit.getScheduler().scheduleSyncDelayedTask(Plugin.getInstance(), new Runnable() { - public void run() { - Plugin.getInstance().getChatManager().disableChat(player); - } - }, 1); - } - } - -} diff --git a/src/net/buycraft/commands/BuyCommand.java b/src/net/buycraft/commands/BuyCommand.java deleted file mode 100755 index 089a8a8..0000000 --- a/src/net/buycraft/commands/BuyCommand.java +++ /dev/null @@ -1,78 +0,0 @@ -package net.buycraft.commands; - -import net.buycraft.Plugin; -import net.buycraft.tasks.VisitLinkTask; -import net.buycraft.util.Chat; - -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; - -public class BuyCommand { - - private BuyCommand() {} - - public static boolean process(Player player, String[] args) { - Plugin plugin = Plugin.getInstance(); - if (!plugin.getSettings().getBoolean("disableBuyCommand")) { - if (plugin.isAuthenticated(player)) { - String pageToView = "0"; - String categoryToView = "0"; - - if (args.length > 1) { - if (args[1].equalsIgnoreCase("page") && (args.length == 3 || args.length == 4)) { - if (args.length == 4) { - pageToView = args[3]; - categoryToView = args[2]; - } - else if (args.length == 3) { - pageToView = args[2]; - } - } else { - if (args.length == 2 && isNumber(args[1])) { - int packageID = Integer.valueOf(args[1]); - - Plugin.getInstance().getBuyUi().showPackage(player, packageID); - return true; - } else { - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + ChatColor.RED + plugin.getLanguage().getString("invalidBuyCommand")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - } - } - } - - if (isNumber(pageToView) && isNumber(categoryToView) && pageToView.length() < 5) { - // Fetch page number and category id - int pageNumber = Integer.parseInt(pageToView); - int categoryId = Integer.parseInt(categoryToView); - Plugin.getInstance().getBuyUi().showPage(player, categoryId, pageNumber); - return true; - } - - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + ChatColor.RED + plugin.getLanguage().getString("invalidBuyCommand")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - } - } else { - VisitLinkTask.call((Player) player, plugin.getServerStore()); - } - - return true; - } - - private static boolean isNumber(String string) { - char[] c = string.toCharArray(); - - for (int i = 0; i < string.length(); i++) { - if (!Character.isDigit(c[i])) { - return false; - } - } - - return true; - } -} diff --git a/src/net/buycraft/commands/BuycraftCommand.java b/src/net/buycraft/commands/BuycraftCommand.java deleted file mode 100755 index 1e69ba7..0000000 --- a/src/net/buycraft/commands/BuycraftCommand.java +++ /dev/null @@ -1,170 +0,0 @@ -package net.buycraft.commands; - -import net.buycraft.Plugin; -import net.buycraft.tasks.AuthenticateTask; -import net.buycraft.tasks.RecentPaymentsTask; -import net.buycraft.tasks.ReloadPackagesTask; -import net.buycraft.tasks.ReportTask; -import net.buycraft.util.Chat; - -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public class BuycraftCommand { - - private BuycraftCommand() {} - - public static boolean process(CommandSender commandSender, String[] args) { - Plugin plugin = Plugin.getInstance(); - if (args.length > 0) { - if (commandSender instanceof Player == false || commandSender.hasPermission("buycraft.admin") || commandSender.isOp()) { - - if(args[0].equalsIgnoreCase("payments")) { - String playerLookup = ""; - - if(args.length == 2) { - playerLookup = args[1]; - } - - RecentPaymentsTask.call(commandSender, playerLookup); - - return true; - } - - if (args[0].equalsIgnoreCase("report")) { - // Call the report task, if it fails we don't send the following messages to the player - if (ReportTask.call(commandSender) && commandSender instanceof Player) { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.GREEN + "Beginning generation of report"); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.footer()); - } - return true; - } - - if (args[0].equalsIgnoreCase("secret")) { - if(plugin.getSettings().getBoolean("disable-secret-command") == false) { - if (args.length == 2) { - String secretKey = args[1]; - - if (commandSender instanceof Player) { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.GREEN + "Server authenticated. Type /buycraft for confirmation."); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.footer()); - } - - plugin.getSettings().setString("secret", secretKey); - plugin.getApi().setApiKey(secretKey); - - AuthenticateTask.call(); - - return true; - } else { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "Please enter a valid secret key."); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.footer()); - - return true; - } - } else { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + "Please change the key in settings.conf."); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.footer()); - - return true; - } - } - - if (plugin.isAuthenticated(commandSender)) { - if (args[0].equalsIgnoreCase("reload")) { - if (plugin.getBuyUi() != null) { - plugin.getBuyUi().pluginReloaded(); - } - - AuthenticateTask.call(); - - if (commandSender instanceof Player) { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.GREEN + "Package cache successfully reloaded."); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.footer()); - } - - return true; - } - - if (args[0].equalsIgnoreCase("forcecheck")) { - plugin.getPendingPlayerCheckerTask().call(true); - - if(plugin.getHeadFile().enabled) - { - plugin.getHeadFile().getHeadThread().update(); - } - - if (commandSender instanceof Player) { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.GREEN + "Force check successfully executed."); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.footer()); - } else { - commandSender.sendMessage("Force check successfully executed."); - } - - return true; - } - } else { - return true; - } - } else { - commandSender.sendMessage(ChatColor.RED + "You do not have permission to execute that command."); - - return true; - } - } else { - if (plugin.isAuthenticated(commandSender)) { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/" + plugin.getBuyCommand() + ":" + ChatColor.GREEN + " View available packages for sale"); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/" + plugin.getBuyCommand() + " page :" + ChatColor.GREEN + " Navigate through package pages"); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/" + plugin.getBuyCommand() + " : " + ChatColor.GREEN + " Purchase a specific package"); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator()); - - if (commandSender instanceof Player == false || commandSender.hasPermission("buycraft.admin") || commandSender.isOp()) { - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft reload:" + ChatColor.GREEN + " Reload the package cache"); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft forcecheck:" + ChatColor.GREEN + " Check for pending commands"); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft secret :" + ChatColor.GREEN + " Set the Secret key"); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft payments :" + ChatColor.GREEN + " Get recent payments of a user"); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buycraft report:" + ChatColor.GREEN + " Generate an error report"); - } - - if (commandSender instanceof Player == false || commandSender.hasPermission("buycraft.admin") || commandSender.hasPermission("buycraft.signs") || commandSender.isOp()) { - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buysign begin/filter :" + ChatColor.GREEN + " Set payment signs"); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "/buysign end:" + ChatColor.GREEN + " End payment signs"); - } - - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "Server ID: " + ChatColor.GREEN + String.valueOf(plugin.getServerID())); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "Server URL: " + ChatColor.GREEN + String.valueOf(plugin.getServerStore())); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "Version: " + ChatColor.GREEN + String.valueOf(plugin.getVersion())); - commandSender.sendMessage(Chat.seperator() + ChatColor.LIGHT_PURPLE + "Website: " + ChatColor.GREEN + "http://buycraft.net"); - commandSender.sendMessage(Chat.footer()); - } - - return true; - } - - return false; - } -} diff --git a/src/net/buycraft/commands/EnableChatCommand.java b/src/net/buycraft/commands/EnableChatCommand.java deleted file mode 100755 index 7efd3a9..0000000 --- a/src/net/buycraft/commands/EnableChatCommand.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.buycraft.commands; - -import net.buycraft.Plugin; -import net.buycraft.util.Chat; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public class EnableChatCommand { - - private EnableChatCommand() {} - - public static boolean process(CommandSender commandSender, String[] args) { - Plugin plugin = Plugin.getInstance(); - if(commandSender instanceof Player) - { - if (plugin.isAuthenticated(commandSender)) { - if (plugin.getChatManager().isDisabled((Player) commandSender)) { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.GREEN + plugin.getLanguage().getString("chatEnabled")); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.footer()); - plugin.getChatManager().enableChat((Player) commandSender); - } else { - commandSender.sendMessage(Chat.header()); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.seperator() + ChatColor.RED + plugin.getLanguage().getString("chatAlreadyEnabled")); - commandSender.sendMessage(Chat.seperator()); - commandSender.sendMessage(Chat.footer()); - } - } - } - else - { - commandSender.sendMessage("You cannot execute this command from the console."); - } - - return true; - } -} diff --git a/src/net/buycraft/heads/Head.java b/src/net/buycraft/heads/Head.java deleted file mode 100644 index d584e3f..0000000 --- a/src/net/buycraft/heads/Head.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.buycraft.heads; - -import net.buycraft.Plugin; - -import org.bukkit.SkullType; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.Sign; -import org.bukkit.block.Skull; - -import java.text.NumberFormat; - -public class Head { - // [ign] - private final String name; - // [price] - private final double price; - private final String currency; - // packages filter - private final String[] packages; - - public Head(String name, double price, String currency, String[] packages) { - this.name = name; - this.price = price; - this.currency = currency; - this.packages = packages; - } - - // used for the filter(String package) method - public boolean hasPackage(String p) { - for(String pa : packages) { - if(pa.equalsIgnoreCase(p)) { - return true; - } - } - return false; - } - - private Skull getSkull(Sign sign) { - Block b = sign.getBlock().getRelative(BlockFace.UP); - for(BlockFace face : HeadListener.FACES) { - Block s = b.getRelative(face); - if(s.getState() instanceof Skull) { - return (Skull) s.getState(); - } - } - return null; - } - - // format - public void format(Sign sign, boolean currency) { - Skull skull = getSkull(sign); - // if the skull is not null, set the skull to the ign - if(skull != null) { - skull.setSkullType(SkullType.PLAYER); - skull.setOwner(name); - skull.update(); - } - sign.setLine(1, name); - if(currency) { - if (price <= 0.0 && Plugin.getInstance().getSettings().getBoolean("buysignsRemoveFreePrice")) { - sign.setLine(2, null); - } else { - sign.setLine(2, NumberFormat.getCurrencyInstance().format(price).substring(1) + " " + this.currency); - } - } - sign.update(); - } - -} diff --git a/src/net/buycraft/heads/HeadFile.java b/src/net/buycraft/heads/HeadFile.java deleted file mode 100644 index 0039367..0000000 --- a/src/net/buycraft/heads/HeadFile.java +++ /dev/null @@ -1,119 +0,0 @@ -package net.buycraft.heads; - -import net.buycraft.Plugin; -import net.buycraft.tasks.ReportTask; -import net.buycraft.util.Settings; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.json.JSONException; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - -public class HeadFile { - - // config values - public boolean enabled = false; - public boolean currency = false; - public int update = 5; - - Plugin plugin; - private YamlConfiguration config = null; - private File file = null; - - private List signs = new ArrayList(); - HeadThread thread = null; - HeadListener listener = null; - - public HeadFile(Plugin plugin) { - this.plugin = plugin; - this.file = new File(plugin.getDataFolder(), "heads.yml"); - this.config = new YamlConfiguration(); - - checkConfig(); - registerEvents(); - - try { - if(enabled) { - onEnable(); - loadSigns(); - - thread = new HeadThread(this); - - Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, thread, 20*60*update, 20*60*update); - } - } catch(Exception e) { - e.printStackTrace(); - ReportTask.setLastException(e); - } - - plugin.getCommand("buysign").setExecutor(listener); - } - - private void registerEvents() { - listener = new HeadListener(this); - Bukkit.getPluginManager().registerEvents(listener, plugin); - } - - private void checkConfig() { - Settings settings = plugin.getSettings(); - enabled = settings.getString("headsEnabled").equals("true"); - currency = settings.getString("headsCurrency").equals("true"); - update = 30; - } - - private void onEnable() throws Exception { - if(file.getParentFile().exists()) { - file.getParentFile().mkdirs(); - } - if(!file.exists()) { - file.createNewFile(); - } - config = new YamlConfiguration(); - config.load(file); - } - - public List getSigns() { - return signs; - } - - public void addSign(HeadSign h) { - List signs = config.getStringList("signs"); - if(signs == null) { - signs = new ArrayList(); - } - signs.add(h.serialize()); - config.set("signs", signs); - try { - config.save(file); - // and cleanly load the signs again - loadSigns(); - } catch(Exception e) { - e.printStackTrace(); - ReportTask.setLastException(e); - } - } - - public void loadSigns() throws JSONException { - // clear current signs if not empty, so we can re-use this method safely - if(!this.signs.isEmpty()) { - this.signs.clear(); - } - List signs = config.getStringList("signs"); - if(signs != null && signs.size() > 0) { - for(String sign : signs) { - // deserialize and add to the list - HeadSign h = HeadSign.deserialize(sign); - this.signs.add(h); - } - } - } - - public HeadThread getHeadThread() - { - return thread; - } -} diff --git a/src/net/buycraft/heads/HeadListener.java b/src/net/buycraft/heads/HeadListener.java deleted file mode 100644 index 4f35e06..0000000 --- a/src/net/buycraft/heads/HeadListener.java +++ /dev/null @@ -1,185 +0,0 @@ -package net.buycraft.heads; - -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.Sign; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.player.PlayerInteractEvent; - -import java.util.*; - -public class HeadListener implements Listener, CommandExecutor { - - public static final BlockFace[] FACES = {BlockFace.SELF, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}; - private final HeadFile headFile; - - private Map> cache = new HashMap>(); - private Map filter = new HashMap(); - - public Set protections = new HashSet(); - - public HeadListener(HeadFile headFile) { - this.headFile = headFile; - } - - public boolean isProtected(Block block) { - if(headFile.getSigns().isEmpty()) { - return false; - } - if(protections.size() != 0) { - return protections.contains(HeadSign.getLocation(block.getLocation())); - } - // iterate through heads - for(HeadSign head : headFile.getSigns()) { - // and faces - Location[] locations = head.getLocation(); - for(BlockFace face : FACES) { - Location l = block.getRelative(face).getLocation(); - for(Location loc : locations) { - if(loc.getWorld().getName().equals(l.getWorld().getName()) && - loc.getBlockX() == l.getBlockX() && - loc.getBlockY() == l.getBlockY() && - loc.getBlockZ() == l.getBlockZ()) { - protections.add(HeadSign.getLocation(block.getLocation())); - } - } - l = block.getRelative(face).getRelative(BlockFace.UP).getLocation(); - for(Location loc : locations) { - if(loc.getWorld().getName().equals(l.getWorld().getName()) && - loc.getBlockX() == l.getBlockX() && - loc.getBlockY() == l.getBlockY() && - loc.getBlockZ() == l.getBlockZ()) { - protections.add(HeadSign.getLocation(block.getLocation())); - } - } - } - } - return protections.contains(HeadSign.getLocation(block.getLocation())); - } - - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - // do nothing where nothing happens - if(!(sender.hasPermission("buycraft.admin") || sender.hasPermission("buycraft.signs"))) { - sender.sendMessage(ChatColor.RED + "You don't have permission to do that."); - return true; - } - if(!(sender instanceof Player)) { - sender.sendMessage(ChatColor.RED + "You aren't a player, go away!"); - return true; - } - if(!headFile.enabled) - { - sender.sendMessage(ChatColor.RED + "You need to enable this feature in the settings.conf file."); - - return true; - } - Player player = (Player) sender; - if(args.length < 1 || args.length > 2) { - return false; - } - // filtering - if(args.length == 2 && args[0].equalsIgnoreCase("filter")) { - filter.put(player.getName(), args[1]); - player.sendMessage("Filtering signs based on '"+args[1]+"'"); - return true; - } else if(args.length == 2) { - return false; - } - // the rest of the commands - if(args[0].equalsIgnoreCase("begin")) { - cache.put(player.getName(), new ArrayList()); - player.sendMessage("Sign detection begun, punch the signs to add them to the list."); - return true; - } else if(args[0].equalsIgnoreCase("end")) { - if(cache.containsKey(player.getName())) { - protections.clear(); // clear protections so things are recached - // now process adding to the thing and update all the heads, hurrah! - List blocks = this.cache.remove(player.getName()); - String filter = this.filter.remove(player.getName()); - // put to file and update - Location[] l = new Location[blocks.size()]; - l = blocks.toArray(l); - this.headFile.addSign(new HeadSign(l, filter)); - player.sendMessage("Sign detection ended, updating signs..."); - headFile.thread.updateHeads(); - return true; - } else { - return false; - } - } - return false; - } - - @EventHandler - public void onPlayerInteract(PlayerInteractEvent event) { - if(!headFile.enabled) return; - // filter out non-clicked block events - if(event.getAction().equals(Action.LEFT_CLICK_AIR) || event.getAction().equals(Action.RIGHT_CLICK_AIR) || event.getAction().equals(Action.PHYSICAL)) { - return; - } - if(!(event.getClickedBlock().getState() instanceof Sign)) { - return; - } - if(event.getPlayer().hasPermission("buycraft.admin") || event.getPlayer().hasPermission("buycraft.signs")) { - if(this.cache.containsKey(event.getPlayer().getName())) { - // cancel event - event.setCancelled(true); - // that way they can't break the block - List locations = cache.get(event.getPlayer().getName()); - Location l = event.getClickedBlock().getLocation(); - boolean add = true; - // locations.contains(l); - for(Location loc : locations) { - if(loc.getWorld().getName().equals(l.getWorld().getName()) && - loc.getBlockX() == l.getBlockX() && - loc.getBlockY() == l.getBlockY() && - loc.getBlockZ() == l.getBlockZ()) { - add = false; - } - } - if(add) { - locations.add(l); - event.getPlayer().sendMessage("Sign added to list!"); - } else { - event.getPlayer().sendMessage("Sign already in the list!"); - } - } - } else if(isProtected(event.getClickedBlock())) { - event.setCancelled(true); - event.getPlayer().sendMessage(ChatColor.RED+"That block is protected!"); - } - } - - @EventHandler - public void onBlockPlace(BlockPlaceEvent event) { - if(!headFile.enabled) return; - if(event.getPlayer().hasPermission("buycraft.admin") || event.getPlayer().hasPermission("buycraft.signs")) { - // do nothing - } else if(isProtected(event.getBlockPlaced())) { - event.setCancelled(true); - event.getPlayer().sendMessage(ChatColor.RED+"That block is protected!"); - } - } - - @EventHandler - public void onBlockBreak(BlockBreakEvent event) { - if(!headFile.enabled) return; - if(event.getPlayer().hasPermission("buycraft.admin") || event.getPlayer().hasPermission("buycraft.signs")) { - // do nothing - } else if(isProtected(event.getBlock())) { - event.setCancelled(true); - event.getPlayer().sendMessage(ChatColor.RED+"That block is protected!"); - } - } -} diff --git a/src/net/buycraft/heads/HeadSign.java b/src/net/buycraft/heads/HeadSign.java deleted file mode 100644 index 6ca08b4..0000000 --- a/src/net/buycraft/heads/HeadSign.java +++ /dev/null @@ -1,89 +0,0 @@ -package net.buycraft.heads; - -import java.util.ArrayList; - -import net.buycraft.Plugin; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.json.JSONArray; -import org.json.JSONException; - -public class HeadSign { - - private final Location[] loc; - private final String filter; - - public HeadSign(Location[] loc, String filter) { - this.loc = loc; - this.filter = filter; - } - - public String getFilter() { - return filter; - } - - public Location getLocation(int i) { - return loc[i]; - } - - public Location[] getLocation() { - return loc; - } - - public String serialize() { - JSONArray arr = new JSONArray(); - // put "null" as the String into the serialization - if(filter == null) { - arr.put("null"); - } else { - arr.put(filter); - } - for(Location loc : this.loc) { - arr.put(getLocation(loc)); - } - return arr.toString(); - } - - public static HeadSign deserialize(String json) throws JSONException { - JSONArray arr = new JSONArray(json); - String filter = arr.getString(0); - if(filter.equalsIgnoreCase("null")) { - filter = null; - } - ArrayList loc = new ArrayList(arr.length()-1); - for(int i=1; i headList = new ArrayList(); - - private HeadFile headFile = null; - - public HeadThread(HeadFile headFile) { - this.headFile = headFile; - Bukkit.getScheduler().runTaskAsynchronously(headFile.plugin, this); - } - - public void run() { - update(); - } - - public void update() { - List headList = new ArrayList(); - try { - // repopulate headList - JSONObject apiResponse = Plugin.getInstance().getApi().paymentsAction(100, false, ""); - - if (apiResponse != null && apiResponse.getInt("code") == 0) { - JSONArray entries = apiResponse.getJSONArray("payload"); - // now iterate through the payload, if there is any - if(entries != null && entries.length() > 0) { - for(int i=0; i 0) { - packages = new String[p.length()]; - for(int j=0; j signs = headFile.getSigns(); - List heads = getHeads(); - for(HeadSign sign : signs) { - // use this to set the HeadSign - List head = new ArrayList(); - if(sign.getFilter() == null) { - head.addAll(heads); - } else { - for(int i=0; i getHeads() { - return new ArrayList(headList); - } - -} diff --git a/src/net/buycraft/packages/ItemParser.java b/src/net/buycraft/packages/ItemParser.java deleted file mode 100644 index 773d029..0000000 --- a/src/net/buycraft/packages/ItemParser.java +++ /dev/null @@ -1,162 +0,0 @@ -package net.buycraft.packages; - -import java.util.ArrayList; - -import net.buycraft.Plugin; - -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -public class ItemParser { - - private static final ItemStack nextPage; - private static final ItemStack previousPage; - private static final ItemStack homePage; - - static - { - nextPage = new ItemStack(Material.MAP); - previousPage = new ItemStack(Material.MAP); - homePage = new ItemStack(Material.MAP); - - setDisplayName(nextPage, ChatColor.LIGHT_PURPLE + Plugin.getInstance().getLanguage().getString("nextPage")); - setDisplayName(previousPage, ChatColor.LIGHT_PURPLE + Plugin.getInstance().getLanguage().getString("previousPage")); - setDisplayName(homePage, ChatColor.LIGHT_PURPLE + Plugin.getInstance().getLanguage().getString("homePage")); - } - - private ItemParser() {} - - public static PackageCategory getCategory(ItemStack item) { - int index = Plugin.getInstance().getLanguage().getString("packageId").length() + 6; - String idStr = item.getItemMeta().getLore().get(0).substring(index); - return Plugin.getInstance().getPackageManager().getPackageCategoryByNiceId(Integer.valueOf(idStr)); - } - - public static ItemStack getCategoryItem(PackageCategory c) { - ItemStack item = new ItemStack(c.getGuiItem() != null ? c.getGuiItem() : Material.BOOK); - - setDisplayName(item, ChatColor.LIGHT_PURPLE + c.getName()); - setLore(item, c.getDescription(), new String[] { - ChatColor.YELLOW + Plugin.getInstance().getLanguage().getString("packageId") + ": " + ChatColor.LIGHT_PURPLE + c.getNiceId(), - null, null, null - }); - - return item; - } - - public static int getPackage(ItemStack item) { - int index = Plugin.getInstance().getLanguage().getString("packageId").length() + 6; - String idStr = item.getItemMeta().getLore().get(0).substring(index); - return Integer.valueOf(idStr); - } - - public static ItemStack getPackageItem(PackageModal p) { - ItemStack item = new ItemStack(p.getMaterial() != null ? p.getMaterial() : Material.PAPER); - - setDisplayName(item, ChatColor.LIGHT_PURPLE + p.getName()); - setLore(item, p.getDescription(), new String[] { - ChatColor.YELLOW + Plugin.getInstance().getLanguage().getString("packageId") + ": " + ChatColor.LIGHT_PURPLE + p.getOrder(), - ChatColor.YELLOW + Plugin.getInstance().getLanguage().getString("packagePrice") + ": " + ChatColor.LIGHT_PURPLE + p.getPrice() + " " + Plugin.getInstance().getServerCurrency(), - null, null - }); - - return item; - } - - public static ItemStack getNextPage() { - return nextPage.clone(); - } - - public static ItemStack getPreviousPage() { - return previousPage.clone(); - } - - public static ItemStack getHomePage() { - return homePage.clone(); - } - - private static void setDisplayName(ItemStack item, String name) { - ItemMeta meta = item.getItemMeta(); - meta.setDisplayName(name); - item.setItemMeta(meta); - } - - private static void fillLore(String description, String[] lore) { - int loreI = 0; - while (loreI < 4 && lore[++loreI] != null); - - int maxLineLength = description.length() / (4 - loreI); - - while (loreI < 4) { - // Find the first space - int index = description.indexOf(' '); - - // If there are no more spaces we just set the string - if (index == -1 || description.length() < maxLineLength) { - lore[loreI] = description; - return; - } - // Find the next part of the string we can cut off - while ((index = description.indexOf(' ', index + 1)) < maxLineLength) { - if (index == -1) { - index = description.lastIndexOf(' '); - break; - } - } - - if (loreI == 3 && description.indexOf(' ', index + 1) == -1) { - lore[loreI++] = description; - return; - } - - lore[loreI++] = description.substring(0, index); - description = description.substring(index + 1); - } - } - - private static void setLore(ItemStack item, String description, String[] lines) { - ItemMeta meta = item.getItemMeta(); - if (description != null) { - fillLore(description, lines); - } - ArrayList lore = new ArrayList(lines.length); - for (String str : lines) { - if (str != null) { - lore.add(str); - } - } - meta.setLore(lore); - item.setItemMeta(meta); - } - - public enum ItemType { - NEXT, - PREV, - HOME, - OTHER; - - public static ItemType checkType(ItemStack item) { - if (!item.hasItemMeta()) { - return null; - } - - ItemMeta meta = item.getItemMeta(); - if (!meta.hasDisplayName()) { - return null; - } - - String name = meta.getDisplayName(); - - if (name.equals(nextPage.getItemMeta().getDisplayName())) - return NEXT; - if (name.equals(previousPage.getItemMeta().getDisplayName())) - return PREV; - if (name.equals(homePage.getItemMeta().getDisplayName())) - return HOME; - return OTHER; - } - } - -} diff --git a/src/net/buycraft/packages/PackageCategory.java b/src/net/buycraft/packages/PackageCategory.java deleted file mode 100644 index 92fe51d..0000000 --- a/src/net/buycraft/packages/PackageCategory.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.buycraft.packages; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.bukkit.Material; - -public class PackageCategory { - - private final List packages; - protected int niceId; - private final int id; - private final String name; - private final String description; - private final Material guiItem; - - @SuppressWarnings("deprecation") - public PackageCategory(int id, String name, String description, int guiItemId) { - this.packages = new ArrayList(1); - this.id = id; - this.name = name; - this.description = description != null && description.length() > 0 ? description : null; - this.guiItem = Material.getMaterial(guiItemId); - } - - public int getNiceId() { - return niceId; - } - - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public Material getGuiItem() { - return guiItem; - } - - protected void addPackage(PackageModal p) { - packages.add(p); - } - - public List getPackages() { - return Collections.unmodifiableList(packages); - } -} diff --git a/src/net/buycraft/packages/PackageManager.java b/src/net/buycraft/packages/PackageManager.java deleted file mode 100755 index b6c0172..0000000 --- a/src/net/buycraft/packages/PackageManager.java +++ /dev/null @@ -1,87 +0,0 @@ -package net.buycraft.packages; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -public class PackageManager { - private List packageCategories; - private List packagesForSale; - - public PackageManager() { - this.packageCategories = new ArrayList(); - this.packagesForSale = new ArrayList(); - } - - public synchronized void addCategory(int categoryId, String name, String description, int guiItemId) { - packageCategories.add(new PackageCategory(categoryId, name, description, guiItemId)); - } - - public synchronized void add(int categoryId, int id, int materialId, String name, String description, String price) { - PackageCategory category = getPackageCategory(categoryId); - packagesForSale.add(new PackageModal(category, id, materialId, name, description, price, packagesForSale.size() + 1)); - } - - public synchronized void cleanCategories() { - int nextId = 1; - Iterator it = packageCategories.iterator(); - while (it.hasNext()) { - PackageCategory p = it.next(); - if (p.getPackages().isEmpty()) { - it.remove(); - } else { - p.niceId = p.getId() != 0 ? nextId++ : 0; - } - } - } - - public synchronized List getCategories() { - return Collections.unmodifiableList(packageCategories); - } - - public synchronized PackageCategory getPackageCategory(int categoryId) { - for (PackageCategory c : packageCategories) { - if (c.getId() == categoryId) - return c; - } - return null; - } - - public synchronized PackageCategory getPackageCategoryByNiceId(int categoryId) { - for (PackageCategory c : packageCategories) { - if (c.getNiceId() == categoryId) - return c; - } - return null; - } - - public synchronized List getPackagesForSale() { - return Collections.unmodifiableList(packagesForSale); - } - - public synchronized PackageModal getPackageById(int packageId) { - for (PackageModal packageModel : packagesForSale) { - if (packageModel.getId() == packageId) { - return packageModel; - } - } - - return null; - } - - public synchronized PackageModal getPackageByOrderId(int orderId) { - for (PackageModal packageModel : packagesForSale) { - if (packageModel.getOrder() == orderId) { - return packageModel; - } - } - - return null; - } - - public synchronized void reset() { - packageCategories.clear(); - packagesForSale.clear(); - } -} diff --git a/src/net/buycraft/packages/PackageModal.java b/src/net/buycraft/packages/PackageModal.java deleted file mode 100644 index ca7d8e4..0000000 --- a/src/net/buycraft/packages/PackageModal.java +++ /dev/null @@ -1,56 +0,0 @@ -package net.buycraft.packages; - -import org.bukkit.Material; - -public class PackageModal { - - private final PackageCategory category; - private final int id; - private final Material material; - private final String name; - private final String description; - private final String price; - private final int order; - - @SuppressWarnings("deprecation") - public PackageModal(PackageCategory category, int id, int itemId, String name, String description, String price, int order) { - this.category = category; - this.id = id; - this.material = Material.getMaterial(itemId); - this.name = name; - this.description = description != null && description.length() > 0 ? description : null; - this.price = price; - this.order = order; - - if (category != null) - category.addPackage(this); - } - - public PackageCategory getCategory() { - return category; - } - - public int getId() { - return id; - } - - public Material getMaterial() { - return material; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public String getPrice() { - return price; - } - - public int getOrder() { - return order; - } -} diff --git a/src/net/buycraft/tasks/ReloadPackagesTask.java b/src/net/buycraft/tasks/ReloadPackagesTask.java deleted file mode 100644 index 3822f6e..0000000 --- a/src/net/buycraft/tasks/ReloadPackagesTask.java +++ /dev/null @@ -1,65 +0,0 @@ -package net.buycraft.tasks; - -import java.util.logging.Level; - -import net.buycraft.Plugin; -import net.buycraft.api.ApiTask; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -public class ReloadPackagesTask extends ApiTask { - Plugin plugin; - - public static void call() { - Plugin.getInstance().addTask(new ReloadPackagesTask()); - } - - private ReloadPackagesTask() { - this.plugin = Plugin.getInstance(); - } - - public void run() { - plugin.getPackageManager().reset(); - - try { - JSONObject categoriesResponse = plugin.getApi().categoriesAction(); - JSONObject packagesResponse = plugin.getApi().packagesAction(); - - if (categoriesResponse == null || categoriesResponse.getInt("code") != 0 || packagesResponse == null || packagesResponse.getInt("code") != 0) { - plugin.getLogger().severe("No response/invalid key during package reload."); - return; - } - - JSONArray categories = categoriesResponse.getJSONArray("payload"); - JSONArray packages = packagesResponse.getJSONArray("payload"); - - for (int i = 0; i < categories.length(); ++i) { - JSONObject row = categories.getJSONObject(i); - plugin.getPackageManager().addCategory(row.isNull("id") ? 0 : row.getInt("id"), row.getString("name"), row.getString("shortDescription"), row.getInt("guiItemId")); - } - - for (int i = 0; i < packages.length(); i++) { - if (packages.isNull(i)) { - continue; - } - - JSONObject row = packages.getJSONObject(i); - int categoryId = row.isNull("category") ? 0 : row.getInt("category"); - plugin.getPackageManager().add(categoryId, row.getInt("id"), row.getInt("guiItemId"), row.get("name").toString(), row.getString("shortDescription"), row.get("price").toString()); - } - - plugin.getPackageManager().cleanCategories(); - plugin.getBuyUi().packagesReset(); - plugin.getLogger().info("Loaded " + packages.length() + " package(s) into the cache."); - - } catch (JSONException e) { - plugin.getLogger().severe("Failed to load packages due to JSON parse error."); - ReportTask.setLastException(e); - } catch (Exception e) { - plugin.getLogger().log(Level.SEVERE, "Error while resetting packages", e); - ReportTask.setLastException(e); - } - } -} diff --git a/src/net/buycraft/tasks/VisitLinkTask.java b/src/net/buycraft/tasks/VisitLinkTask.java deleted file mode 100644 index 8d83563..0000000 --- a/src/net/buycraft/tasks/VisitLinkTask.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.buycraft.tasks; - -import net.buycraft.Plugin; -import net.buycraft.api.ApiTask; -import net.buycraft.util.Chat; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - -public class VisitLinkTask extends ApiTask { - private String playerName; - private String URL; - - public static void call(Player player, String URL) { - Plugin.getInstance().addTask(new VisitLinkTask(player.getName(), URL)); - } - - private VisitLinkTask(String playerName, String URL) { - try { - this.playerName = playerName; - this.URL = URLEncoder.encode(URL, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - ReportTask.setLastException(e); - } - } - - public void run() { - Player player = Bukkit.getPlayerExact(playerName); - try { - JSONObject jsonResponse = getApi().urlAction(URL).getJSONObject("payload"); - - if (jsonResponse != null) { - if (jsonResponse.has("url") && !jsonResponse.isNull("url")) { - - if (player != null) { - player.sendMessage(new String[] {Chat.header(), Chat.seperator(), - Chat.seperator() + ChatColor.GREEN + getLanguage().getString("pleaseVisit") + ":", - Chat.seperator(), Chat.seperator() + jsonResponse.getString("url"), - Chat.seperator(), Chat.seperator() + ChatColor.RED + getLanguage().getString("turnChatBackOn"), Chat.seperator(), Chat.footer()}); - } - - disableChat(playerName); - - getLogger().info("Generated short URL " + jsonResponse.getString("url") + "."); - - return; - } else { - getLogger().severe(jsonResponse.getString("errormessage")); - } - } else { - getLogger().severe("HTTP request error during url shortening."); - } - } catch (JSONException e) { - getLogger().severe("JSON parsing error."); - ReportTask.setLastException(e); - } - - if (player != null) { - player.sendMessage(new String[] {Chat.header(), Chat.seperator(), - Chat.seperator() + ChatColor.RED + getLanguage().getString("urlError"), - Chat.seperator(), Chat.footer()}); - } - } -} \ No newline at end of file diff --git a/src/net/buycraft/ui/AbstractBuyUI.java b/src/net/buycraft/ui/AbstractBuyUI.java deleted file mode 100644 index be9fdad..0000000 --- a/src/net/buycraft/ui/AbstractBuyUI.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.buycraft.ui; - -import net.buycraft.Plugin; -import net.buycraft.packages.PackageModal; -import net.buycraft.tasks.VisitLinkTask; -import net.buycraft.util.Chat; - -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; - -public abstract class AbstractBuyUI implements BuyInterface { - - public boolean showPackage(Player player, int packageId) { - boolean packageExists = false; - PackageModal packageModel = null; - - for (PackageModal row : Plugin.getInstance().getPackageManager().getPackagesForSale()) { - if (row.getOrder() == packageId) { - packageExists = true; - packageModel = row; - break; - } - } - - if (packageExists == true) { - String buyNowLink = Plugin.getInstance().getServerStore() + "/checkout/packages?action=add&package=" + packageModel.getId() + "&ign=" + player.getName(); - - if (Plugin.getInstance().getSettings().getBoolean("directPay")) { - buyNowLink = Plugin.getInstance().getServerStore() + "/checkout/packages?popup=true&action=add&direct=true&package=" + packageModel.getId() + "&ign=" + player.getName(); - } - - VisitLinkTask.call(player, buyNowLink); - return true; - } else { - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + ChatColor.RED + Plugin.getInstance().getLanguage().getString("packageNotFound")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - return false; - } - } - -} diff --git a/src/net/buycraft/ui/BuyChatUI.java b/src/net/buycraft/ui/BuyChatUI.java deleted file mode 100644 index 2be6bac..0000000 --- a/src/net/buycraft/ui/BuyChatUI.java +++ /dev/null @@ -1,81 +0,0 @@ -package net.buycraft.ui; - -import java.util.List; - -import net.buycraft.Plugin; -import net.buycraft.packages.PackageModal; -import net.buycraft.util.Chat; - -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; - -public class BuyChatUI extends AbstractBuyUI { - - public void showCategoryPage(Player player, int pageNumber) { - pageNumber++; - } - - public void showPage(Player player, int categoryId, int pageNumber) { - // TODO implement categories - if (pageNumber == 0) - pageNumber = 1; - - List packages = Plugin.getInstance().getPackageManager().getPackagesForSale(); - - if (packages.size() == 0) { - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + ChatColor.RED + Plugin.getInstance().getLanguage().getString("noPackagesForSale")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - } else { - int pageCount = (int) Math.ceil(packages.size() / 3.0); - - int startingPoint = -3 + (3 * pageNumber); - int finishPoint = 0 + (3 * pageNumber); - - if (finishPoint > packages.size() || finishPoint < 3) finishPoint = packages.size(); - if (startingPoint > packages.size() || startingPoint < 0) startingPoint = packages.size(); - - List packagesToDisplay = packages.subList(startingPoint, finishPoint); - - if (packagesToDisplay.size() > 0) { - Plugin.getInstance().getChatManager().disableChat((Player) player); - - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + ChatColor.GREEN + Plugin.getInstance().getLanguage().getString("toPurchase") + " " + ChatColor.LIGHT_PURPLE + "/" + Plugin.getInstance().getBuyCommand() + " "); - - if (pageCount > 1) { - player.sendMessage(Chat.seperator() + ChatColor.GREEN + Plugin.getInstance().getLanguage().getString("howToNavigate") + " " + ChatColor.LIGHT_PURPLE + "/" + Plugin.getInstance().getBuyCommand() + " page <1-" + pageCount + ">"); - } - - player.sendMessage(Chat.seperator()); - - for (PackageModal row : packagesToDisplay) { - player.sendMessage(Chat.seperator() + ChatColor.YELLOW + Plugin.getInstance().getLanguage().getString("packageId") + ": " + ChatColor.LIGHT_PURPLE + row.getOrder()); - player.sendMessage(Chat.seperator() + ChatColor.YELLOW + Plugin.getInstance().getLanguage().getString("packageName") + ": " + ChatColor.LIGHT_PURPLE + row.getName()); - player.sendMessage(Chat.seperator() + ChatColor.YELLOW + Plugin.getInstance().getLanguage().getString("packagePrice") + ": " + ChatColor.LIGHT_PURPLE + row.getPrice() + ' ' + Plugin.getInstance().getServerCurrency()); - player.sendMessage(Chat.seperator()); - } - - player.sendMessage(Chat.seperator() + ChatColor.RED + Plugin.getInstance().getLanguage().getString("turnChatBackOn")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - } else { - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + ChatColor.RED + Plugin.getInstance().getLanguage().getString("pageNotFound")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - } - } - } - - public void packagesReset() { - } - - public void pluginReloaded() { - } - -} diff --git a/src/net/buycraft/ui/BuyInterface.java b/src/net/buycraft/ui/BuyInterface.java deleted file mode 100644 index 3c76483..0000000 --- a/src/net/buycraft/ui/BuyInterface.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.buycraft.ui; - -import org.bukkit.entity.Player; - -public interface BuyInterface { - public void showCategoryPage(Player player, int pageNumber); - - public void showPage(Player player, int categoryId, int pageNumber); - - public boolean showPackage(Player player, int packageId); - - public void packagesReset(); - - public void pluginReloaded(); -} diff --git a/src/net/buycraft/ui/BuyInventoryUI.java b/src/net/buycraft/ui/BuyInventoryUI.java deleted file mode 100644 index f96e4a6..0000000 --- a/src/net/buycraft/ui/BuyInventoryUI.java +++ /dev/null @@ -1,280 +0,0 @@ -package net.buycraft.ui; - -import java.util.HashMap; -import java.util.List; -import java.util.Map.Entry; - -import net.buycraft.Plugin; -import net.buycraft.packages.ItemParser; -import net.buycraft.packages.ItemParser.ItemType; -import net.buycraft.packages.PackageCategory; -import net.buycraft.util.BuycraftInventoryCreator; -import net.buycraft.util.Chat; -import net.buycraft.util.BuycraftInventoryCreator.BuycraftInventoryType; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; - -public class BuyInventoryUI extends AbstractBuyUI implements Listener, InventoryHolder { - - private int expectedInventoryHolderId = 0; - private HashMap buyMenus; - private HashMap menuKeys; - - private boolean useMainMenu = false; - - public BuyInventoryUI() { - // Register the listener - Bukkit.getPluginManager().registerEvents(this, Plugin.getInstance()); - } - - /** - * Listens for inventory clicks which occur within BuyCraft inventories - */ - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public synchronized void onInventoryClick(InventoryClickEvent event) { - Inventory inv = event.getInventory(); - // Check if the player is in a Buycraft Inventory - if (inv.getType() != InventoryType.CHEST || !(inv.getHolder() instanceof BuyMenuInventoryHolder) || !(event.getWhoClicked() instanceof Player)) { - return; - } - - event.setCancelled(true); - - BuyMenuInventoryHolder holder = (BuyMenuInventoryHolder) inv.getHolder(); - - // Check that the inventory is valid - if (holder.id != expectedInventoryHolderId) { - while (!inv.getViewers().isEmpty()) { - inv.getViewers().get(0).closeInventory(); - } - return; - } - - String key = menuKeys.get(event.getInventory().getName()); - - // Check the player is clicking inside our inventory - if (event.getRawSlot() >= inv.getSize()) { - return; - } - - // Find out what the player is doing - BuycraftInventoryType type = BuycraftInventoryType.getType(key); - key = type.stripType(key); - if (type == BuycraftInventoryType.MAIN_MENU) { - handleCategoryMenuClick(event, Integer.valueOf(key)); - } else { - int currentCategoryId = -1; - int currentPage = 0; - - int index = key.indexOf('-'); - if (index == -1) - currentPage = Integer.valueOf(key); - else - { - currentCategoryId = Integer.valueOf(key.substring(0, index)); - currentPage = Integer.valueOf(key.substring(index+1)); - } - handleCategoryViewClick(event, currentCategoryId, currentPage); - } - } - - private synchronized void handleCategoryMenuClick(InventoryClickEvent event, int currentPage) { - if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) { - return; - } - - ItemType t = ItemType.checkType(event.getCurrentItem()); - - // Clicking in an invalid place - if (t == null) { - return; - } - - if (t != ItemType.OTHER) { - currentPage += t == ItemType.NEXT ? 1 : -1; - showCategoryPage((Player) event.getWhoClicked(), currentPage); - return; - } - - // Show category items - PackageCategory c = ItemParser.getCategory(event.getCurrentItem()); - - // Invalid category - if (c == null) { - Plugin.getInstance().getLogger().severe("Failed to find PackageCategory shown in inventory"); - event.getInventory().setItem(event.getSlot(), null); - return; - } - - Player player = (Player) event.getWhoClicked(); - player.playSound(player.getLocation(), Sound.CLICK, 0.5f, 1f); - - event.getWhoClicked().closeInventory(); - showPage((Player) event.getWhoClicked(), c.getNiceId(), 1); - } - - private synchronized void handleCategoryViewClick(InventoryClickEvent event, int currentCategoryId, int currentPage) { - if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) { - return; - } - - Player player = (Player) event.getWhoClicked(); - player.playSound(player.getLocation(), Sound.CLICK, 0.5f, 1f); - - ItemType t = ItemType.checkType(event.getCurrentItem()); - - // Clicking in an invalid place - if (t == null) { - return; - } - - if (t == ItemType.HOME) { - event.getWhoClicked().closeInventory(); - showCategoryPage((Player) event.getWhoClicked(), 1); - return; - } - - if (t != ItemType.OTHER) { - currentPage += t == ItemType.NEXT ? 1 : -1; - event.getWhoClicked().closeInventory(); - showPage((Player) event.getWhoClicked(), currentCategoryId, currentPage); - return; - } - - int orderId = ItemParser.getPackage(event.getCurrentItem()); - - event.getWhoClicked().closeInventory(); - if (!showPackage((Player) event.getWhoClicked(), orderId)) { - // Invalid package - event.getInventory().setItem(event.getSlot(), null); - } - } - - public synchronized void showCategoryPage(Player player, int pageNumber) { - if (!useMainMenu) { - showPage(player, 0, 1); - return; - } - - if (!checkReady(player)) { - return; - } - - String key = BuycraftInventoryType.MAIN_MENU.toString() + pageNumber; - Inventory inv = buyMenus.get(key); - - if (inv == null) { - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + Plugin.getInstance().getLanguage().getString("pageNotFound")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - } else { - player.openInventory(inv); - } - } - - public synchronized void showPage(Player player, int categoryId, int pageNumber) { - if (!checkReady(player)) { - return; - } - - if (pageNumber == 0) { - if (useMainMenu) { - showCategoryPage(player, 1); - return; - } - pageNumber = 1; - } - - String key = BuycraftInventoryType.CATEGORY_MENU.toString() + categoryId + "-" + pageNumber; - Inventory inv = buyMenus.get(key); - - if (inv == null) { - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + Plugin.getInstance().getLanguage().getString("pageNotFound")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - } else { - player.openInventory(inv); - } - } - - public synchronized void packagesReset() { - HashMap newBuyMenus = new HashMap(); - - List categories = Plugin.getInstance().getPackageManager().getCategories(); - boolean useMainMenu = false; - BuyMenuInventoryHolder invHolder = new BuyMenuInventoryHolder(++expectedInventoryHolderId); - - if (categories.size() > 1) { - useMainMenu = true; - BuycraftInventoryCreator.createMainMenu(invHolder, newBuyMenus, categories); - } - - for (PackageCategory c : categories) { - BuycraftInventoryCreator.createPackagePages(invHolder, newBuyMenus, c, useMainMenu); - } - - HashMap newMenuKeys = new HashMap(newBuyMenus.size()); - - for (Entry e : newBuyMenus.entrySet()) { - newMenuKeys.put(e.getValue().getName(), e.getKey()); - } - - this.useMainMenu = useMainMenu; - this.menuKeys = newMenuKeys; - this.buyMenus = newBuyMenus; - } - - public synchronized void pluginReloaded() { - if (buyMenus != null) { - for (Inventory inv : buyMenus.values()) { - while (!inv.getViewers().isEmpty()) { - inv.getViewers().get(0).closeInventory(); - } - } - } - } - - public synchronized boolean checkReady(Player player) { - if (buyMenus == null) { - player.sendMessage(Chat.header()); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.seperator() + ChatColor.RED + Plugin.getInstance().getLanguage().getString("inventoryMenuNotReady")); - player.sendMessage(Chat.seperator()); - player.sendMessage(Chat.footer()); - return false; - } - return true; - } - - public Inventory getInventory() { - throw new UnsupportedOperationException("BuyInventoryUI does not support this method call"); - } - - private class BuyMenuInventoryHolder implements InventoryHolder { - public final int id; - - BuyMenuInventoryHolder(int id) { - this.id = id; - } - - public Inventory getInventory() { - return null; - } - } - -} diff --git a/src/net/buycraft/util/BuycraftInventoryCreator.java b/src/net/buycraft/util/BuycraftInventoryCreator.java deleted file mode 100644 index 52bd9f3..0000000 --- a/src/net/buycraft/util/BuycraftInventoryCreator.java +++ /dev/null @@ -1,154 +0,0 @@ -package net.buycraft.util; - -import java.util.List; -import java.util.Map; - -import net.buycraft.Plugin; -import net.buycraft.packages.ItemParser; -import net.buycraft.packages.PackageCategory; -import net.buycraft.packages.PackageModal; -import static net.buycraft.util.BuycraftInventoryCreator.BuycraftInventoryType.*; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; - -public class BuycraftInventoryCreator { - - private BuycraftInventoryCreator() {} - - public static void createMainMenu(InventoryHolder holder, Map inventories, List categories) { - int pageId = 1; - int itemsToBeMade = categories.size() + 1; - boolean globalMade = false; - String invTitle = Plugin.getInstance().getLanguage().getString("chooseACategory"); - for (int categoryI = 0; categoryI < categories.size(); ++categoryI) { - // Fetch the number of slots we need - int size = getSize(itemsToBeMade + (globalMade?0:1)); - - // Create the inventory - Inventory inv = createInventory(holder, size, pageId > 1 ? invTitle + " " + pageId : invTitle); - - // If we are adding any other buttons we need a free line of slots - if (itemsToBeMade + (globalMade?0:1) > size) { - size -= 9; - // Add random items to make sure we don't put packages on the first line - for (int i = 0; i < 9; ++i) { - inv.setItem(i, new ItemStack(Material.STONE)); - } - } - - // Fill empty slots of the inventory - for (; categoryI < categories.size(); ++categoryI) { - // When this returns a un-empty map the current view is full - if (!inv.addItem(ItemParser.getCategoryItem(categories.get(categoryI))).isEmpty()) { - break; - } - } - - // If we are adding any buttons we need to clear the top line of junk items - if (itemsToBeMade + (globalMade?0:1) > size) { - for (int i = 0; i < 9; ++i) { - inv.clear(i); - } - // Then add buttons - placePrevAndNextPage(inv, pageId > 1, itemsToBeMade > size, false); - } - itemsToBeMade -= size; - inventories.put(MAIN_MENU.toString() + pageId++, inv); - } - } - - public static void createPackagePages(InventoryHolder holder, Map inventories, PackageCategory category, boolean addHome) { - int pageId = 1; - List packages = category.getPackages(); - int itemsToBeMade = packages.size(); - for (int itemI = 0; itemI < packages.size(); ++itemI) { - // Fetch the number of slots we need - int size = getSize(itemsToBeMade + 9); - - // Create the inventory - Inventory inv = createInventory(holder, size, pageId > 1 ? category.getName() + " " + pageId : category.getName()); - - // If we are adding any other buttons we need a free line of slots - if (addHome || itemsToBeMade > size) { - size -= 9; - // Add random items to make sure we don't put packages on the first line - for (int i = 0; i < 9; ++i) { - inv.setItem(i, new ItemStack(Material.STONE)); - } - } - - // Fill empty slots of the inventory - for (; itemI < packages.size(); ++itemI) { - inv.addItem(ItemParser.getPackageItem(packages.get(itemI))); - } - - // If we are adding any buttons we need to clear the top line of junk items - if (addHome || itemsToBeMade > size) { - for (int i = 0; i < 9; ++i) { - inv.clear(i); - } - // Then add buttons - placePrevAndNextPage(inv, pageId > 1, itemsToBeMade > size, addHome); - } - itemsToBeMade -= size; - inventories.put(CATEGORY_MENU.toString() + (addHome ? category.getNiceId() : 0) + "-" + pageId++, inv); - } - } - - private static void placePrevAndNextPage(Inventory inventory, boolean prevPage, boolean nextPage, boolean homePage) { - if (prevPage) { - inventory.setItem(nextPage ? 7 : 8, ItemParser.getPreviousPage()); - } - if (nextPage) { - inventory.setItem(8, ItemParser.getNextPage()); - } - if (homePage) { - inventory.setItem(0, ItemParser.getHomePage()); - } - } - - private static Inventory createInventory(InventoryHolder holder, int size, String title) { - // Fetch the inventory title prefix - String prefix = Plugin.getInstance().getLanguage().getString("inventoryMenuPrefix") + " - "; - - if (prefix.length() + title.length() > 30) { - if (title.length() > 30) { - title = title.substring(0, 28) + ".."; - } - } else { - title = prefix + title; - } - - return Bukkit.createInventory(holder, size, ChatColor.DARK_RED + title); - } - - private static int getSize(int requiredSize) { - requiredSize = 9 * (requiredSize / 9 + (requiredSize % 9 > 0 ? 1 : 0)); - if (requiredSize > 6 * 9) { - requiredSize = 6 * 9; - } - return requiredSize; - } - - public enum BuycraftInventoryType { - MAIN_MENU, - CATEGORY_MENU; - - public String stripType(String key) { - return key.substring(toString().length()); - } - - public static BuycraftInventoryType getType(String key) { - for (BuycraftInventoryType t : values()) { - if (key.startsWith(t.toString())) - return t; - } - return null; - } - } -} diff --git a/src/net/buycraft/util/Updater.java b/src/net/buycraft/util/Updater.java deleted file mode 100644 index 70c2b0d..0000000 --- a/src/net/buycraft/util/Updater.java +++ /dev/null @@ -1,514 +0,0 @@ -/* - * Updater for Bukkit. - * - * This class provides the means to safely and easily update a plugin, or check to see if it is updated using dev.bukkit.org - */ - -package net.buycraft.util; - -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.Plugin; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; - -/** - * Check dev.bukkit.org to find updates for a given plugin, and download the updates if needed. - *

- * VERY, VERY IMPORTANT: Because there are no standards for adding auto-update toggles in your plugin's config, this system provides NO CHECK WITH YOUR CONFIG to make sure the user has allowed auto-updating. - *
- * It is a BUKKIT POLICY that you include a boolean value in your config that prevents the auto-updater from running AT ALL. - *
- * If you fail to include this option in your config, your plugin will be REJECTED when you attempt to submit it to dev.bukkit.org. - *

- * An example of a good configuration option would be something similar to 'auto-update: true' - if this value is set to false you may NOT run the auto-updater. - *
- * If you are unsure about these rules, please read the plugin submission guidelines: http://goo.gl/8iU5l - * - * @author Gravity - * @version 2.0 - */ - -public class Updater { - - private Plugin plugin; - private UpdateType type; - private String versionName; - private String versionLink; - private String versionType; - private String versionGameVersion; - - private boolean announce; // Whether to announce file downloads - - private URL url; // Connecting to RSS - private File file; // The plugin's file - private Thread thread; // Updater thread - - private int id = -1; // Project's Curse ID - private String apiKey = null; // BukkitDev ServerMods API key - private static final String TITLE_VALUE = "name"; // Gets remote file's title - private static final String LINK_VALUE = "downloadUrl"; // Gets remote file's download link - private static final String TYPE_VALUE = "releaseType"; // Gets remote file's release type - private static final String VERSION_VALUE = "gameVersion"; // Gets remote file's build version - private static final String QUERY = "/servermods/files?projectIds="; // Path to GET - private static final String HOST = "https://api.curseforge.com"; // Slugs will be appended to this to get to the project's RSS feed - - private static final String[] NO_UPDATE_TAG = { "-DEV", "-PRE", "-SNAPSHOT" }; // If the version number contains one of these, don't update. - private static final int BYTE_SIZE = 1024; // Used for downloading files - private YamlConfiguration config; // Config file - private String updateFolder;// The folder that downloads will be placed in - private Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; // Used for determining the outcome of the update process - - /** - * Gives the dev the result of the update process. Can be obtained by called getResult(). - */ - public enum UpdateResult { - /** - * The updater found an update, and has readied it to be loaded the next time the server restarts/reloads. - */ - SUCCESS, - /** - * The updater did not find an update, and nothing was downloaded. - */ - NO_UPDATE, - /** - * The server administrator has disabled the updating system - */ - DISABLED, - /** - * The updater found an update, but was unable to download it. - */ - FAIL_DOWNLOAD, - /** - * For some reason, the updater was unable to contact dev.bukkit.org to download the file. - */ - FAIL_DBO, - /** - * When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'. - */ - FAIL_NOVERSION, - /** - * The id provided by the plugin running the updater was invalid and doesn't exist on DBO. - */ - FAIL_BADID, - /** - * The server administrator has improperly configured their API key in the configuration - */ - FAIL_APIKEY, - /** - * The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded. - */ - UPDATE_AVAILABLE - } - - /** - * Allows the dev to specify the type of update that will be run. - */ - public enum UpdateType { - /** - * Run a version check, and then if the file is out of date, download the newest version. - */ - DEFAULT, - /** - * Don't run a version check, just find the latest update and download it. - */ - NO_VERSION_CHECK, - /** - * Get information about the version and the download size, but don't actually download anything. - */ - NO_DOWNLOAD - } - - /** - * Initialize the updater - * - * @param plugin The plugin that is checking for an update. - * @param id The dev.bukkit.org id of the project - * @param file The file that the plugin is running from, get this by doing this.getFile() from within your main class. - * @param type Specify the type of update this will be. See {@link UpdateType} - * @param announce True if the program should announce the progress of new updates in console - */ - public Updater(Plugin plugin, int id, File file, UpdateType type, boolean announce) { - this.plugin = plugin; - this.type = type; - this.announce = announce; - this.file = file; - this.id = id; - this.updateFolder = plugin.getServer().getUpdateFolder(); - - final File pluginFile = plugin.getDataFolder().getParentFile(); - final File updaterFile = new File(pluginFile, "Updater"); - final File updaterConfigFile = new File(updaterFile, "config.yml"); - - if (!updaterFile.exists()) { - updaterFile.mkdir(); - } - if (!updaterConfigFile.exists()) { - try { - updaterConfigFile.createNewFile(); - } catch (final IOException e) { - plugin.getLogger().severe("The updater could not create a configuration in " + updaterFile.getAbsolutePath()); - e.printStackTrace(); - } - } - this.config = YamlConfiguration.loadConfiguration(updaterConfigFile); - - this.config.options().header("This configuration file affects all plugins using the Updater system (version 2+ - http://forums.bukkit.org/threads/96681/ )" + '\n' - + "If you wish to use your API key, read http://wiki.bukkit.org/ServerMods_API and place it below." + '\n' - + "Some updating systems will not adhere to the disabled value, but these may be turned off in their plugin's configuration."); - this.config.addDefault("api-key", "PUT_API_KEY_HERE"); - this.config.addDefault("disable", false); - - if (this.config.get("api-key", null) == null) { - this.config.options().copyDefaults(true); - try { - this.config.save(updaterConfigFile); - } catch (final IOException e) { - plugin.getLogger().severe("The updater could not save the configuration in " + updaterFile.getAbsolutePath()); - e.printStackTrace(); - } - } - - if (this.config.getBoolean("disable")) { - this.result = UpdateResult.DISABLED; - return; - } - - String key = this.config.getString("api-key"); - if (key.equalsIgnoreCase("PUT_API_KEY_HERE") || key.equals("")) { - key = null; - } - - this.apiKey = key; - - try { - this.url = new URL(Updater.HOST + Updater.QUERY + id); - } catch (final MalformedURLException e) { - plugin.getLogger().severe("The project ID provided for updating, " + id + " is invalid."); - this.result = UpdateResult.FAIL_BADID; - e.printStackTrace(); - } - - this.thread = new Thread(new UpdateRunnable()); - this.thread.start(); - } - - /** - * Get the result of the update process. - */ - public Updater.UpdateResult getResult() { - this.waitForThread(); - return this.result; - } - - /** - * Get the latest version's release type (release, beta, or alpha). - */ - public String getLatestType() { - this.waitForThread(); - return this.versionType; - } - - /** - * Get the latest version's game version. - */ - public String getLatestGameVersion() { - this.waitForThread(); - return this.versionGameVersion; - } - - /** - * Get the latest version's name. - */ - public String getLatestName() { - this.waitForThread(); - return this.versionName; - } - - /** - * Get the latest version's file link. - */ - public String getLatestFileLink() { - this.waitForThread(); - return this.versionLink; - } - - /** - * As the result of Updater output depends on the thread's completion, it is necessary to wait for the thread to finish - * before allowing anyone to check the result. - */ - private void waitForThread() { - if ((this.thread != null) && this.thread.isAlive()) { - try { - this.thread.join(); - } catch (final InterruptedException e) { - e.printStackTrace(); - } - } - } - - /** - * Save an update from dev.bukkit.org into the server's update folder. - */ - private void saveFile(File folder, String file, String u) { - if (!folder.exists()) { - folder.mkdir(); - } - BufferedInputStream in = null; - FileOutputStream fout = null; - try { - // Download the file - final URL url = new URL(u); - final int fileLength = url.openConnection().getContentLength(); - in = new BufferedInputStream(url.openStream()); - fout = new FileOutputStream(folder.getAbsolutePath() + "/" + file); - - final byte[] data = new byte[Updater.BYTE_SIZE]; - int count; - if (this.announce) { - this.plugin.getLogger().info("About to download a new update: " + this.versionName); - } - long downloaded = 0; - while ((count = in.read(data, 0, Updater.BYTE_SIZE)) != -1) { - downloaded += count; - fout.write(data, 0, count); - final int percent = (int) ((downloaded * 100) / fileLength); - if (this.announce && ((percent % 10) == 0)) { - this.plugin.getLogger().info("Downloading update: " + percent + "% of " + fileLength + " bytes."); - } - } - //Just a quick check to make sure we didn't leave any files from last time... - for (final File xFile : new File(this.plugin.getDataFolder().getParent(), this.updateFolder).listFiles()) { - if (xFile.getName().endsWith(".zip")) { - xFile.delete(); - } - } - // Check to see if it's a zip file, if it is, unzip it. - final File dFile = new File(folder.getAbsolutePath() + "/" + file); - if (dFile.getName().endsWith(".zip")) { - // Unzip - this.unzip(dFile.getCanonicalPath()); - } - if (this.announce) { - this.plugin.getLogger().info("Finished updating."); - } - } catch (final Exception ex) { - this.plugin.getLogger().warning("The auto-updater tried to download a new update, but was unsuccessful."); - this.result = Updater.UpdateResult.FAIL_DOWNLOAD; - } finally { - try { - if (in != null) { - in.close(); - } - if (fout != null) { - fout.close(); - } - } catch (final Exception ex) { - } - } - } - - /** - * Part of Zip-File-Extractor, modified by Gravity for use with Bukkit - */ - private void unzip(String file) { - try { - final File fSourceZip = new File(file); - final String zipPath = file.substring(0, file.length() - 4); - ZipFile zipFile = new ZipFile(fSourceZip); - Enumeration e = zipFile.entries(); - while (e.hasMoreElements()) { - ZipEntry entry = e.nextElement(); - File destinationFilePath = new File(zipPath, entry.getName()); - destinationFilePath.getParentFile().mkdirs(); - if (entry.isDirectory()) { - continue; - } else { - final BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry)); - int b; - final byte buffer[] = new byte[Updater.BYTE_SIZE]; - final FileOutputStream fos = new FileOutputStream(destinationFilePath); - final BufferedOutputStream bos = new BufferedOutputStream(fos, Updater.BYTE_SIZE); - while ((b = bis.read(buffer, 0, Updater.BYTE_SIZE)) != -1) { - bos.write(buffer, 0, b); - } - bos.flush(); - bos.close(); - bis.close(); - final String name = destinationFilePath.getName(); - if (name.endsWith(".jar") && this.pluginFile(name)) { - destinationFilePath.renameTo(new File(this.plugin.getDataFolder().getParent(), this.updateFolder + "/" + name)); - } - } - entry = null; - destinationFilePath = null; - } - e = null; - zipFile.close(); - zipFile = null; - - // Move any plugin data folders that were included to the right place, Bukkit won't do this for us. - for (final File dFile : new File(zipPath).listFiles()) { - if (dFile.isDirectory()) { - if (this.pluginFile(dFile.getName())) { - final File oFile = new File(this.plugin.getDataFolder().getParent(), dFile.getName()); // Get current dir - final File[] contents = oFile.listFiles(); // List of existing files in the current dir - for (final File cFile : dFile.listFiles()) // Loop through all the files in the new dir - { - boolean found = false; - for (final File xFile : contents) // Loop through contents to see if it exists - { - if (xFile.getName().equals(cFile.getName())) { - found = true; - break; - } - } - if (!found) { - // Move the new file into the current dir - cFile.renameTo(new File(oFile.getCanonicalFile() + "/" + cFile.getName())); - } else { - // This file already exists, so we don't need it anymore. - cFile.delete(); - } - } - } - } - dFile.delete(); - } - new File(zipPath).delete(); - fSourceZip.delete(); - } catch (final IOException ex) { - this.plugin.getLogger().warning("The auto-updater tried to unzip a new update file, but was unsuccessful."); - this.result = Updater.UpdateResult.FAIL_DOWNLOAD; - ex.printStackTrace(); - } - new File(file).delete(); - } - - /** - * Check if the name of a jar is one of the plugins currently installed, used for extracting the correct files out of a zip. - */ - private boolean pluginFile(String name) { - for (final File file : new File("plugins").listFiles()) { - if (file.getName().equals(name)) { - return true; - } - } - return false; - } - - /** - * Check to see if the program should continue by evaluation whether the plugin is already updated, or shouldn't be updated - */ - private boolean versionCheck(String title) { - if (this.type != UpdateType.NO_VERSION_CHECK) { - final String version = this.plugin.getDescription().getVersion(); - if (title.split(" v").length == 2) { - final String remoteVersion = title.split(" v")[1].split(" ")[0]; // Get the newest file's version number - - if (this.hasTag(version) || version.equalsIgnoreCase(remoteVersion)) { - // We already have the latest version, or this build is tagged for no-update - this.result = Updater.UpdateResult.NO_UPDATE; - return false; - } - } else { - // The file's name did not contain the string 'vVersion' - final String authorInfo = this.plugin.getDescription().getAuthors().size() == 0 ? "" : " (" + this.plugin.getDescription().getAuthors().get(0) + ")"; - this.plugin.getLogger().warning("The author of this plugin" + authorInfo + " has misconfigured their Auto Update system"); - this.plugin.getLogger().warning("File versions should follow the format 'PluginName vVERSION'"); - this.plugin.getLogger().warning("Please notify the author of this error."); - this.result = Updater.UpdateResult.FAIL_NOVERSION; - return false; - } - } - return true; - } - - /** - * Evaluate whether the version number is marked showing that it should not be updated by this program - */ - private boolean hasTag(String version) { - for (final String string : Updater.NO_UPDATE_TAG) { - if (version.contains(string)) { - return true; - } - } - return false; - } - - private boolean read() { - try { - final URLConnection conn = this.url.openConnection(); - conn.setConnectTimeout(5000); - - if (this.apiKey != null) { - conn.addRequestProperty("X-API-Key", this.apiKey); - } - conn.addRequestProperty("User-Agent", "Updater (by Gravity)"); - - conn.setDoOutput(true); - - final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); - final String response = reader.readLine(); - - final JSONArray array = (JSONArray) JSONValue.parse(response); - - if (array.size() == 0) { - this.plugin.getLogger().warning("The updater could not find any files for the project id " + this.id); - this.result = UpdateResult.FAIL_BADID; - return false; - } - - this.versionName = (String) ((JSONObject) array.get(array.size() - 1)).get(Updater.TITLE_VALUE); - this.versionLink = (String) ((JSONObject) array.get(array.size() - 1)).get(Updater.LINK_VALUE); - this.versionType = (String) ((JSONObject) array.get(array.size() - 1)).get(Updater.TYPE_VALUE); - this.versionGameVersion = (String) ((JSONObject) array.get(array.size() - 1)).get(Updater.VERSION_VALUE); - - return true; - } catch (final IOException e) { - if (e.getMessage().contains("HTTP response code: 403")) { - this.plugin.getLogger().warning("dev.bukkit.org rejected the API key provided in plugins/Updater/config.yml"); - this.plugin.getLogger().warning("Please double-check your configuration to ensure it is correct."); - this.result = UpdateResult.FAIL_APIKEY; - } else { - this.plugin.getLogger().warning("The updater could not contact dev.bukkit.org for updating."); - this.plugin.getLogger().warning("If you have not recently modified your configuration and this is the first time you are seeing this message, the site may be experiencing temporary downtime."); - this.result = UpdateResult.FAIL_DBO; - } - e.printStackTrace(); - return false; - } - } - - private class UpdateRunnable implements Runnable { - - public void run() { - if (Updater.this.url != null) { - // Obtain the results of the project's file feed - if (Updater.this.read()) { - if (Updater.this.versionCheck(Updater.this.versionName)) { - if ((Updater.this.versionLink != null) && (Updater.this.type != UpdateType.NO_DOWNLOAD)) { - String name = Updater.this.file.getName(); - // If it's a zip file, it shouldn't be downloaded as the plugin's name - if (Updater.this.versionLink.endsWith(".zip")) { - final String[] split = Updater.this.versionLink.split("/"); - name = split[split.length - 1]; - } - Updater.this.saveFile(new File(Updater.this.plugin.getDataFolder().getParent(), Updater.this.updateFolder), name, Updater.this.versionLink); - } else { - Updater.this.result = UpdateResult.UPDATE_AVAILABLE; - } - } - } - } - } - } -} \ No newline at end of file