commit fbe0a03a8f688f7528b41b85da160ea6a6080f18 Author: extendedclip Date: Tue Mar 20 19:04:14 2018 -0400 2.8.5 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1451c9c --- /dev/null +++ b/pom.xml @@ -0,0 +1,71 @@ + + 4.0.0 + me.clip + placeholderapi + + 2.8.5 + PlaceholderAPI + An awesome placeholder plugin + http://extendedclip.com + + + UTF-8 + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + sonatype + https://oss.sonatype.org/content/repositories/snapshots/ + + + + + + org.spigotmc + spigot-api + 1.12.2-R0.1-SNAPSHOT + provided + + + + + clean package + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + true + + + + + + + + src/main/resources + true + + + + \ No newline at end of file diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java new file mode 100644 index 0000000..816a14a --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java @@ -0,0 +1,297 @@ +package me.clip.placeholderapi; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import me.clip.placeholderapi.events.PlaceholderHookUnloadEvent; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.expansion.Relational; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +public class PlaceholderAPI { + + private PlaceholderAPI() { + } + + private final static Pattern PLACEHOLDER_PATTERN = Pattern.compile("[%]([^%]+)[%]"); + private final static Pattern BRACKET_PLACEHOLDER_PATTERN = Pattern.compile("[{]([^{}]+)[}]"); + private final static Pattern RELATIONAL_PLACEHOLDER_PATTERN = Pattern.compile("[%](rel_)([^%]+)[%]"); + private final static Map placeholders = new HashMap<>(); + + /** + * unregister ALL placeholder hooks that are currently registered + */ + protected static void unregisterAll() { + unregisterAllExpansions(); + placeholders.clear(); + } + + /** + * unregister all expansions + */ + public static void unregisterAllExpansions() { + if (placeholders.isEmpty()) return; + getPlaceholders().forEach((key, value) -> { + if (value instanceof PlaceholderExpansion) { + Bukkit.getPluginManager().callEvent(new PlaceholderHookUnloadEvent(key, value)); + unregisterPlaceholderHook(key); + } + }); + } + + /** + * check if a specific placeholder identifier is currently registered + * @param identifier to check + * @return true if identifier is already registered + */ + public static boolean isRegistered(String identifier) { + return !placeholders.isEmpty() && getRegisteredIdentifiers().stream().filter(id -> id.equalsIgnoreCase(identifier)).findFirst().orElse(null) != null; + } + + /** + * Register a new placeholder hook + * @param identifier Identifier of the placeholder -> "%(identifier)_(args...)% + * + * @param placeholderHook implementing class that contains the onPlaceholderRequest method which is called when a value is needed for the specific placeholder + * @return true if the hook was successfully registered, false if there is already a hook registered for the specified identifier + */ + public static boolean registerPlaceholderHook(String identifier, PlaceholderHook placeholderHook) { + Validate.notNull(identifier, "Identifier can not be null"); + Validate.notNull(placeholderHook, "Placeholderhook can not be null"); + if (isRegistered(identifier)) return false; + //test + placeholders.put(identifier.toLowerCase(), placeholderHook); + return true; + } + + /** + * unregister a placeholder hook by identifier + * @param identifier the identifier for the placeholder hook to unregister + * @return true if the placeholder hook was successfully unregistered, false if there was no placeholder hook registered for the identifier specified + */ + public static boolean unregisterPlaceholderHook(String identifier) { + Validate.notNull(identifier, "Identifier can not be null"); + return placeholders.remove(identifier.toLowerCase()) != null; + } + + /** + * Get all registered placeholder identifiers + * @return all registered placeholder identifiers + */ + public static Set getRegisteredIdentifiers() { + if (placeholders.isEmpty()) return new HashSet<>(); + return new HashSet<>(placeholders.keySet()); + } + + /** + * Get map of registered placeholders + * @return copy of the internal placeholder map + */ + public static Map getPlaceholders() { + return new HashMap<>(placeholders); + } + + /** + * check if a String contains any PlaceholderAPI placeholders + * @param text String to check + * @return true if String contains any registered placeholder identifiers, false otherwise + */ + public static boolean containsPlaceholders(String text) { + return text != null && PLACEHOLDER_PATTERN.matcher(text).find(); + } + + /** + * check if a String contains any PlaceholderAPI bracket placeholders + * @param text String to check + * @return true if String contains any registered placeholder identifiers, false otherwise + */ + public static boolean containsBracketPlaceholders(String text) { + return text != null && BRACKET_PLACEHOLDER_PATTERN.matcher(text).find(); + } + + /** + * set placeholders in the list text provided + * placeholders are matched with the pattern {} when set with this method + * @param p Player to parse the placeholders for + * @param text text to set the placeholder values in + * @return modified list with all placeholders set to the corresponding values + */ + public static List setBracketPlaceholders(Player p, List text) { + if (text == null) return null; + List temp = new ArrayList<>(); + text.forEach(line -> { + temp.add(setBracketPlaceholders(p, line)); + }); + return temp; + } + + /** + * set placeholders in the text specified + * placeholders are matched with the pattern {} when set with this method + * @param player Player to parse the placeholders for + * @param text text to parse the placeholder values to + * @return modified text with all placeholders set to the corresponding values + */ + public static String setBracketPlaceholders(Player player, String text) { + if (text == null) return null; + if (placeholders.isEmpty()) return colorize(text); + Matcher placeholderMatcher = BRACKET_PLACEHOLDER_PATTERN.matcher(text); + Map hooks = getPlaceholders(); + while (placeholderMatcher.find()) { + String format = placeholderMatcher.group(1); + int index = format.indexOf("_"); + if (index == -1 || index >= format.length()) continue; + String identifier = format.substring(0, index).toLowerCase(); + String params = format.substring(index+1); + if (hooks.containsKey(identifier)) { + String value = hooks.get(identifier).onPlaceholderRequest(player, params); + if (value != null) { + text = text.replaceAll("\\{"+format+"\\}", Matcher.quoteReplacement(value)); + } + } + } + return colorize(text); + } + + /** + * set placeholders in the list text provided + * placeholders are matched with the pattern %(identifier)_(params)>% when set with this method + * @param p Player to parse the placeholders for + * @param text text to parse the placeholder values in + * @return modified list with all placeholders set to the corresponding values + */ + public static List setPlaceholders(Player p, List text) { + if (text == null) return null; + List temp = new ArrayList<>(); + text.forEach(line -> { + temp.add(setPlaceholders(p, line)); + }); + return temp; + } + + /** + * set placeholders in the text specified + * placeholders are matched with the pattern %<(identifier)_(params)>% when set with this method + * @param player Player to parse the placeholders for + * @param text text to parse the placeholder values to + * @return text with all placeholders set to the corresponding values + */ + public static String setPlaceholders(Player player, String text) { + if (text == null) return null; + if (placeholders.isEmpty()) return colorize(text); + Matcher m = PLACEHOLDER_PATTERN.matcher(text); + Map hooks = getPlaceholders(); + while (m.find()) { + String format = m.group(1); + int index = format.indexOf("_"); + if (index <= 0 || index >= format.length()) continue; + String identifier = format.substring(0, index).toLowerCase(); + String params = format.substring(index+1); + if (hooks.containsKey(identifier)) { + String value = hooks.get(identifier).onPlaceholderRequest(player, params); + if (value != null) { + text = text.replace("%"+format+"%", Matcher.quoteReplacement(value)); + } + } + } + return ChatColor.translateAlternateColorCodes('&', text); + } + + /** + * set relational placeholders in the text specified + * placeholders are matched with the pattern %% when set with this method + * @param one Player to compare + * @param two Player to compare + * @param text text to parse the placeholder values to + * @return text with all relational placeholders set to the corresponding values + */ + public static List setRelationalPlaceholders(Player one, Player two, List text) { + if (text == null) return null; + List temp = new ArrayList<>(); + text.forEach(line -> { + temp.add(setRelationalPlaceholders(one, two, line)); + }); + return temp; + } + + /** + * set relational placeholders in the text specified + * placeholders are matched with the pattern %% when set with this method + * @param one Player to compare + * @param two Player to compare + * @param text text to parse the placeholder values to + * @return text with all relational placeholders set to the corresponding values + */ + public static String setRelationalPlaceholders(Player one, Player two, String text) { + if (text == null) return null; + if (placeholders.isEmpty()) return colorize(text); + Matcher m = RELATIONAL_PLACEHOLDER_PATTERN.matcher(text); + Map hooks = getPlaceholders(); + while (m.find()) { + String format = m.group(2); + int index = format.indexOf("_"); + if (index <= 0 || index >= format.length()) continue; + String identifier = format.substring(0, index).toLowerCase(); + String params = format.substring(index+1); + + if (hooks.containsKey(identifier)) { + + if (!(hooks.get(identifier) instanceof Relational)) { + continue; + } + + Relational rel = (Relational) hooks.get(identifier); + String value = rel.onPlaceholderRequest(one, two, params); + + if (value != null) { + text = text.replace("%rel_"+format+"%", Matcher.quoteReplacement(value)); + } + } + } + return ChatColor.translateAlternateColorCodes('&', text); + } + + public static Pattern getPlaceholderPattern() { + return PLACEHOLDER_PATTERN; + } + + public static Pattern getBracketPlaceholderPattern() { + return BRACKET_PLACEHOLDER_PATTERN; + } + + public static Pattern getRelationalPlaceholderPattern() { + return RELATIONAL_PLACEHOLDER_PATTERN; + } + + public static String colorize(String text) { + return ChatColor.translateAlternateColorCodes('&', text); + } + + @Deprecated + public static Set getRegisteredPlaceholderPlugins() { + return getRegisteredIdentifiers(); + } + @Deprecated + public static Set getExternalPlaceholderPlugins() { + return null; + } + @Deprecated + public static boolean registerPlaceholderHook(Plugin plugin, PlaceholderHook placeholderHook) { + return plugin != null && registerPlaceholderHook(plugin.getName(), placeholderHook); + } + @Deprecated + public static boolean unregisterPlaceholderHook(Plugin plugin) { + return plugin != null && unregisterPlaceholderHook(plugin.getName()); + } +} diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java new file mode 100644 index 0000000..4ace626 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java @@ -0,0 +1,258 @@ +package me.clip.placeholderapi; + +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import me.clip.placeholderapi.configuration.PlaceholderAPIConfig; +import me.clip.placeholderapi.expansion.ExpansionManager; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.expansion.Version; +import me.clip.placeholderapi.expansion.cloud.ExpansionCloudManager; +import me.clip.placeholderapi.updatechecker.UpdateChecker; +import me.clip.placeholderapi.util.TimeUtil; +import me.clip.placeholderapi.metrics.Metrics; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; + + +/** + * Yes I have a shit load of work to do... + * + * @author Ryan McCarthy + * + */ +public class PlaceholderAPIPlugin extends JavaPlugin { + + private static PlaceholderAPIPlugin instance; + + private PlaceholderAPIConfig config; + + private ExpansionManager expansionManager; + + private ExpansionCloudManager expansionCloud; + + private static SimpleDateFormat dateFormat; + + private static String booleanTrue; + + private static String booleanFalse; + + private static Version serverVersion; + + private long startTime; + + @Override + public void onLoad() { + startTime = System.currentTimeMillis(); + instance = this; + serverVersion = getVersion(); + config = new PlaceholderAPIConfig(this); + expansionManager = new ExpansionManager(this); + } + + @Override + public void onEnable() { + config.loadDefConfig(); + setupOptions(); + setupCommands(); + new PlaceholderListener(this); + getLogger().info("Placeholder expansion registration initializing..."); + expansionManager.registerAllExpansions(); + if (config.checkUpdates()) { + new UpdateChecker(this); + } + if (config.isCloudEnabled()) { + enableCloud(); + } + setupMetrics(); + } + + @Override + public void onDisable() { + disableCloud(); + PlaceholderAPI.unregisterAll(); + expansionManager.clean(); + expansionManager = null; + Bukkit.getScheduler().cancelTasks(this); + serverVersion = null; + instance = null; + } + + public void reloadConf(CommandSender s) { + boolean cloudEnabled = this.expansionCloud != null; + expansionManager.clean(); + PlaceholderAPI.unregisterAllExpansions(); + reloadConfig(); + saveConfig(); + setupOptions(); + expansionManager.registerAllExpansions(); + if (!config.isCloudEnabled()) { + disableCloud(); + } else if (!cloudEnabled) { + enableCloud(); + } + s.sendMessage(ChatColor.translateAlternateColorCodes('&', PlaceholderAPI.getRegisteredIdentifiers().size() + " &aplaceholder hooks successfully registered!")); + } + + private void setupCommands() { + if (serverVersion != null && serverVersion.isSpigot()) { + getCommand("placeholderapi").setExecutor(new me.clip.placeholderapi.commands.spigot.PlaceholderAPICommands(this)); + } else { + getCommand("placeholderapi").setExecutor(new me.clip.placeholderapi.commands.bukkit.PlaceholderAPICommands(this)); + } + } + + private void setupOptions() { + booleanTrue = config.booleanTrue(); + if (booleanTrue == null) { + booleanTrue = "true"; + } + booleanFalse = config.booleanFalse(); + if (booleanFalse == null) { + booleanFalse = "false"; + } + try { + dateFormat = new SimpleDateFormat(config.dateFormat()); + } catch (Exception e) { + dateFormat = new SimpleDateFormat("MM/dd/yy HH:mm:ss"); + } + } + + private void setupMetrics() { + Metrics m = new Metrics(this); + m.addCustomChart(new Metrics.SimplePie("using_expansion_cloud", () -> getExpansionCloud() != null ? "yes" : "no")); + + m.addCustomChart(new Metrics.SimplePie("using_spigot", () -> getServerVersion().isSpigot() ? "yes" : "no")); + + m.addCustomChart(new Metrics.AdvancedPie("expansions_used", () -> { + Map map = new HashMap<>(); + Map p = PlaceholderAPI.getPlaceholders(); + + if (!p.isEmpty()) { + + for (PlaceholderHook hook : p.values()) { + if (hook instanceof PlaceholderExpansion) { + PlaceholderExpansion ex = (PlaceholderExpansion) hook; + map.put(ex.getPlugin() == null ? ex.getIdentifier() + : ex.getPlugin(), 1); + } + } + } + return map; + + })); + + } + + private static Version getVersion() { + String v = "unknown"; + boolean spigot = false; + try { + v = Bukkit.getServer().getClass().getPackage().getName() + .split("\\.")[3]; + } catch (ArrayIndexOutOfBoundsException ex) { + } + try { + Class.forName("org.spigotmc.SpigotConfig"); + Class.forName("net.md_5.bungee.api.chat.BaseComponent"); + spigot = true; + } catch (ExceptionInInitializerError | ClassNotFoundException exception) { + } + return new Version(v, spigot); + } + + public void enableCloud() { + if (expansionCloud == null) { + expansionCloud = new ExpansionCloudManager(this); + expansionCloud.fetch(); + } else { + expansionCloud.clean(); + expansionCloud.fetch(); + } + } + + public void disableCloud() { + if (expansionCloud != null) { + expansionCloud.clean(); + expansionCloud = null; + } + } + + /** + * Gets the static instance of the main class for PlaceholderAPI. This class + * is not the actual API class, this is the main class that extends + * JavaPlugin. For most API methods, use static methods available from the + * class: {@link PlaceholderAPI} + * + * @return PlaceholderAPIPlugin instance + */ + public static PlaceholderAPIPlugin getInstance() { + return instance; + } + + /** + * Get the configurable {@linkplain SimpleDateFormat} object that is used to + * parse time for generic time based placeholders + * + * @return date format + */ + public static SimpleDateFormat getDateFormat() { + return dateFormat != null ? dateFormat : new SimpleDateFormat( + "MM/dd/yy HH:mm:ss"); + } + + /** + * Get the configurable {@linkplain String} value that should be returned + * when a boolean is true + * + * @return string value of true + */ + public static String booleanTrue() { + return booleanTrue != null ? booleanTrue : "true"; + } + + /** + * Get the configurable {@linkplain String} value that should be returned + * when a boolean is false + * + * @return string value of false + */ + public static String booleanFalse() { + return booleanFalse != null ? booleanFalse : "false"; + } + + public static Version getServerVersion() { + return serverVersion != null ? serverVersion : getVersion(); + } + + /** + * Obtain the configuration class for PlaceholderAPI. + * + * @return PlaceholderAPIConfig instance + */ + public PlaceholderAPIConfig getPlaceholderAPIConfig() { + return config; + } + + public ExpansionManager getExpansionManager() { + return expansionManager; + } + + public ExpansionCloudManager getExpansionCloud() { + return expansionCloud; + } + + public String getUptime() { + return TimeUtil.getTime((int) TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - startTime)); + } + + public long getUptimeMillis() { + return (System.currentTimeMillis() - startTime); + } +} diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderHook.java b/src/main/java/me/clip/placeholderapi/PlaceholderHook.java new file mode 100644 index 0000000..7aa71cd --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/PlaceholderHook.java @@ -0,0 +1,14 @@ +package me.clip.placeholderapi; + +import org.bukkit.entity.Player; + +public abstract class PlaceholderHook { + + /** + * called when a placeholder is requested from this PlaceholderHook + * @param p Player requesting the placeholder value for, null if not needed for a player + * @param params String passed for the placeholder hook to determine what value to return + * @return value for the requested player and params + */ + public abstract String onPlaceholderRequest(Player p, String params); +} diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderListener.java b/src/main/java/me/clip/placeholderapi/PlaceholderListener.java new file mode 100644 index 0000000..54940ce --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/PlaceholderListener.java @@ -0,0 +1,138 @@ +package me.clip.placeholderapi; + +import java.util.Map; +import java.util.Map.Entry; + +import me.clip.placeholderapi.events.PlaceholderHookUnloadEvent; +import me.clip.placeholderapi.expansion.Cacheable; +import me.clip.placeholderapi.expansion.Cleanable; +import me.clip.placeholderapi.expansion.ExpansionManager; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.expansion.Taskable; +import me.clip.placeholderapi.expansion.cloud.CloudExpansion; +import me.clip.placeholderapi.external.EZPlaceholderHook; + +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; + +@SuppressWarnings("deprecation") +public class PlaceholderListener implements Listener { + + private PlaceholderAPIPlugin plugin; + + public PlaceholderListener(PlaceholderAPIPlugin instance) { + plugin = instance; + Bukkit.getPluginManager().registerEvents(this, instance); + } + + @EventHandler + public void onInternalUnload(PlaceholderHookUnloadEvent event) { + + if (event.getHook() instanceof Listener) { + HandlerList.unregisterAll((Listener)event.getHook()); + plugin.getLogger().info("Unregistered event listener for placeholder expansion: " + event.getHookName()); + } + + if (event.getHook() instanceof Taskable) { + plugin.getLogger().info("Cancelling scheduled task for placeholder expansion: " + event.getHookName()); + ((Taskable) event.getHook()).stop(); + } + + if (event.getHook() instanceof Cacheable) { + ((Cacheable) event.getHook()).clear(); + } + + if (plugin.getExpansionCloud() != null) { + + CloudExpansion ex = plugin.getExpansionCloud().getCloudExpansion(event.getHookName()); + + if (ex != null) { + ex.setHasExpansion(false); + ex.setShouldUpdate(false); + } + } + } + + @EventHandler + public void onEnable(PluginEnableEvent event) { + ExpansionManager m = plugin.getExpansionManager(); + PlaceholderExpansion e = m.getCachedExpansion(event.getPlugin().getName().toLowerCase()); + if (e != null && e.canRegister()) { + if (e.isRegistered() || m.registerExpansion(e)) { + m.removeCachedExpansion(e.getPlugin()); + } + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onPluginUnload(PluginDisableEvent e) { + + String n = e.getPlugin().getName(); + + if (n == null) { + return; + } + + if (n.equals(plugin.getName())) { + return; + } + + Map hooks = PlaceholderAPI.getPlaceholders(); + + for (Entry hook : hooks.entrySet()) { + + PlaceholderHook i = hook.getValue(); + + if (i instanceof EZPlaceholderHook) { + + EZPlaceholderHook h = (EZPlaceholderHook) i; + + if (h.getPluginName() == null) { + continue; + } + + if (h.getPluginName().equalsIgnoreCase(n)) { + if (PlaceholderAPI.unregisterPlaceholderHook(hook.getKey())) { + plugin.getLogger().info("Unregistered placeholder hook for placeholder: " + h.getPlaceholderName()); + break; + } + } + } else if (i instanceof PlaceholderExpansion) { + + PlaceholderExpansion ex = (PlaceholderExpansion) i; + + if (ex.getPlugin() == null) { + continue; + } + + if (ex.getPlugin().equalsIgnoreCase(n)) { + if (PlaceholderAPI.unregisterPlaceholderHook(hook.getKey())) { + plugin.getLogger().info("Unregistered placeholder expansion: " + ex.getIdentifier()); + } + } + } + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + + Map placeholders = PlaceholderAPI.getPlaceholders(); + + if (placeholders.isEmpty()) { + return; + } + + for (PlaceholderHook hooks : placeholders.values()) { + if (hooks instanceof Cleanable) { + ((Cleanable) hooks).cleanup(e.getPlayer()); + } + } + } +} diff --git a/src/main/java/me/clip/placeholderapi/commands/bukkit/ExpansionCloudCommands.java b/src/main/java/me/clip/placeholderapi/commands/bukkit/ExpansionCloudCommands.java new file mode 100644 index 0000000..c26a7d9 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/commands/bukkit/ExpansionCloudCommands.java @@ -0,0 +1,215 @@ +package me.clip.placeholderapi.commands.bukkit; + +import java.util.Map; +import java.util.Map.Entry; + +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.expansion.cloud.CloudExpansion; +import me.clip.placeholderapi.util.Msg; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class ExpansionCloudCommands implements CommandExecutor { + + private PlaceholderAPIPlugin plugin; + + public ExpansionCloudCommands(PlaceholderAPIPlugin instance) { + plugin = instance; + } + + @Override + public boolean onCommand(CommandSender s, Command c, String label, String[] args) { + + if (args.length == 1) { + Msg.msg(s, "&bExpansion cloud commands"); + Msg.msg(s, " "); + Msg.msg(s, "&b/papi ecloud status"); + Msg.msg(s, "&fView status of the cloud"); + Msg.msg(s, "&b/papi ecloud list (page)"); + Msg.msg(s, "&fList all/author specific available expansions"); + Msg.msg(s, "&b/papi ecloud info "); + Msg.msg(s, "&fView information about a specific expansion available on the cloud"); + Msg.msg(s, "&b/papi ecloud download "); + Msg.msg(s, "&fDownload a specific expansion from the cloud"); + Msg.msg(s, "&b/papi ecloud refresh"); + Msg.msg(s, "&fFetch the most up to date list of expansions available."); + Msg.msg(s, "&b/papi ecloud clear"); + Msg.msg(s, "&fClear the expansion cloud cache."); + return true; + } + + if (args[1].equalsIgnoreCase("refresh")) { + Msg.msg(s, "&aRefresh task started. Use &7/papi ecloud list all &fin a few!!"); + plugin.getExpansionCloud().clean(); + plugin.getExpansionCloud().fetch(); + return true; + } + + if (plugin.getExpansionCloud().getCloudExpansions().isEmpty()) { + Msg.msg(s, "&7No cloud expansions are available at this time."); + return true; + } + + if (args[1].equalsIgnoreCase("clear")) { + plugin.getExpansionCloud().clean(); + Msg.msg(s, "&aThe cloud cache has been cleared!!"); + return true; + } + + if (args[1].equalsIgnoreCase("download")) { + + if (args.length < 3) { + Msg.msg(s, "&cAn expansion name must be specified!"); + return true; + } + + CloudExpansion expansion = plugin.getExpansionCloud().getCloudExpansion(args[2]); + + if (expansion == null) { + Msg.msg(s, "&cNo expansion found with the name: &f" + args[2]); + return true; + } + + PlaceholderExpansion loaded = plugin.getExpansionManager().getRegisteredExpansion(args[2]); + + if (loaded != null && loaded.isRegistered()) { + PlaceholderAPI.unregisterPlaceholderHook(loaded.getIdentifier()); + } + + Msg.msg(s, "&aAttempting download of expansion &f" + expansion.getName()); + + String player = ((s instanceof Player) ? s.getName() : null); + + plugin.getExpansionCloud().downloadExpansion(player, expansion); + + return true; + } + + if (args[1].equalsIgnoreCase("status")) { + + Msg.msg(s, "&bThere are &f" + plugin.getExpansionCloud().getCloudExpansions().size() + + " &bcloud expansions available to download on demand."); + Msg.msg(s, "&bA total of &f" + plugin.getExpansionCloud().getCloudAuthorCount() + + " &bauthors have contributed to the expansion cloud."); + + return true; + } else if (args[1].equalsIgnoreCase("info")) { + + if (args.length < 3) { + Msg.msg(s, "&cAn expansion name must be specified!"); + return true; + } + + CloudExpansion expansion = plugin.getExpansionCloud().getCloudExpansion(args[2]); + + if (expansion == null) { + Msg.msg(s, "&cNo expansion found with the name: &f" + args[2]); + return true; + } + + PlaceholderExpansion exp = plugin.getExpansionManager().getRegisteredExpansion(args[2]); + + boolean enabled = false; + String version = null; + + if (exp != null) { + enabled = exp.isRegistered(); + version = exp.getVersion(); + } + + Msg.msg(s, "&aExpansion: &f" + expansion.getName()); + if (enabled) { + Msg.msg(s, "&aThis expansion is currently enabled!"); + Msg.msg(s, "&bYour version&7: &f" + version); + } + + Msg.msg(s, "&bCloud version&7: &f" + expansion.getVersion()); + Msg.msg(s, "&bAuthor&7: &f" + expansion.getAuthor()); + + String desc = expansion.getVersion(); + + if (desc.indexOf("\n") > 0) { + for (String line : desc.split("\n")) { + Msg.msg(s, line); + } + } else { + Msg.msg(s, desc); + } + + Msg.msg(s, "&bDownload with &7/papi ecloud download " + expansion.getName()); + return true; + + } else if (args[1].equalsIgnoreCase("list")) { + + int page = 1; + + String author; + + if (args.length < 3) { + Msg.msg(s, "&cIncorrect usage! &7/papi ecloud list (page)"); + return true; + } + + author = args[2]; + + if (author.equalsIgnoreCase("all")) { + author = null; + } + + if (args.length >= 4) { + try { + page = Integer.parseInt(args[3]); + } catch (NumberFormatException ex) { + Msg.msg(s, "&cPage number must be an integer!"); + return true; + } + } + + if (page < 1) { + Msg.msg(s, "&cPage must be greater than or equal to 1!"); + return true; + } + + int avail; + + Map ex; + + if (author == null) { + ex = plugin.getExpansionCloud().getCloudExpansions(); + } else { + ex = plugin.getExpansionCloud().getAllByAuthor(author); + } + + if (ex == null) { + Msg.msg(s, "&cNo expansions available" + (author != null ? " for author &f" + author : "")); + return true; + } + + avail = plugin.getExpansionCloud().getPagesAvailable(ex, 10); + + if (page > avail) { + Msg.msg(s, "&cThere " + ((avail == 1) ? " is only &f" + avail + " &cpage available!" : "are only &f" + avail + " &cpages available!")); + return true; + } + + Msg.msg(s, "&bExpansion cloud for &f" + (author != null ? author : "all available")+ " &8&m-- &r&bamount&7: &f" + ex.size() + " &bpage&7: &f" + page + "&7/&f" + avail); + + ex = plugin.getExpansionCloud().getPage(ex, page); + + for (Entry expansion : ex.entrySet()) { + Msg.msg(s, "&b" + (expansion.getKey()+1) + "&7: &f" + expansion.getValue().getName() + " &8&m-- &r" + expansion.getValue().getLink()); + } + Msg.msg(s, "&bDownload an expansion with &7/papi ecloud download "); + Msg.msg(s, "&bView more info on an expansion with &7/papi ecloud info "); + return true; + } + + return true; + } + +} diff --git a/src/main/java/me/clip/placeholderapi/commands/bukkit/PlaceholderAPICommands.java b/src/main/java/me/clip/placeholderapi/commands/bukkit/PlaceholderAPICommands.java new file mode 100644 index 0000000..5853e58 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/commands/bukkit/PlaceholderAPICommands.java @@ -0,0 +1,226 @@ +package me.clip.placeholderapi.commands.bukkit; + +import java.util.Set; + +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.util.Msg; + +import org.apache.commons.lang.StringUtils; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class PlaceholderAPICommands implements CommandExecutor { + + private PlaceholderAPIPlugin plugin; + + private ExpansionCloudCommands eCloud; + + + public PlaceholderAPICommands(PlaceholderAPIPlugin i) { + plugin = i; + eCloud = new ExpansionCloudCommands(i); + } + + @Override + public boolean onCommand(CommandSender s, Command c, String label, String[] args) { + + if (args.length == 0) { + Msg.msg(s, "PlaceholderAPI &7version &b&o"+plugin.getDescription().getVersion()); + Msg.msg(s, "&fCreated by&7: &bextended_clip"); + return true; + + } else { + + if (args[0].equalsIgnoreCase("help")) { + Msg.msg(s, "PlaceholderAPI &aHelp &e(&f" + plugin.getDescription().getVersion() + "&e)"); + Msg.msg(s, "&b/papi"); + Msg.msg(s, "&fView plugin info/version info"); + Msg.msg(s, "&b/papi list"); + Msg.msg(s, "&fList all placeholder expansions that are currently active"); + Msg.msg(s, "&b/papi info "); + Msg.msg(s, "&fView information for a specific expansion"); + Msg.msg(s, "&b/papi parse <...args>"); + Msg.msg(s, "&fParse a String with placeholders"); + Msg.msg(s, "&b/papi reload"); + Msg.msg(s, "&fReload the config settings"); + + boolean enabled = plugin.getExpansionCloud() != null; + + + if (s.isOp()) { + if (!enabled) { + Msg.msg(s, "&b/papi enablecloud"); + Msg.msg(s, "&fEnable the expansion cloud"); + } else { + Msg.msg(s, "&b/papi disablecloud"); + Msg.msg(s, "&fDisable the expansion cloud"); + Msg.msg(s, "&b/papi ecloud"); + Msg.msg(s, "&fView information about the PlaceholderAPI expansion cloud"); + Msg.msg(s, "&b/papi ecloud status"); + Msg.msg(s, "&fView status of the PlaceholderAPI expansion cloud"); + Msg.msg(s, "&b/papi ecloud list "); + Msg.msg(s, "&fList all available expansions"); + Msg.msg(s, "&b/papi ecloud info "); + Msg.msg(s, "&fView information about a specific expansion on the cloud"); + Msg.msg(s, "&b/papi ecloud download "); + Msg.msg(s, "&fDownload a specific expansion from the cloud"); + } + } + + return true; + + } else if (args[0].equalsIgnoreCase("ecloud")) { + if (!s.isOp()) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + + if (plugin.getExpansionCloud() == null) { + Msg.msg(s, "&7The expansion cloud is not enabled!"); + return true; + } + + return eCloud.onCommand(s, c, label, args); + + } else if (args[0].equalsIgnoreCase("enablecloud")) { + + if (!s.isOp()) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + + if (plugin.getExpansionCloud() != null) { + Msg.msg(s, "&7The cloud is already enabled!"); + return true; + } + + plugin.enableCloud(); + plugin.getPlaceholderAPIConfig().setCloudEnabled(true); + Msg.msg(s, "&aThe cloud has been enabled!"); + return true; + + } else if (args[0].equalsIgnoreCase("disablecloud")) { + + if (!s.isOp()) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + + if (plugin.getExpansionCloud() == null) { + Msg.msg(s, "&7The cloud is already disabled!"); + return true; + } + + plugin.disableCloud(); + plugin.getPlaceholderAPIConfig().setCloudEnabled(false); + Msg.msg(s, "&aThe cloud has been disabled!"); + return true; + + } else if (args[0].equalsIgnoreCase("info")) { + + if (!s.hasPermission("placeholderapi.info")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + + PlaceholderExpansion ex = plugin.getExpansionManager().getRegisteredExpansion(args[1]); + + if (ex == null) { + Msg.msg(s, "&cThere is no expansion loaded with the identifier: &f" + args[1]); + return true; + } + + Msg.msg(s, "&7Placeholder expansion info for: &f%" + ex.getIdentifier() + "_%"); + + Msg.msg(s, "&7Status: " + (ex.isRegistered() ? "&aRegistered" : "&cNot registered")); + + if (ex.getAuthor() != null) { + Msg.msg(s, "&7Created by: &f" + ex.getAuthor()); + } + + if (ex.getVersion() != null) { + Msg.msg(s, "&7Version: &f" + ex.getVersion()); + } + + if (ex.getDescription() != null) { + Msg.msg(s, ex.getDescription()); + } + + if (ex.getLink() != null) { + Msg.msg(s, "&7Link: &f" + ex.getLink()); + } + + if (ex.getPlugin() != null) { + Msg.msg(s, "&7Requires plugin: &f" + ex.getPlugin()); + } + + if (ex.getPlaceholders() != null) { + Msg.msg(s, "&8&m-- &r&7Placeholders &8&m--"); + for (String placeholder : ex.getPlaceholders()) { + Msg.msg(s, placeholder); + } + } + + return true; + } else if (args[0].equalsIgnoreCase("parse")) { + + if (!(s instanceof Player)) { + Msg.msg(s, "&cThis command can only be used in game!"); + return true; + } else { + if (!s.hasPermission("placeholderapi.parse")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + } + + Player p = (Player) s; + + String parse = StringUtils.join(args, " ", 1, args.length); + + Msg.msg(s, "&r" + PlaceholderAPI.setPlaceholders(p, parse)); + + return true; + } else if (args[0].equalsIgnoreCase("reload")) { + + if (s instanceof Player) { + if (!s.hasPermission("placeholderapi.reload")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + } + + Msg.msg(s, "&cClips &fPlaceholder&7API &bconfiguration reloaded!"); + + plugin.reloadConf(s); + + } else if (args[0].equalsIgnoreCase("list")) { + + if (s instanceof Player) { + if (!s.hasPermission("placeholderapi.list")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + } + + Set registered = PlaceholderAPI.getRegisteredIdentifiers(); + + if (registered.isEmpty()) { + Msg.msg(s, "&7There are no placeholder hooks currently registered!"); + return true; + } + Msg.msg(s, registered.size()+" &7Placeholder hooks registered:"); + Msg.msg(s, registered.toString()); + } else { + Msg.msg(s, "&cIncorrect usage! &7/papi help"); + } + } + + return true; + } + +} diff --git a/src/main/java/me/clip/placeholderapi/commands/spigot/ExpansionCloudCommands.java b/src/main/java/me/clip/placeholderapi/commands/spigot/ExpansionCloudCommands.java new file mode 100644 index 0000000..88b8309 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/commands/spigot/ExpansionCloudCommands.java @@ -0,0 +1,301 @@ +package me.clip.placeholderapi.commands.spigot; + +import java.util.Map; +import java.util.Map.Entry; + +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.expansion.cloud.CloudExpansion; +import me.clip.placeholderapi.util.Msg; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class ExpansionCloudCommands implements CommandExecutor { + + private PlaceholderAPIPlugin plugin; + + public ExpansionCloudCommands(PlaceholderAPIPlugin instance) { + plugin = instance; + } + + @Override + public boolean onCommand(CommandSender s, Command c, String label, String[] args) { + + if (args.length == 1) { + Msg.msg(s, "&bExpansion cloud commands"); + Msg.msg(s, " "); + Msg.msg(s, "&b/papi ecloud status"); + Msg.msg(s, "&fView status of the cloud"); + Msg.msg(s, "&b/papi ecloud list (page)"); + Msg.msg(s, "&fList all/author specific available expansions"); + Msg.msg(s, "&b/papi ecloud info "); + Msg.msg(s, "&fView information about a specific expansion available on the cloud"); + Msg.msg(s, "&b/papi ecloud download "); + Msg.msg(s, "&fDownload a specific expansion from the cloud"); + Msg.msg(s, "&b/papi ecloud refresh"); + Msg.msg(s, "&fFetch the most up to date list of expansions available."); + Msg.msg(s, "&b/papi ecloud clear"); + Msg.msg(s, "&fClear the expansion cloud cache."); + return true; + } + + if (args[1].equalsIgnoreCase("refresh") || args[1].equalsIgnoreCase("update") || args[1].equalsIgnoreCase("fetch")) { + Msg.msg(s, "&aRefresh task started. Use &f/papi ecloud list all &ain a few!!"); + plugin.getExpansionCloud().clean(); + plugin.getExpansionCloud().fetch(); + return true; + } + + if (plugin.getExpansionCloud().getCloudExpansions().isEmpty()) { + Msg.msg(s, "&7No cloud expansions are available at this time."); + return true; + } + + if (args[1].equalsIgnoreCase("clear")) { + plugin.getExpansionCloud().clean(); + Msg.msg(s, "&aThe cloud cache has been cleared!!"); + return true; + } + + if (args[1].equalsIgnoreCase("status")) { + + Msg.msg(s, "&bThere are &f" + plugin.getExpansionCloud().getCloudExpansions().size() + " &bexpansions available on the cloud."); + Msg.msg(s, "&7A total of &f" + plugin.getExpansionCloud().getCloudAuthorCount() + + " &7authors have contributed to the expansion cloud."); + if (plugin.getExpansionCloud().getToUpdateCount() > 0) { + Msg.msg(s, "&eYou have &f" + plugin.getExpansionCloud().getToUpdateCount() + + " &eexpansions installed that have updates available."); + } + + return true; + } + + if (args[1].equalsIgnoreCase("info")) { + + if (args.length < 3) { + Msg.msg(s, "&cAn expansion name must be specified!"); + return true; + } + + CloudExpansion expansion = plugin.getExpansionCloud().getCloudExpansion(args[2]); + + if (expansion == null) { + Msg.msg(s, "&cNo expansion found with the name: &f" + args[2]); + return true; + } + + if (!(s instanceof Player)) { + Msg.msg(s, (expansion.shouldUpdate() ? "&e" : "") + expansion.getName() + " &8&m-- &r" + expansion.getLink()); + return true; + } + + Player p = (Player) s; + + Msg.msg(s, "&bCloud expansion info for&7:" + (expansion.shouldUpdate() ? "&6" : (expansion.hasExpansion() ? "&e" : "")) + expansion.getName()); + + StringBuilder sb = new StringBuilder(); + + sb.append(expansion.getDescription()); + + if (expansion.getReleaseNotes() != null) { + sb.append("\n\n" + expansion.getReleaseNotes()); + } + + String hover = ChatColor.translateAlternateColorCodes('&', sb.toString()); + + if (expansion.hasExpansion()) { + if (expansion.shouldUpdate()) { + Msg.msg(s, "&6You have this expansion but there is a newer version available."); + } else { + Msg.msg(s, "&aYou have the latest version of this expansion!"); + } + } else { + Msg.msg(s, "&7You do not have this expansion installed"); + } + + sms(p, "&bAuthor&7: &f" + expansion.getAuthor(), hover, null); + sms(p, "&bVersion&7: &f" + expansion.getVersion(), hover, null); + if (expansion.getLastUpdate() > 1) { + sb.append("&bLast updated&7: &f" + expansion.getTimeSinceLastUpdate() + " ago"); + } + sms(p, "&aClick here to download!", hover, expansion.getName()); + return true; + } + + if (args[1].equalsIgnoreCase("list")) { + + int page = 1; + + String author; + boolean installed = false; + + if (args.length < 3) { + Msg.msg(s, "&cIncorrect usage! &7/papi ecloud list (page)"); + return true; + } + + author = args[2]; + + if (author.equalsIgnoreCase("all")) { + author = null; + } else if (author.equalsIgnoreCase("installed")) { + author = null; + installed = true; + } + + if (args.length >= 4) { + try { + page = Integer.parseInt(args[3]); + } catch (NumberFormatException ex) { + Msg.msg(s, "&cPage number must be an integer!"); + return true; + } + } + + if (page < 1) { + Msg.msg(s, "&cPage must be greater than or equal to 1!"); + return true; + } + + int avail; + + Map ex; + + if (installed) { + ex = plugin.getExpansionCloud().getAllInstalled(); + } else if (author == null) { + ex = plugin.getExpansionCloud().getCloudExpansions(); + } else { + ex = plugin.getExpansionCloud().getAllByAuthor(author); + } + + if (ex == null || ex.isEmpty()) { + Msg.msg(s, "&cNo expansions available" + (author != null ? " for author &f" + author : "")); + return true; + } + + avail = plugin.getExpansionCloud().getPagesAvailable(ex, 10); + + if (page > avail) { + Msg.msg(s, "&cThere " + ((avail == 1) ? " is only &f" + avail + " &cpage available!" : "are only &f" + avail + " &cpages available!")); + return true; + } + + Msg.msg(s, "&bShowing expansions for&7: &f" + (author != null ? author : (installed ? "all installed" : "all available"))+ " &8&m--&r &bamount&7: &f" + ex.size() + " &bpage&7: &f" + page + "&7/&f" + avail); + + ex = plugin.getExpansionCloud().getPage(ex, page); + + if (ex == null) { + Msg.msg(s, "&cThere was a problem getting the requested page..."); + return true; + } + + Msg.msg(s, "&aGreen = Expansions you have"); + Msg.msg(s, "&6Gold = Expansions which need updated"); + + if (!(s instanceof Player)) { + + for (Entry expansion : ex.entrySet()) { + if (expansion == null || expansion.getKey() == null || expansion.getValue() == null) continue; + Msg.msg(s, "&b" + (expansion.getKey()+1) + "&7: " + (expansion.getValue().shouldUpdate() ? "&6" : (expansion.getValue().hasExpansion() ? "&a" : "&7")) + expansion.getValue().getName() + " &8&m-- &r" + expansion.getValue().getLink()); + } + + return true; + } + + Player p = (Player) s; + + for (Entry expansion : ex.entrySet()) { + + if (expansion == null || expansion.getValue() == null) { + continue; + } + + StringBuilder sb = new StringBuilder(); + if (expansion.getValue().shouldUpdate()) { + sb.append("&6Click to update to the latest version of this expansion\n\n"); + } else if (!expansion.getValue().hasExpansion()) { + sb.append("&bClick to download this expansion\n\n"); + } else { + sb.append("&aYou have the latest version of this expansion\n\n"); + } + sb.append("&bAuthor&7: &f" + expansion.getValue().getAuthor() + "\n"); + sb.append("&bVersion&7: &f" + expansion.getValue().getVersion() + "\n"); + if (expansion.getValue().getLastUpdate() > 1) { + sb.append("&bLast updated&7: &f" + expansion.getValue().getTimeSinceLastUpdate() + " ago\n"); + } + if (expansion.getValue().getReleaseNotes() != null) { + sb.append("&bRelease Notes&7: &f" + expansion.getValue().getReleaseNotes() + "\n"); + } + sb.append("\n" + expansion.getValue().getDescription()); + + String msg = ChatColor.translateAlternateColorCodes('&', "&b" + (expansion.getKey()+1) + "&7: " + (expansion.getValue().shouldUpdate() ? "&6" : (expansion.getValue().hasExpansion() ? "&a" : "")) + expansion.getValue().getName()); + + String hover = ChatColor.translateAlternateColorCodes('&', sb.toString()); + + sms(p, msg, hover, expansion.getValue().getName()); + } + + return true; + } + + + if (args[1].equalsIgnoreCase("download")) { + + if (args.length < 3) { + Msg.msg(s, "&cAn expansion name must be specified!"); + return true; + } + + CloudExpansion expansion = plugin.getExpansionCloud().getCloudExpansion(args[2]); + + if (expansion == null) { + Msg.msg(s, "&cNo expansion found with the name: &f" + args[2]); + return true; + } + + if (expansion.hasExpansion() && !expansion.shouldUpdate()) { + Msg.msg(s, "&aYou already have this expansion installed and your version is up to date!"); + return true; + } + + PlaceholderExpansion loaded = plugin.getExpansionManager().getRegisteredExpansion(args[2]); + + if (loaded != null && loaded.isRegistered()) { + PlaceholderAPI.unregisterPlaceholderHook(loaded.getIdentifier()); + } + + Msg.msg(s, "&aAttempting download of expansion &f" + expansion.getName()); + + String player = ((s instanceof Player) ? s.getName() : null); + + plugin.getExpansionCloud().downloadExpansion(player, expansion); + + return true; + } + + Msg.msg(s, "&cIncorrect usage! &b/papi ecloud"); + return true; + } + + private void sms(Player p, String text, String hover, String link) { + TextComponent message = new TextComponent( ChatColor.translateAlternateColorCodes('&', text) ); + if (hover != null) { + message.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(ChatColor.translateAlternateColorCodes('&', hover)).create() ) ); + } + if (link != null) { + message.setClickEvent( new ClickEvent( ClickEvent.Action.SUGGEST_COMMAND, "/papi ecloud download " + link) ); + } + p.spigot().sendMessage( message ); + } + +} diff --git a/src/main/java/me/clip/placeholderapi/commands/spigot/PlaceholderAPICommands.java b/src/main/java/me/clip/placeholderapi/commands/spigot/PlaceholderAPICommands.java new file mode 100644 index 0000000..c784e61 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/commands/spigot/PlaceholderAPICommands.java @@ -0,0 +1,258 @@ +package me.clip.placeholderapi.commands.spigot; + +import java.util.Set; + +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.util.Msg; + +import org.apache.commons.lang.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class PlaceholderAPICommands implements CommandExecutor { + + private PlaceholderAPIPlugin plugin; + + private ExpansionCloudCommands eCloud; + + public PlaceholderAPICommands(PlaceholderAPIPlugin i) { + plugin = i; + eCloud = new ExpansionCloudCommands(i); + } + + @Override + public boolean onCommand(CommandSender s, Command c, String label, String[] args) { + + if (args.length == 0) { + Msg.msg(s, "PlaceholderAPI &7version &b&o"+plugin.getDescription().getVersion()); + Msg.msg(s, "&fCreated by&7: &bextended_clip"); + return true; + + } else { + + if (args[0].equalsIgnoreCase("help")) { + Msg.msg(s, "PlaceholderAPI &aHelp &e(&f" + plugin.getDescription().getVersion() + "&e)"); + Msg.msg(s, "&b/papi"); + Msg.msg(s, "&fView plugin info/version info"); + Msg.msg(s, "&b/papi list"); + Msg.msg(s, "&fList all placeholder expansions that are currently active"); + Msg.msg(s, "&b/papi info "); + Msg.msg(s, "&fView information for a specific expansion"); + Msg.msg(s, "&b/papi parse <...args>"); + Msg.msg(s, "&fParse a String with placeholders"); + Msg.msg(s, "&b/papi parserel <...args>"); + Msg.msg(s, "&fParse a String with relational placeholders"); + Msg.msg(s, "&b/papi reload"); + Msg.msg(s, "&fReload the config settings"); + + boolean enabled = plugin.getExpansionCloud() != null; + + + if (s.isOp()) { + if (!enabled) { + Msg.msg(s, "&b/papi enablecloud"); + Msg.msg(s, "&fEnable the expansion cloud"); + } else { + Msg.msg(s, "&b/papi disablecloud"); + Msg.msg(s, "&fDisable the expansion cloud"); + Msg.msg(s, "&b/papi ecloud"); + Msg.msg(s, "&fView information about the PlaceholderAPI expansion cloud"); + Msg.msg(s, "&b/papi ecloud status"); + Msg.msg(s, "&fView status of the PlaceholderAPI expansion cloud"); + Msg.msg(s, "&b/papi ecloud list "); + Msg.msg(s, "&fList all available expansions"); + Msg.msg(s, "&b/papi ecloud info "); + Msg.msg(s, "&fView information about a specific expansion on the cloud"); + Msg.msg(s, "&b/papi ecloud download "); + Msg.msg(s, "&fDownload a specific expansion from the cloud"); + } + } + + return true; + + } else if (args[0].equalsIgnoreCase("ecloud")) { + if (!s.isOp()) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + + if (plugin.getExpansionCloud() == null) { + Msg.msg(s, "&7The expansion cloud is not enabled!"); + return true; + } + + return eCloud.onCommand(s, c, label, args); + + } else if (args[0].equalsIgnoreCase("enablecloud")) { + + if (!s.isOp()) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + + if (plugin.getExpansionCloud() != null) { + Msg.msg(s, "&7The cloud is already enabled!"); + return true; + } + + plugin.enableCloud(); + plugin.getPlaceholderAPIConfig().setCloudEnabled(true); + Msg.msg(s, "&aThe cloud has been enabled!"); + return true; + + } else if (args[0].equalsIgnoreCase("disablecloud")) { + + if (!s.isOp()) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + + if (plugin.getExpansionCloud() == null) { + Msg.msg(s, "&7The cloud is already disabled!"); + return true; + } + + plugin.disableCloud(); + plugin.getPlaceholderAPIConfig().setCloudEnabled(false); + Msg.msg(s, "&aThe cloud has been disabled!"); + return true; + + } else if (args.length > 1 && args[0].equalsIgnoreCase("info")) { + + if (!s.hasPermission("placeholderapi.info")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + + PlaceholderExpansion ex = plugin.getExpansionManager().getRegisteredExpansion(args[1]); + + if (ex == null) { + Msg.msg(s, "&cThere is no expansion loaded with the identifier: &f" + args[1]); + return true; + } + + Msg.msg(s, "&7Placeholder expansion info for: &f%" + ex.getIdentifier() + "_%"); + + Msg.msg(s, "&7Status: " + (ex.isRegistered() ? "&aRegistered" : "&cNot registered")); + + if (ex.getAuthor() != null) { + Msg.msg(s, "&7Created by: &f" + ex.getAuthor()); + } + + if (ex.getVersion() != null) { + Msg.msg(s, "&7Version: &f" + ex.getVersion()); + } + + if (ex.getDescription() != null) { + Msg.msg(s, ex.getDescription()); + } + + if (ex.getLink() != null) { + Msg.msg(s, "&7Link: &f" + ex.getLink()); + } + + if (ex.getPlugin() != null) { + Msg.msg(s, "&7Requires plugin: &f" + ex.getPlugin()); + } + + if (ex.getPlaceholders() != null) { + Msg.msg(s, "&8&m-- &r&7Placeholders &8&m--"); + for (String placeholder : ex.getPlaceholders()) { + Msg.msg(s, placeholder); + } + } + + return true; + } else if (args.length > 1 && args[0].equalsIgnoreCase("parse")) { + + if (!(s instanceof Player)) { + Msg.msg(s, "&cThis command can only be used in game!"); + return true; + } else { + if (!s.hasPermission("placeholderapi.parse")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + } + + Player p = (Player) s; + + String parse = StringUtils.join(args, " ", 1, args.length); + + Msg.msg(s, "&r" + PlaceholderAPI.setPlaceholders(p, parse)); + + return true; + } else if (args.length > 3 && args[0].equalsIgnoreCase("parserel")) { + + if (!(s instanceof Player)) { + Msg.msg(s, "&cThis command can only be used in game!"); + return true; + } else { + if (!s.hasPermission("placeholderapi.parse")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + } + + Player one = Bukkit.getPlayer(args[1]); + if (one == null) { + Msg.msg(s, args[1] + " &cis not online!"); + return true; + } + + Player two = Bukkit.getPlayer(args[2]); + + if (two == null) { + Msg.msg(s, args[2] + " &cis not online!"); + return true; + } + + String parse = StringUtils.join(args, " ", 3, args.length); + + Msg.msg(s, "&r" + PlaceholderAPI.setRelationalPlaceholders(one, two, parse)); + + return true; + } else if (args[0].equalsIgnoreCase("reload")) { + + if (s instanceof Player) { + if (!s.hasPermission("placeholderapi.reload")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + } + + Msg.msg(s, "&fPlaceholder&7API &bconfiguration reloaded!"); + + plugin.reloadConf(s); + + } else if (args[0].equalsIgnoreCase("list")) { + + if (s instanceof Player) { + if (!s.hasPermission("placeholderapi.list")) { + Msg.msg(s, "&cYou don't have permission to do that!"); + return true; + } + } + + Set registered = PlaceholderAPI.getRegisteredIdentifiers(); + + if (registered.isEmpty()) { + Msg.msg(s, "&7There are no placeholder hooks currently registered!"); + return true; + } + Msg.msg(s, registered.size()+" &7Placeholder hooks registered:"); + Msg.msg(s, registered.toString()); + } else { + Msg.msg(s, "&cIncorrect usage! &7/papi help"); + } + } + + return true; + } + +} diff --git a/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java b/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java new file mode 100644 index 0000000..5fc8745 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java @@ -0,0 +1,74 @@ +package me.clip.placeholderapi.configuration; + +import me.clip.placeholderapi.PlaceholderAPIPlugin; + +import org.bukkit.configuration.file.FileConfiguration; + +public class PlaceholderAPIConfig { + + private PlaceholderAPIPlugin plugin; + + public PlaceholderAPIConfig(PlaceholderAPIPlugin i) { + plugin = i; + } + + public void loadDefConfig() { + + FileConfiguration c = plugin.getConfig(); + + c.options().header("PlaceholderAPI version "+plugin.getDescription().getVersion()+"" + + "\nCreated by extended_clip" + + "\n" + + "\nNo placeholders are provided with this plugin." + + "\nDownload placeholders with /papi ecloud" + + "\nExample:" + + "\n/papi ecloud refresh" + + "\n/papi ecloud list all" + + "\n/papi ecloud list all 2" + + "\n/papi ecloud download Player" + + "\n/papi ecloud download Vault" + + "\n/papi reload"); + + c.set("auto_install_expansions", null); + c.set("cloud_enable_unverified_expansions", null); + c.addDefault("check_updates", true); + c.addDefault("cloud_enabled", true); + c.addDefault("cloud_allow_unverified_expansions", false); + c.addDefault("boolean.true", "yes"); + c.addDefault("boolean.false", "no"); + c.addDefault("date_format", "MM/dd/yy HH:mm:ss"); + c.options().copyDefaults(true); + plugin.saveConfig(); + plugin.reloadConfig(); + } + + public boolean checkUpdates() { + return plugin.getConfig().getBoolean("check_updates"); + } + + public boolean cloudAllowUnverifiedExpansions() { + return plugin.getConfig().getBoolean("cloud_allow_unverified_expansions"); + } + + public boolean isCloudEnabled() { + return plugin.getConfig().getBoolean("cloud_enabled"); + } + + public void setCloudEnabled(boolean b) { + plugin.getConfig().set("cloud_enabled", b); + plugin.saveConfig(); + plugin.reloadConfig(); + } + + public String booleanTrue() { + return plugin.getConfig().getString("boolean.true"); + } + + public String booleanFalse() { + return plugin.getConfig().getString("boolean.false"); + } + + public String dateFormat() { + return plugin.getConfig().getString("date_format"); + } +} diff --git a/src/main/java/me/clip/placeholderapi/events/PlaceholderHookUnloadEvent.java b/src/main/java/me/clip/placeholderapi/events/PlaceholderHookUnloadEvent.java new file mode 100644 index 0000000..b019b38 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/events/PlaceholderHookUnloadEvent.java @@ -0,0 +1,35 @@ +package me.clip.placeholderapi.events; + +import me.clip.placeholderapi.PlaceholderHook; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class PlaceholderHookUnloadEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + private String plugin; + private PlaceholderHook hook; + + public PlaceholderHookUnloadEvent(String plugin, PlaceholderHook placeholderHook) { + this.plugin = plugin; + this.hook = placeholderHook; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public String getHookName() { + return plugin; + } + + public PlaceholderHook getHook() { + return hook; + } +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/Cacheable.java b/src/main/java/me/clip/placeholderapi/expansion/Cacheable.java new file mode 100644 index 0000000..6325524 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/Cacheable.java @@ -0,0 +1,17 @@ +package me.clip.placeholderapi.expansion; + +/** + * This interface allows a class which extends a {@link PlaceholderExpansion} + * to have the clear method called when the implementing expansion is unregistered + * from PlaceholderAPI. + * This is useful if we want to do things when the implementing hook is unregistered + * @author Ryan McCarthy + * + */ +public interface Cacheable { + + /** + * Called when the implementing class is unregistered from PlaceholderAPI + */ + void clear(); +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/Cleanable.java b/src/main/java/me/clip/placeholderapi/expansion/Cleanable.java new file mode 100644 index 0000000..9c29df5 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/Cleanable.java @@ -0,0 +1,19 @@ +package me.clip.placeholderapi.expansion; + +import org.bukkit.entity.Player; + +/** + * This interface allows a class which extends a {@link PlaceholderExpansion} + * to have the cleanup method called every time a player leaves the server. + * This is useful if we want to clean up after the player + * @author Ryan McCarthy + * + */ +public interface Cleanable { + + /** + * Called when a player leaves the server + * @param p (@link Player} who left the server + */ + void cleanup(Player p); +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/Configurable.java b/src/main/java/me/clip/placeholderapi/expansion/Configurable.java new file mode 100644 index 0000000..9249416 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/Configurable.java @@ -0,0 +1,21 @@ +package me.clip.placeholderapi.expansion; + +import java.util.Map; + +/** + * Any {@link PlaceholderExpansion} class which implements configurable will + * have any options listed in the getDefaults map automatically added to the PlaceholderAPI config.yml file + * @author Ryan McCarthy + * + */ +public interface Configurable { + + /** + * This method will be called before the implementing class is registered + * to obtain a map of configuration options that the implementing class needs + * These paths and values will be added to the PlaceholderAPI config.yml in the configuration section + * expansions.(placeholder identifier).(your key): (your value) + * @return Map of config path / values which need to be added / removed from the PlaceholderAPI config.yml file + */ + Map getDefaults(); +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/ExpansionManager.java b/src/main/java/me/clip/placeholderapi/expansion/ExpansionManager.java new file mode 100644 index 0000000..ff57bb2 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/ExpansionManager.java @@ -0,0 +1,191 @@ +package me.clip.placeholderapi.expansion; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.event.Listener; + +import me.clip.placeholderapi.PlaceholderHook; +import me.clip.placeholderapi.expansion.cloud.CloudExpansion; +import me.clip.placeholderapi.util.FileUtil; + +public final class ExpansionManager { + + private PlaceholderAPIPlugin plugin; + + private final Map cache = new HashMap<>(); + + public ExpansionManager(PlaceholderAPIPlugin instance) { + plugin = instance; + } + + public void clean() { + cache.clear(); + } + + public PlaceholderExpansion getCachedExpansion(String plugin) { + return cache.getOrDefault(plugin, null); + } + + public boolean removeCachedExpansion(String identifier) { + return cache.remove(identifier) != null; + } + + public PlaceholderExpansion getRegisteredExpansion(String name) { + for (Entry hook : PlaceholderAPI.getPlaceholders().entrySet()) { + if (hook.getValue() instanceof PlaceholderExpansion) { + if (name.equalsIgnoreCase(hook.getKey())) { + return (PlaceholderExpansion) hook.getValue(); + } + } + } + return null; + } + + public boolean registerExpansion(PlaceholderExpansion c) { + + if (c == null || c.getIdentifier() == null) { + return false; + } + + if (c instanceof Configurable) { + + Map defaults = ((Configurable) c).getDefaults(); + + String pre = "expansions." + c.getIdentifier() + "."; + + if (defaults != null && !defaults.isEmpty()) { + + FileConfiguration cfg = plugin.getConfig(); + + boolean save = false; + + for (Entry entries : defaults.entrySet()) { + if (entries.getKey() == null || entries.getKey().isEmpty()) { + continue; + } + + if (entries.getValue() == null) { + if (cfg.contains(pre + entries.getKey())) { + save = true; + cfg.set(pre + entries.getKey(), null); + } + } else { + if (!cfg.contains(pre + entries.getKey())) { + save = true; + cfg.set(pre + entries.getKey(), entries.getValue()); + } + } + } + + if (save) { + plugin.saveConfig(); + plugin.reloadConfig(); + } + } + } + + if (c instanceof VersionSpecific) { + VersionSpecific nms = (VersionSpecific) c; + if (!nms.isCompatibleWith(PlaceholderAPIPlugin.getServerVersion())) { + plugin.getLogger().info("Your server version is not compatible with expansion: " + c.getIdentifier() + + " version: " + c.getVersion()); + return false; + } + } + + if (!c.canRegister()) { + if (c.getPlugin() != null) { + cache.put(c.getPlugin().toLowerCase(), c); + } + return false; + } + + if (!c.register()) { + if (c.getPlugin() != null) { + cache.put(c.getPlugin().toLowerCase(), c); + } + return false; + } + + if (c instanceof Listener) { + Listener l = (Listener) c; + Bukkit.getPluginManager().registerEvents(l, plugin); + plugin.getLogger().info("Registered event listener for expansion: " + c.getIdentifier()); + } + + plugin.getLogger().info("Successfully registered expansion: " + c.getIdentifier()); + + if (c instanceof Taskable) { + ((Taskable) c).start(); + plugin.getLogger().info("Started scheduled task for expansion: " + c.getIdentifier()); + } + + return true; + } + + public void registerAllExpansions() { + + if (plugin == null) { + return; + } + + List> subs = FileUtil.getClasses("expansions", PlaceholderExpansion.class); + + if (subs == null || subs.isEmpty()) { + return; + } + + for (Class klass : subs) { + + if (klass == null) { + continue; + } + + try { + + PlaceholderExpansion ex = null; + + Constructor[] c = klass.getConstructors(); + + if (c.length == 0) { + ex = (PlaceholderExpansion) klass.newInstance(); + } else { + for (Constructor con : c) { + if (con.getParameterTypes().length == 0) { + ex = (PlaceholderExpansion) klass.newInstance(); + break; + } + } + } + + if (ex == null) { + continue; + } + + if (registerExpansion(ex)) { + if (plugin.getExpansionCloud() != null) { + CloudExpansion ce = plugin.getExpansionCloud().getCloudExpansion(ex.getIdentifier()); + if (ce != null) { + ce.setHasExpansion(true); + if (!ce.getVersion().equals(ex.getVersion())) { + ce.setShouldUpdate(true); + } + } + } + } + + } catch (Throwable t) { + plugin.getLogger().severe("Failed to load placeholder expansion from class: " + klass.getName()); + plugin.getLogger().severe(t.getMessage()); + } + } + } +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/NMSVersion.java b/src/main/java/me/clip/placeholderapi/expansion/NMSVersion.java new file mode 100644 index 0000000..19b9f35 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/NMSVersion.java @@ -0,0 +1,37 @@ +package me.clip.placeholderapi.expansion; + +public enum NMSVersion { + + UNKNOWN("unknown"), + SPIGOT_1_7_R1("v1_7_R1"), + SPIGOT_1_7_R2("v1_7_R2"), + SPIGOT_1_7_R3("v1_7_R3"), + SPIGOT_1_7_R4("v1_7_R4"), + SPIGOT_1_8_R1("v1_8_R1"), + SPIGOT_1_8_R2("v1_8_R2"), + SPIGOT_1_8_R3("v1_8_R3"), + SPIGOT_1_9_R1("v1_9_R1"), + SPIGOT_1_9_R2("v1_9_R2"), + SPIGOT_1_10_R1("v1_10_R1"), + SPIGOT_1_11_R1("v1_11_R1"), + SPIGOT_1_12_R1("v1_12_R1"); + + private String version; + + NMSVersion(String version) { + this.version = version; + } + + public String getVersion() { + return version; + } + + public static NMSVersion getVersion(String version) { + for (NMSVersion v : values()) { + if (v.getVersion().equalsIgnoreCase(version)) { + return v; + } + } + return NMSVersion.UNKNOWN; + } +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java b/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java new file mode 100644 index 0000000..0e69acb --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java @@ -0,0 +1,145 @@ +package me.clip.placeholderapi.expansion; + +import java.util.List; + +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import me.clip.placeholderapi.PlaceholderHook; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; + +public abstract class PlaceholderExpansion extends PlaceholderHook { + + /** + * The name of this expansion + * @return identifier used for expansion if no name present + */ + public String getName() { + return getIdentifier(); + } + /** + * Get the identifier that this placeholder expansion uses to be passed placeholder requests + * @return placeholder identifier that is associated with this class + */ + public abstract String getIdentifier(); + + /** + * Get the plugin that this expansion hooks into. + * This will ensure the expansion is added to a cache if canRegister() returns false + * get. + * The expansion will be removed from the cache + * once a plugin loads with the name that is here and the expansion is registered + * @return placeholder identifier that is associated with this class + */ + public abstract String getPlugin(); + + /** + * Get the author of this PlaceholderExpansion + * @return name of the author for this expansion + */ + public abstract String getAuthor(); + + /** + * Get the version of this PlaceholderExpansion + * @return current version of this expansion + */ + public abstract String getVersion(); + + /** + * Check if a placeholder has already been registered with this identifier + * @return true if the identifier for this expansion has already been registered + */ + public boolean isRegistered() { + Validate.notNull(getIdentifier(), "Placeholder identifier can not be null!"); + return PlaceholderAPI.getRegisteredIdentifiers().contains(getIdentifier()); + } + + /** + * If any requirements are required to be checked before this hook can register, add them here + * @return true if this hook meets all the requirements to register + */ + public boolean canRegister() { + return getPlugin() == null || Bukkit.getPluginManager().getPlugin(getPlugin()) != null; + } + + /** + * Attempt to register this PlaceholderExpansion with PlaceholderAPI + * @return true if this class and identifier have been successfully registered with PlaceholderAPI + */ + public boolean register() { + Validate.notNull(getIdentifier(), "Placeholder identifier can not be null!"); + return PlaceholderAPI.registerPlaceholderHook(getIdentifier(), this); + } + + + + /** + * Quick getter for the {@link PlaceholderAPIPlugin} instance + * @return {@link PlaceholderAPIPlugin} instance + */ + public PlaceholderAPIPlugin getPlaceholderAPI() { + return PlaceholderAPIPlugin.getInstance(); + } + + /** + * A short description of this expansion + * @return null if no description + */ + public String getDescription() { + return null; + } + + /** + * The url link to this expansion page + * @return null if no link + */ + public String getLink() { + return null; + } + + /** + * A list of all valid placeholders + * @return null if you dont care + */ + public List getPlaceholders() { + return null; + } + + public String getString(String path, String def) { + return getPlaceholderAPI().getConfig().getString("expansions." + getIdentifier() + "." + path, def); + } + + public int getInt(String path, int def) { + return getPlaceholderAPI().getConfig().getInt("expansions." + getIdentifier() + "." + path, def); + } + + public long getLong(String path, long def) { + return getPlaceholderAPI().getConfig().getLong("expansions." + getIdentifier() + "." + path, def); + } + + public double getDouble(String path, double def) { + return getPlaceholderAPI().getConfig().getDouble("expansions." + getIdentifier() + "." + path, def); + } + + public List getStringList(String path) { + return getPlaceholderAPI().getConfig().getStringList("expansions." + getIdentifier() + "." + path); + } + + public Object get(String path, Object def) { + return getPlaceholderAPI().getConfig().get("expansions." + getIdentifier() + "." + path, def); + } + + public ConfigurationSection getConfigSection(String path) { + return getPlaceholderAPI().getConfig().getConfigurationSection("expansions." + getIdentifier() + "." + path); + } + + public ConfigurationSection getConfigSection() { + return getPlaceholderAPI().getConfig().getConfigurationSection("expansions." + getIdentifier()); + } + + public boolean configurationContains(String path) { + return getPlaceholderAPI().getConfig().contains("expansions." + getIdentifier() + "." + path); + } +} \ No newline at end of file diff --git a/src/main/java/me/clip/placeholderapi/expansion/Relational.java b/src/main/java/me/clip/placeholderapi/expansion/Relational.java new file mode 100644 index 0000000..50c73f4 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/Relational.java @@ -0,0 +1,7 @@ +package me.clip.placeholderapi.expansion; + +import org.bukkit.entity.Player; + +public interface Relational { + String onPlaceholderRequest(Player one, Player two, String identifier); +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/Taskable.java b/src/main/java/me/clip/placeholderapi/expansion/Taskable.java new file mode 100644 index 0000000..b4baa07 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/Taskable.java @@ -0,0 +1,17 @@ +package me.clip.placeholderapi.expansion; + + +public interface Taskable { + + /** + * Called when the implementing class has successfully been registered to the placeholder map + * Tasks that need to be performed when this expansion is registered should go here + */ + void start(); + + /** + * Called when the implementing class has been unregistered from PlaceholderAPI + * Tasks that need to be performed when this expansion has unregistered should go here + */ + void stop(); +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/Version.java b/src/main/java/me/clip/placeholderapi/expansion/Version.java new file mode 100644 index 0000000..de57b92 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/Version.java @@ -0,0 +1,21 @@ +package me.clip.placeholderapi.expansion; + +public class Version { + + private boolean isSpigot; + + private String version; + + public Version(String version, boolean isSpigot) { + this.version = version; + this.isSpigot = isSpigot; + } + + public String getVersion() { + return version == null ? "unknown" : version; + } + + public boolean isSpigot() { + return isSpigot; + } +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/VersionSpecific.java b/src/main/java/me/clip/placeholderapi/expansion/VersionSpecific.java new file mode 100644 index 0000000..01f205b --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/VersionSpecific.java @@ -0,0 +1,19 @@ +package me.clip.placeholderapi.expansion; + +/** + * Placeholder expansions which use NMS code should be version specific. + * Implementing this class allows you to perform checks based on the version the server is running. + * The isCompatibleWith method will be passed the server version and allow you to return if your expansion is compatible with that version. + * @author Ryan McCarthy + * + */ +public interface VersionSpecific { + + /** + * This method is called before the expansion is attempted to be registered + * The server version will be passed to this method so you know what version the server is currently running. + * + * @return true if your expansion is compatible with the version the server is running. + */ + boolean isCompatibleWith(Version v); +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/cloud/CloudExpansion.java b/src/main/java/me/clip/placeholderapi/expansion/cloud/CloudExpansion.java new file mode 100644 index 0000000..214b36b --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/cloud/CloudExpansion.java @@ -0,0 +1,80 @@ +package me.clip.placeholderapi.expansion.cloud; + +import java.util.concurrent.TimeUnit; + +import me.clip.placeholderapi.util.TimeUtil; + + +public class CloudExpansion { + + private String name, author, version, description, link, releaseNotes; + + private boolean hasExpansion, shouldUpdate; + + private long lastUpdate; + + public CloudExpansion(String name, String author, String version, String description, String link) { + this.name = name; + this.author = author; + this.version = version; + this.description = description; + this.link = link; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + public String getVersion() { + return version; + } + + public String getDescription() { + return description; + } + + public String getLink() { + return link; + } + + public boolean hasExpansion() { + return hasExpansion; + } + + public void setHasExpansion(boolean hasExpansion) { + this.hasExpansion = hasExpansion; + } + + public boolean shouldUpdate() { + return shouldUpdate; + } + + public void setShouldUpdate(boolean shouldUpdate) { + this.shouldUpdate = shouldUpdate; + } + + public long getLastUpdate() { + return lastUpdate; + } + + public void setLastUpdate(long lastUpdate) { + this.lastUpdate = lastUpdate; + } + + public String getReleaseNotes() { + return releaseNotes; + } + + public void setReleaseNotes(String releaseNotes) { + this.releaseNotes = releaseNotes; + } + + public String getTimeSinceLastUpdate() { + int time = (int) TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - getLastUpdate()); + return TimeUtil.getTime(time); + } +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/cloud/ExpansionCloudManager.java b/src/main/java/me/clip/placeholderapi/expansion/cloud/ExpansionCloudManager.java new file mode 100644 index 0000000..9c8633a --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/cloud/ExpansionCloudManager.java @@ -0,0 +1,422 @@ +package me.clip.placeholderapi.expansion.cloud; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.util.Msg; + +public class ExpansionCloudManager { + + private final File dir; + + private PlaceholderAPIPlugin plugin; + + private final TreeMap remote = new TreeMap<>(); + + private final List downloading = new ArrayList<>(); + + private int toUpdate = 0; + + public ExpansionCloudManager(PlaceholderAPIPlugin instance) { + + plugin = instance; + dir = new File(instance.getDataFolder() + File.separator + "expansions"); + + if (!dir.exists()) { + try { + dir.mkdirs(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + public void clean() { + remote.clear(); + toUpdate = 0; + downloading.clear(); + } + + public boolean isDownloading(String expansion) { + return downloading.contains(expansion); + } + + public Map getCloudExpansions() { + return remote; + } + + public CloudExpansion getCloudExpansion(String name) { + + for (CloudExpansion ex : remote.values()) { + if (ex.getName().equalsIgnoreCase(name)) { + return ex; + } + } + return null; + } + + public int getCloudAuthorCount() { + + if (remote == null) { + return 0; + } + + List temp = new ArrayList<>(); + + for (CloudExpansion ex : remote.values()) { + if (!temp.contains(ex.getAuthor())) { + temp.add(ex.getAuthor()); + } + } + return temp.size(); + } + + public int getToUpdateCount() { + return toUpdate; + } + + public void decrementToUpdateCount() { + if (toUpdate > 0) { + toUpdate--; + } + } + + public Map getAllByAuthor(String author) { + + if (remote.isEmpty()) { + return null; + } + + TreeMap byAuthor = new TreeMap<>(); + boolean first = true; + + for (CloudExpansion ex : remote.values()) { + if (ex.getAuthor().equalsIgnoreCase(author)) { + if (first) { + first = false; + byAuthor.put(0, ex); + } else { + byAuthor.put(byAuthor.lastKey()+1, ex); + } + } + } + + if (byAuthor.isEmpty()) { + return null; + } + return byAuthor; + } + + public Map getAllInstalled() { + + if (remote.isEmpty()) { + return null; + } + + TreeMap has = new TreeMap<>(); + + boolean first = true; + + for (CloudExpansion ex : remote.values()) { + if (ex.hasExpansion()) { + if (first) { + first = false; + has.put(0, ex); + } else { + has.put(has.lastKey()+1, ex); + } + } + } + + if (has.isEmpty()) { + return null; + } + return has; + } + + public int getPagesAvailable(Map map, int amount) { + + if (map == null) { + return 0; + } + + int pages = map.size() > 0 ? 1 : 0; + + if (pages == 0) { + return pages; + } + + if (map.size() > amount) { + + pages = map.size()/amount; + + if (map.size() % amount > 0) { + pages = pages+1; + } + } + return pages; + } + + public Map getPage(Map map, int page) { + + if (map == null || map.size() == 0) { + return null; + } + + if (page > getPagesAvailable(map, 10)) { + return null; + } + + int end = 10*page; + + int start = end-10; + + end = end-1; + + int size = map.size(); + + if (end > size) { + end = size-1; + } + + TreeMap ex = new TreeMap<>(); + + for (int i = start ; i <= end ; i++) { + ex.put(i, map.get(i)); + } + return ex.isEmpty() ? null : ex; + } + + public void fetch() { + + plugin.getLogger().info("Fetching available expansion list..."); + + toUpdate = 0; + + new BukkitRunnable() { + + @Override + public void run() { + + StringBuilder sb; + + try { + + URL api = new URL("http://api.extendedclip.com/"); + + HttpURLConnection connection = (HttpURLConnection) api.openConnection(); + + connection.setRequestMethod("GET"); + + connection.connect(); + + BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream())); + + sb = new StringBuilder(); + + String line; + + while ((line = br.readLine()) != null) { + sb.append(line); + } + + br.close(); + connection.disconnect(); + + } catch (Exception e) { + return; + } + + String json = sb.toString(); + JSONParser parser = new JSONParser(); + Object obj = null; + + try { + obj = parser.parse(json); + } catch (ParseException e) { + e.printStackTrace(); + } + + if (obj == null) { + return; + } + + List unsorted = new ArrayList<>(); + + if (obj instanceof JSONObject) { + + JSONObject jo = (JSONObject) obj; + + for (Object o : jo.keySet()) { + + JSONObject sub = (JSONObject) jo.get(o); + String name = o.toString(); + String author = (String) sub.get("author"); + String version = (String) sub.get("version"); + String link = (String) sub.get("link"); + String description = (String) sub.get("description"); + String notes = ""; + long update = -1; + + if (sub.get("release_notes") != null) { + notes = (String) sub.get("release_notes"); + } + + if (sub.get("last_update") != null) { + + Object u = sub.get("last_update"); + + if (u instanceof Long) { + update = (long) sub.get("last_update"); + } + } + + CloudExpansion ce = new CloudExpansion(name, author, version, description, link); + + ce.setReleaseNotes(notes); + + ce.setLastUpdate(update); + + PlaceholderExpansion ex = plugin.getExpansionManager().getRegisteredExpansion(name); + + if (ex != null && ex.isRegistered()) { + ce.setHasExpansion(true); + if (!ex.getVersion().equals(version)) { + ce.setShouldUpdate(true); + toUpdate++; + } + } + + unsorted.add(ce); + } + + int count = 0; + + unsorted.sort(Comparator.comparing(CloudExpansion::getLastUpdate).reversed()); + + for (CloudExpansion e : unsorted) { + remote.put(count, e); + count++; + } + + plugin.getLogger().info(count + " placeholder expansions are available on the cloud."); + plugin.getLogger().info(toUpdate + " expansions you use have updates."); + } + } + }.runTaskAsynchronously(plugin); + } + + private void download(URL url, String name) throws IOException { + + InputStream is = null; + + FileOutputStream fos = null; + + try { + + URLConnection urlConn = url.openConnection(); + + is = urlConn.getInputStream(); + + fos = new FileOutputStream(dir.getAbsolutePath() + File.separator + "Expansion-" + name + ".jar"); + + byte[] buffer = new byte[1024]; + + int l; + + while ((l = is.read(buffer)) > 0) { + fos.write(buffer, 0, l); + } + } finally { + try { + if (is != null) { + is.close(); + } + } finally { + if (fos != null) { + fos.close(); + } + } + } + } + + public void downloadExpansion(final String player, final CloudExpansion ex) { + + if (downloading.contains(ex.getName())) { + return; + } + + if (ex.getLink() == null) { + return; + } + + downloading.add(ex.getName()); + + plugin.getLogger().info("Attempting download of expansion: " + ex.getName() + (player != null ? " by user: " + player : "") + " from url: " + ex.getLink()); + + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + + try { + + download(new URL(ex.getLink()), ex.getName()); + + plugin.getLogger().info("Download of expansion: " + ex.getName() + " complete!"); + + } catch (Exception e) { + + plugin.getLogger().warning("Failed to download expansion: " + ex.getName() + " from: " + ex.getLink()); + + Bukkit.getScheduler().runTask(plugin, () -> { + + downloading.remove(ex.getName()); + + if (player != null) { + Player p = Bukkit.getPlayer(player); + + if (p != null) { + Msg.msg(p, "&cThere was a problem downloading expansion: &f" + ex.getName()); + } + } + }); + + return; + } + + Bukkit.getScheduler().runTask(plugin, () -> { + + downloading.remove(ex.getName()); + + if (player != null) { + + Player p = Bukkit.getPlayer(player); + + if (p != null) { + Msg.msg(p, "&aExpansion &f" + ex.getName() + " &adownload complete!"); + } + } + }); + + }); + } +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/cloud/ExpansionComparator.java b/src/main/java/me/clip/placeholderapi/expansion/cloud/ExpansionComparator.java new file mode 100644 index 0000000..55484a9 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/cloud/ExpansionComparator.java @@ -0,0 +1,28 @@ +package me.clip.placeholderapi.expansion.cloud; + +import java.util.Comparator; + +public class ExpansionComparator implements Comparator { + + private static final int AUTHOR = 0; + private static final int LAST = 1; + private static final int RATING = 2; + private static final int TO_UPDATE = 3; + private int compare_mode = LAST; + + public ExpansionComparator() { } + + public ExpansionComparator(int compare_mode) { + this.compare_mode = compare_mode; + } + + @Override + public int compare(CloudExpansion o1, CloudExpansion o2) { + switch (compare_mode) { + case LAST: + return (int) (o2.getLastUpdate() - o1.getLastUpdate()); + default: + return o1.getAuthor().compareTo(o2.getAuthor()); + } + } +} diff --git a/src/main/java/me/clip/placeholderapi/external/EZPlaceholderHook.java b/src/main/java/me/clip/placeholderapi/external/EZPlaceholderHook.java new file mode 100644 index 0000000..ffab5dc --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/external/EZPlaceholderHook.java @@ -0,0 +1,38 @@ +package me.clip.placeholderapi.external; + +import me.clip.placeholderapi.PlaceholderAPI; +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.Plugin; + +import me.clip.placeholderapi.PlaceholderHook; + +@Deprecated +public abstract class EZPlaceholderHook extends PlaceholderHook { + + private String identifier; + + private String plugin; + + public EZPlaceholderHook(Plugin plugin, String identifier) { + Validate.notNull(plugin, "Plugin can not be null!"); + Validate.notNull(identifier, "Placeholder name can not be null!"); + this.identifier = identifier; + this.plugin = plugin.getName(); + } + + public boolean isHooked() { + return PlaceholderAPI.getRegisteredPlaceholderPlugins().contains(identifier); + } + + public boolean hook() { + return PlaceholderAPI.registerPlaceholderHook(identifier, this); + } + + public String getPlaceholderName() { + return identifier; + } + + public String getPluginName() { + return plugin; + } +} diff --git a/src/main/java/me/clip/placeholderapi/metrics/Metrics.java b/src/main/java/me/clip/placeholderapi/metrics/Metrics.java new file mode 100644 index 0000000..b0e7d74 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/metrics/Metrics.java @@ -0,0 +1,666 @@ +package me.clip.placeholderapi.metrics; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import javax.net.ssl.HttpsURLConnection; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +/** + * bStats collects some data for plugin authors. + * Check out https://bStats.org/ to learn more about bStats! + */ +public class Metrics { + + static { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D + final String defaultPackage = new String( + new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); + final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure nobody just copy & pastes the example and use the wrong package names + if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/bukkit"; + + // Should failed requests be logged? + private static boolean logFailedRequests; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private final JavaPlugin plugin; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param plugin The plugin which stats should be submitted. + */ + public Metrics(JavaPlugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + this.plugin = plugin; + + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + + // Inform the server owners about bStats + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "This has nearly no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)" + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { } + } + + // Load the data + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + if (config.getBoolean("enabled", true)) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { } + } + // Register our service + Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, new Runnable() { + @Override + public void run() { + submitData(); + } + }); + } + }, 1000*60*5, 1000*60*30); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JSONObject getPluginData() { + JSONObject data = new JSONObject(); + + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.put("pluginName", pluginName); // Append the name of the plugin + data.put("pluginVersion", pluginVersion); // Append the version of the plugin + JSONArray customCharts = new JSONArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JSONObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.put("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JSONObject getServerData() { + // Minecraft specific data + int playerAmount; + try { + // Around MC 1.8 the return type was changed to a collection from an array, + // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed + } + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = org.bukkit.Bukkit.getVersion(); + bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JSONObject data = new JSONObject(); + + data.put("serverUUID", serverUUID); + + data.put("playerAmount", playerAmount); + data.put("onlineMode", onlineMode); + data.put("bukkitVersion", bukkitVersion); + + data.put("javaVersion", javaVersion); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JSONObject data = getServerData(); + + JSONArray pluginData = new JSONArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + + for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { + try { + pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider())); + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } + } + } catch (NoSuchFieldException ignored) { } + } + + data.put("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + } + + /** + * Sends the data to the bStats server. + * + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(JSONObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + connection.getInputStream().close(); // We don't care about the response - Just send our data :) + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes("UTF-8")); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + private JSONObject getRequestJsonObject() { + JSONObject chart = new JSONObject(); + chart.put("chartId", chartId); + try { + JSONObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.put("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JSONObject getChartData() throws Exception; + + } + + /** + * Represents a custom simple pie. + */ + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom drilldown pie. + */ + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JSONObject value = new JSONObject(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.put(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + values.put(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JSONArray categoryValues = new JSONArray(); + categoryValues.add(entry.getValue()); + values.put(entry.getKey(), categoryValues); + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; + JSONArray categoryValues = new JSONArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.put(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } +} \ No newline at end of file diff --git a/src/main/java/me/clip/placeholderapi/updatechecker/UpdateChecker.java b/src/main/java/me/clip/placeholderapi/updatechecker/UpdateChecker.java new file mode 100644 index 0000000..ade6e77 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/updatechecker/UpdateChecker.java @@ -0,0 +1,106 @@ +package me.clip.placeholderapi.updatechecker; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.regex.Pattern; + +import javax.net.ssl.HttpsURLConnection; + +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +/** + * thanks maxim + */ +public class UpdateChecker implements Listener { + + private PlaceholderAPIPlugin plugin; + + private final int resourceId = 6245; + + private static String latestVersion = ""; + + private static boolean updateAvailable = false; + + public UpdateChecker(PlaceholderAPIPlugin i) { + plugin = i; + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + if (checkForUpdate()) { + Bukkit.getScheduler().runTask(plugin, () -> { + plugin.getLogger().info("An update for PlaceholderAPI (v" + getLatestVersion() + ") is available at:"); + plugin.getLogger().info("https://www.spigotmc.org/resources/placeholderapi." + resourceId + "/"); + register(); + }); + } + }); + } + + private void register() { + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onJoin(PlayerJoinEvent e) { + if (e.getPlayer().isOp()) { + e.getPlayer().sendMessage(ChatColor.translateAlternateColorCodes('&', "&bAn update for &fPlaceholder&7API &e(&fPlaceholder&7API &fv" + getLatestVersion() + "&e)")); + e.getPlayer().sendMessage(ChatColor.translateAlternateColorCodes('&', "&bis available at &ehttps://www.spigotmc.org/resources/placeholderapi." + resourceId + "/")); + } + } + + private String getSpigotVersion() { + try { + HttpsURLConnection con = (HttpsURLConnection) new URL("https://api.spigotmc.org/legacy/update.php?resource=" + resourceId).openConnection(); + con.setRequestMethod("GET"); + String version = new BufferedReader(new InputStreamReader(con.getInputStream())).readLine(); + if (version.length() <= 7) { + return version; + } + } catch (Exception ex) { + plugin.getLogger().info("Failed to check for a update on spigot."); + } + return null; + } + + private boolean checkHigher(String currentVersion, String newVersion) { + String current = toReadable(currentVersion); + String newVers = toReadable(newVersion); + return current.compareTo(newVers) < 0; + } + + public boolean checkForUpdate() { + String version = getSpigotVersion(); + if (version != null) { + if (checkHigher(plugin.getDescription().getVersion(), version)) { + latestVersion = version; + updateAvailable = true; + return true; + } + } + return false; + } + + public static boolean updateAvailable() { + return updateAvailable; + } + + public static String getLatestVersion() { + return latestVersion; + } + + private String toReadable(String version) { + String[] split = Pattern.compile(".", Pattern.LITERAL).split(version.replace("v", "")); + version = ""; + for (String s : split) { + version += String.format("%4s", s); + } + return version; + } + + +} diff --git a/src/main/java/me/clip/placeholderapi/util/FileUtil.java b/src/main/java/me/clip/placeholderapi/util/FileUtil.java new file mode 100644 index 0000000..a526ccc --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/util/FileUtil.java @@ -0,0 +1,73 @@ +package me.clip.placeholderapi.util; + +import java.io.File; +import java.io.FilenameFilter; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +import me.clip.placeholderapi.PlaceholderAPIPlugin; + +public class FileUtil { + + public static List> getClasses(String folder, Class type) { + List> list = new ArrayList<>(); + try { + File f = new File(PlaceholderAPIPlugin.getInstance().getDataFolder(), folder); + if (!f.exists()) { + if (!f.mkdir()) { + PlaceholderAPIPlugin.getInstance().getLogger().severe("Failed to create " + folder + " folder!"); + } + return list; + } + FilenameFilter fileNameFilter = (dir, name) -> { + int i = name.lastIndexOf('.'); + return i > 0 && name.substring(i).equals(".jar"); + }; + File[] jars = f.listFiles(fileNameFilter); + if (jars == null) { + return list; + } + for (File file : jars) { + list = gather(file.toURI().toURL(), list, type); + } + return list; + } catch (Throwable t) {} + return null; + } + + private static List> gather(URL jar, List> list, Class clazz) { + if (list == null) { + list = new ArrayList<>(); + } + try { + URLClassLoader cl = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader()); + JarInputStream jis = new JarInputStream(jar.openStream()); + while (true) { + JarEntry j = jis.getNextJarEntry(); + if (j == null) { + break; + } + String name = j.getName(); + if (name == null || name.isEmpty()) { + continue; + } + if (name.endsWith(".class")) { + name = name.replace("/", "."); + String cname = name.substring(0, name.lastIndexOf(".class")); + Class c = cl.loadClass(cname); + if (clazz.isAssignableFrom(c)) { + list.add(c); + } + } + } + cl.close(); + jis.close(); + } catch (Throwable t) { + } + return list; + } +} diff --git a/src/main/java/me/clip/placeholderapi/util/Msg.java b/src/main/java/me/clip/placeholderapi/util/Msg.java new file mode 100644 index 0000000..851aaad --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/util/Msg.java @@ -0,0 +1,12 @@ +package me.clip.placeholderapi.util; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +public class Msg { + + public static void msg(CommandSender s, String msg) { + s.sendMessage(ChatColor.translateAlternateColorCodes('&', msg)); + } + +} diff --git a/src/main/java/me/clip/placeholderapi/util/TimeFormat.java b/src/main/java/me/clip/placeholderapi/util/TimeFormat.java new file mode 100644 index 0000000..1e3baf2 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/util/TimeFormat.java @@ -0,0 +1,9 @@ +package me.clip.placeholderapi.util; + +public enum TimeFormat { + + DAYS, + HOURS, + MINUTES, + SECONDS +} diff --git a/src/main/java/me/clip/placeholderapi/util/TimeUtil.java b/src/main/java/me/clip/placeholderapi/util/TimeUtil.java new file mode 100644 index 0000000..d9c2de5 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/util/TimeUtil.java @@ -0,0 +1,183 @@ +package me.clip.placeholderapi.util; + +public class TimeUtil { + + public static String getRemaining(int seconds, TimeFormat type) { + + if (seconds < 60) { + switch(type) { + case DAYS: + return "0"; + case HOURS: + return "0"; + case MINUTES: + return "0"; + case SECONDS: + return String.valueOf(seconds); + } + return String.valueOf(seconds); + } + + int minutes = seconds / 60; + + int s = 60 * minutes; + + int secondsLeft = seconds - s; + + if (minutes < 60) { + switch(type) { + case DAYS: + return "0"; + case HOURS: + return "0"; + case MINUTES: + return String.valueOf(minutes); + case SECONDS: + return String.valueOf(secondsLeft); + } + return String.valueOf(seconds); + } + + if (minutes < 1440) { + + int hours = minutes / 60; + + int inMins = 60 * hours; + + int leftOver = minutes - inMins; + + switch(type) { + case DAYS: + return "0"; + case HOURS: + return String.valueOf(hours); + case MINUTES: + return String.valueOf(leftOver); + case SECONDS: + return String.valueOf(secondsLeft); + } + return String.valueOf(seconds); + } + + int days = minutes / 1440; + + int inMins = 1440 * days; + + int leftOver = minutes - inMins; + + + if (leftOver < 60) { + + switch (type) { + case DAYS: + return String.valueOf(days); + case HOURS: + return String.valueOf(0); + case MINUTES: + return String.valueOf(leftOver); + case SECONDS: + return String.valueOf(secondsLeft); + } + return String.valueOf(seconds); + + } else { + + int hours = leftOver / 60; + + int hoursInMins = 60 * hours; + + int minsLeft = leftOver - hoursInMins; + + switch (type) { + case DAYS: + return String.valueOf(days); + case HOURS: + return String.valueOf(hours); + case MINUTES: + return String.valueOf(minsLeft); + case SECONDS: + return String.valueOf(secondsLeft); + } + return String.valueOf(seconds); + } + } + + public static String getTime(int seconds) { + + if (seconds < 60) { + return seconds+"s"; + } + + int minutes = seconds / 60; + + int s = 60 * minutes; + + int secondsLeft = seconds - s; + + if (minutes < 60) { + if (secondsLeft > 0) { + return String.valueOf(minutes+"m "+secondsLeft+"s"); + } else { + return String.valueOf(minutes+"m"); + } + } + + if (minutes < 1440) { + + String time; + + int hours = minutes / 60; + + time = hours+"h"; + + int inMins = 60 * hours; + + int leftOver = minutes - inMins; + + if (leftOver >= 1) { + time = time+" "+leftOver+"m"; + } + + if (secondsLeft > 0) { + time = time+" "+secondsLeft+"s"; + } + + return time; + } + + String time; + + int days = minutes / 1440; + + time = days+"d"; + + int inMins = 1440 * days; + + int leftOver = minutes - inMins; + + if (leftOver >= 1) { + + if (leftOver < 60) { + + time = time+" "+leftOver+"m"; + } else { + + int hours = leftOver / 60; + + time = time+" "+hours+"h"; + + int hoursInMins = 60 * hours; + + int minsLeft = leftOver - hoursInMins; + + time = time+" "+minsLeft+"m"; + } + } + + if (secondsLeft > 0) { + time = time+" "+secondsLeft+"s"; + } + + return time; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..16e4edf --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,32 @@ +name: PlaceholderAPI +main: me.clip.placeholderapi.PlaceholderAPIPlugin +version: ${pom.version} +author: [extended_clip] +description: A placeholder provider +permissions: + placeholderapi.*: + description: ability to use all commands + children: + placeholderapi.admin: true + placeholderapi.admin: + description: ability to use all commands + children: + placeholderapi.list: true + placeholderapi.reload: true + placeholderapi.parse: true + placeholderapi.injector.chat.bypass: true + placeholderapi.injector.signs.bypass: true + placeholderapi.injector.anvil.bypass: true + placeholderapi.list: + description: ability to use the list command + default: op + placeholderapi.reload: + description: ability to use the reload command + default: op + placeholderapi.parse: + description: ability to use parse command + default: op +commands: + placeholderapi: + description: PlaceholderAPI command + aliases: [clipsplaceholderapi, papi]