├── src ├── config.yml ├── plugin.yml ├── variables.yml └── net │ └── TheDgtl │ └── iChat │ ├── iChatMeEvent.java │ ├── playerListener.java │ ├── dYamlConfiguration.java │ ├── iChat.java │ ├── VariableHandler.java │ ├── iChatAPI.java │ └── Metrics │ └── Metrics.java ├── README └── LICENSE /src/config.yml: -------------------------------------------------------------------------------- 1 | handle-me: true 2 | date-format: HH:mm:ss 3 | message-format: '+iname: +message' 4 | me-format: '* +name +message' 5 | iname-format: '+{wName}[+prefix+group+suffix&f] +displayname' 6 | me-permissions: false 7 | variable-refresh: 100 -------------------------------------------------------------------------------- /src/plugin.yml: -------------------------------------------------------------------------------- 1 | name: iChat 2 | main: net.TheDgtl.iChat.iChat 3 | version: 2.6.2 4 | author: Drakia 5 | website: http://thedgtl.net/ 6 | commands: 7 | ichat: 8 | description: Used to reload the plugin. 9 | usage: / reload - Used to reload iChat's config 10 | softdepend: [bPermissions, Permissions, PermissionsBukkit, PermissionsEx, GroupManager] -------------------------------------------------------------------------------- /src/variables.yml: -------------------------------------------------------------------------------- 1 | # iChat Variable Config 2 | # This is now the only method for defining variables 3 | users: 4 | Drakia: 5 | prefix: '&e' 6 | groups: 7 | Admin: 8 | prefix: '&c' 9 | suffix: '' 10 | Default: 11 | prefix: '' 12 | suffix: '' 13 | world: 14 | users: 15 | Drakia: 16 | prefix: '&a' 17 | wName: '[World] ' -------------------------------------------------------------------------------- /src/net/TheDgtl/iChat/iChatMeEvent.java: -------------------------------------------------------------------------------- 1 | package net.TheDgtl.iChat; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Event; 5 | import org.bukkit.event.HandlerList; 6 | 7 | public class iChatMeEvent extends Event { 8 | private String message; 9 | private Player player; 10 | 11 | private static final HandlerList handlers = new HandlerList(); 12 | 13 | public HandlerList getHandlers() { 14 | return handlers; 15 | } 16 | 17 | public static HandlerList getHandlerList() { 18 | return handlers; 19 | } 20 | 21 | public iChatMeEvent(final Player player, final String message) { 22 | this.player = player; 23 | this.message = message; 24 | } 25 | 26 | public String getMessage() { 27 | return message; 28 | } 29 | 30 | public Player getPlayer() { 31 | return player; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/net/TheDgtl/iChat/playerListener.java: -------------------------------------------------------------------------------- 1 | package net.TheDgtl.iChat; 2 | 3 | /** 4 | * iChat - A chat formatting plugin for Bukkit. 5 | * Copyright (C) 2011 Steven "Drakia" Scott 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | import org.bukkit.entity.Player; 22 | import org.bukkit.event.EventHandler; 23 | import org.bukkit.event.EventPriority; 24 | import org.bukkit.event.Listener; 25 | import org.bukkit.event.player.AsyncPlayerChatEvent; 26 | import org.bukkit.event.player.PlayerCommandPreprocessEvent; 27 | import org.bukkit.event.player.PlayerJoinEvent; 28 | import org.bukkit.event.player.PlayerQuitEvent; 29 | 30 | public class playerListener implements Listener { 31 | // Use this for permissions checking. 32 | iChat ichat; 33 | 34 | playerListener(iChat ichat) { 35 | this.ichat = ichat; 36 | } 37 | 38 | @EventHandler(priority = EventPriority.MONITOR) 39 | public void onPlayerJoin(PlayerJoinEvent event) { 40 | final Player p = event.getPlayer(); 41 | ichat.getServer().getScheduler().scheduleSyncDelayedTask(ichat, new Runnable() { 42 | public void run() { 43 | ichat.info.addPlayer(p); 44 | } 45 | }, 1); 46 | } 47 | 48 | @EventHandler(priority = EventPriority.MONITOR) 49 | public void onPlayerQuit(PlayerQuitEvent event) { 50 | ichat.info.removePlayer(event.getPlayer()); 51 | } 52 | 53 | @EventHandler 54 | public void onPlayerChat(AsyncPlayerChatEvent event) { 55 | if (event.isCancelled()) return; 56 | Player player = event.getPlayer(); 57 | String message = event.getMessage(); 58 | if (message == null) return; 59 | event.setFormat(ichat.API.parseChat(player, message, ichat.chatFormat)); 60 | } 61 | 62 | @EventHandler 63 | public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { 64 | if (!ichat.handleMe) return; 65 | if (event.isCancelled()) return; 66 | Player player = event.getPlayer(); 67 | String command = event.getMessage(); 68 | if (command == null) return; 69 | 70 | if (command.toLowerCase().startsWith("/me ")) { 71 | if (ichat.mePerm && !player.hasPermission("ichat.me")) { 72 | event.setCancelled(true); 73 | return; 74 | } 75 | ichat.info.addPlayer(player); 76 | String message = command.substring(command.indexOf(" ")).trim(); 77 | String formatted = ichat.API.parseChat(player, message, ichat.meFormat); 78 | 79 | // Call iChatMeEvent 80 | iChatMeEvent meEvent = new iChatMeEvent(player, message); 81 | ichat.getServer().getPluginManager().callEvent(meEvent); 82 | 83 | // Display in console, send to players, and cancel event 84 | ichat.getServer().broadcastMessage(formatted); 85 | event.setCancelled(true); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/net/TheDgtl/iChat/dYamlConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.TheDgtl.iChat; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.util.Map; 10 | import java.util.logging.Level; 11 | 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.configuration.InvalidConfigurationException; 14 | import org.bukkit.configuration.file.YamlConfiguration; 15 | import org.bukkit.configuration.file.YamlConstructor; 16 | import org.bukkit.configuration.file.YamlRepresenter; 17 | import org.yaml.snakeyaml.DumperOptions; 18 | import org.yaml.snakeyaml.Yaml; 19 | import org.yaml.snakeyaml.error.YAMLException; 20 | import org.yaml.snakeyaml.representer.Representer; 21 | 22 | public class dYamlConfiguration extends YamlConfiguration { 23 | private final DumperOptions yamlOptions = new DumperOptions(); 24 | private final Representer yamlRepresenter = new YamlRepresenter(); 25 | private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions); 26 | 27 | @Override 28 | public void load(InputStream stream) throws IOException, InvalidConfigurationException { 29 | if (stream == null) { 30 | throw new IllegalArgumentException("Stream cannot be null"); 31 | } 32 | 33 | Map input; 34 | try { 35 | input = (Map) yaml.load(stream); 36 | } catch (YAMLException e) { 37 | throw new InvalidConfigurationException(e); 38 | } catch (ClassCastException e) { 39 | throw new InvalidConfigurationException("Top level is not a Map."); 40 | } 41 | 42 | String header = parseHeader(stream); 43 | if (header.length() > 0) { 44 | options().header(header); 45 | } 46 | 47 | if (input != null) { 48 | convertMapsToSections(input, this); 49 | } 50 | } 51 | 52 | protected String parseHeader(InputStream stream) throws IOException { 53 | // Load from stream 54 | InputStreamReader reader = new InputStreamReader(stream); 55 | BufferedReader input = new BufferedReader(reader); 56 | 57 | StringBuilder result = new StringBuilder(); 58 | boolean readingHeader = true; 59 | boolean foundHeader = false; 60 | try { 61 | String line; 62 | while ((line = input.readLine()) != null && (readingHeader)) { 63 | if (line.startsWith(COMMENT_PREFIX)) { 64 | if (result.length() > 0) { 65 | result.append("\n"); 66 | } 67 | 68 | if (line.length() > COMMENT_PREFIX.length()) { 69 | result.append(line.substring(COMMENT_PREFIX.length())); 70 | } 71 | 72 | foundHeader = true; 73 | } else if ((foundHeader) && (line.length() == 0)) { 74 | result.append("\n"); 75 | } else if (foundHeader) { 76 | readingHeader = false; 77 | } 78 | } 79 | } finally { 80 | input.close(); 81 | } 82 | 83 | return result.toString(); 84 | } 85 | 86 | public static dYamlConfiguration loadConfiguration(File file) { 87 | if (file == null) { 88 | throw new IllegalArgumentException("File cannot be null"); 89 | } 90 | 91 | dYamlConfiguration config = new dYamlConfiguration(); 92 | 93 | try { 94 | config.load(file); 95 | } catch (FileNotFoundException ex) { 96 | } catch (IOException ex) { 97 | Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); 98 | } catch (InvalidConfigurationException ex) { 99 | Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file , ex); 100 | } 101 | 102 | return config; 103 | } 104 | 105 | /** 106 | * Creates a new {@link YamlConfiguration}, loading from the given stream. 107 | *

108 | * Any errors loading the Configuration will be logged and then ignored. 109 | * If the specified input is not a valid config, a blank config will be returned. 110 | * 111 | * @param stream Input stream 112 | * @return Resulting configuration 113 | * @throws IllegalArgumentException Thrown is stream is null 114 | */ 115 | public static dYamlConfiguration loadConfiguration(InputStream stream) { 116 | if (stream == null) { 117 | throw new IllegalArgumentException("Stream cannot be null"); 118 | } 119 | 120 | dYamlConfiguration config = new dYamlConfiguration(); 121 | 122 | try { 123 | config.load(stream); 124 | } catch (IOException ex) { 125 | Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex); 126 | } catch (InvalidConfigurationException ex) { 127 | Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex); 128 | } 129 | 130 | return config; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/net/TheDgtl/iChat/iChat.java: -------------------------------------------------------------------------------- 1 | package net.TheDgtl.iChat; 2 | 3 | /** 4 | * iChat - A chat formatting plugin for Bukkit. 5 | * Copyright (C) 2011 Steven "Drakia" Scott 6 | * Copyright (C) 2011 MiracleM4n 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.HashMap; 25 | import java.util.logging.Logger; 26 | 27 | import net.TheDgtl.iChat.Metrics.Metrics; 28 | import net.krinsoft.privileges.Privileges; 29 | 30 | import org.anjocaido.groupmanager.GroupManager; 31 | import org.bukkit.command.Command; 32 | import org.bukkit.command.CommandSender; 33 | import org.bukkit.configuration.file.FileConfiguration; 34 | import org.bukkit.entity.Player; 35 | import org.bukkit.plugin.Plugin; 36 | import org.bukkit.plugin.PluginManager; 37 | import org.bukkit.plugin.java.JavaPlugin; 38 | 39 | import ru.tehkode.permissions.bukkit.PermissionsEx; 40 | 41 | import com.nijiko.permissions.PermissionHandler; 42 | import com.nijikokun.bukkit.Permissions.Permissions; 43 | import com.platymuus.bukkit.permissions.PermissionsPlugin; 44 | 45 | public class iChat extends JavaPlugin implements Runnable { 46 | public PluginManager pm; 47 | 48 | // Permission Handler Name 49 | private String permHandler = ""; 50 | // Permissions 51 | public PermissionHandler permissions; 52 | public boolean permissions3; 53 | // bPermissions (PermissionSet) 54 | public Plugin bPerm; 55 | // PermissionsBukkit (Group) 56 | public PermissionsPlugin pbPlug; 57 | // PEX (PermissionUser) 58 | public PermissionsEx pexPlug; 59 | // GroupManader 60 | public GroupManager gMan; 61 | // Privileges 62 | public Privileges priv; 63 | 64 | // Vairable Handler 65 | public VariableHandler info; 66 | 67 | // Player Connect Time List 68 | public HashMap connectList; 69 | 70 | // Logging and Config 71 | public Logger log; 72 | FileConfiguration newConfig; 73 | 74 | // API 75 | public iChatAPI API; 76 | 77 | // Config variables 78 | public String iNameFormat = "[+prefix+group+suffix&f] +displayname"; 79 | public String chatFormat = "+iname: +message"; 80 | public String meFormat = "* +name +message"; 81 | public String dateFormat = "HH:mm:ss"; 82 | public Integer timeOffset = null; 83 | public boolean handleMe = true; 84 | public boolean mePerm = false; 85 | public int refreshTimeout = 100; 86 | 87 | public void onEnable() { 88 | API = new iChatAPI(this); 89 | pm = getServer().getPluginManager(); 90 | log = getServer().getLogger(); 91 | newConfig = this.getConfig(); 92 | 93 | setupPermissions(); 94 | loadConfig(); 95 | 96 | info = new VariableHandler(this); 97 | connectList = new HashMap(); 98 | 99 | // Register events 100 | pm.registerEvents(new playerListener(this), this); 101 | 102 | // Create the task for refreshing user data 103 | getServer().getScheduler().scheduleSyncRepeatingTask(this, this, 0, refreshTimeout); 104 | 105 | log.info(getDescription().getName() + " (v" + getDescription().getVersion() + ") enabled"); 106 | 107 | // Enable MCStats Metrics 108 | try { 109 | Metrics metrics = new Metrics(this); 110 | 111 | // Setup a graph for keeping track of Permission handler user 112 | Metrics.Graph phGraph = metrics.createGraph("Permission Handler"); 113 | phGraph.addPlotter(new Metrics.Plotter(permHandler) { 114 | @Override 115 | public int getValue() { 116 | return 1; 117 | } 118 | }); 119 | metrics.start(); 120 | } catch (IOException ex) { 121 | // Something went wrong. Owell. 122 | } 123 | } 124 | 125 | public void onDisable() { 126 | getServer().getScheduler().cancelTasks(this); 127 | log.info("[iChat] iChat Disabled"); 128 | } 129 | 130 | private void loadConfig() { 131 | reloadConfig(); 132 | newConfig = this.getConfig(); 133 | newConfig.options().copyDefaults(true); 134 | iNameFormat = newConfig.getString("iname-format"); 135 | chatFormat = newConfig.getString("message-format"); 136 | dateFormat = newConfig.getString("date-format"); 137 | meFormat = newConfig.getString("me-format"); 138 | handleMe = newConfig.getBoolean("handle-me"); 139 | mePerm = newConfig.getBoolean("me-permissions"); 140 | refreshTimeout = newConfig.getInt("variable-refresh"); 141 | // Only get the timezone if it's specified 142 | if (newConfig.isSet("time-offset")) 143 | timeOffset = newConfig.getInt("time-offset"); 144 | 145 | saveConfig(); 146 | 147 | // Restart update task 148 | getServer().getScheduler().cancelTasks(this); 149 | getServer().getScheduler().scheduleSyncRepeatingTask(this, this, 0, refreshTimeout); 150 | } 151 | 152 | private void setupPermissions() { 153 | // Setup already 154 | if (permissions != null || bPerm != null || pbPlug != null || pexPlug != null || gMan != null || priv != null) return; 155 | Plugin tmp = null; 156 | PluginManager pm = getServer().getPluginManager(); 157 | 158 | // Check for bPerms first 159 | tmp = pm.getPlugin("bPermissions"); 160 | if (tmp != null && tmp.isEnabled()) { 161 | permHandler = "bPermissions"; 162 | log.info("[iChat] Found bPermissions v" + tmp.getDescription().getVersion()); 163 | bPerm = tmp; 164 | return; 165 | } 166 | 167 | // Check for PermBukkit next 168 | tmp = pm.getPlugin("PermissionsBukkit"); 169 | if (tmp != null) { 170 | permHandler = "PermissionsBukkit"; 171 | log.info("[iChat] Found PermissionsBukkit v" + tmp.getDescription().getVersion()); 172 | pbPlug = (PermissionsPlugin)tmp; 173 | return; 174 | } 175 | 176 | // Then PEX 177 | tmp = pm.getPlugin("PermissionsEx"); 178 | if (tmp != null && tmp.isEnabled()) { 179 | permHandler = "PermissionsEx"; 180 | log.info("[iChat] Found PermissionsEx v" + tmp.getDescription().getVersion()); 181 | pexPlug = (PermissionsEx)tmp; 182 | return; 183 | } 184 | 185 | // Then GroupManager 186 | tmp = pm.getPlugin("GroupManager"); 187 | if (tmp != null && tmp.isEnabled()) { 188 | permHandler = "GroupManager"; 189 | log.info("[iChat] Found GroupManager v" + tmp.getDescription().getVersion()); 190 | gMan = (GroupManager)tmp; 191 | return; 192 | } 193 | 194 | // Then privileges 195 | tmp = pm.getPlugin("Privileges"); 196 | if (tmp != null && tmp.isEnabled()) { 197 | permHandler = "Privileges"; 198 | log.info("[iChat] Found Privileges v" + tmp.getDescription().getVersion()); 199 | priv = (Privileges)tmp; 200 | return; 201 | } 202 | 203 | // Finally Permissions (This avoids catching bridges) 204 | tmp = pm.getPlugin("Permissions"); 205 | if (tmp != null && tmp.isEnabled()) { 206 | permHandler = "Permissions"; 207 | log.info("[iChat] Found Permissions v" + tmp.getDescription().getVersion()); 208 | permissions = ((Permissions) tmp).getHandler(); 209 | permissions3 = tmp.getDescription().getVersion().startsWith("3"); 210 | if (permissions3) 211 | permHandler = "Permissions3"; 212 | return; 213 | } 214 | 215 | permHandler = "SuperPerms"; 216 | log.info("[iChat] Permissions not found, using SuperPerms"); 217 | return; 218 | } 219 | 220 | /* 221 | * Command Handler 222 | */ 223 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 224 | if (!command.getName().equalsIgnoreCase("ichat")) return false; 225 | if (sender instanceof Player && !API.checkPermissions((Player)sender, "ichat.reload")) { 226 | sender.sendMessage("[iChat] Permission Denied"); 227 | return true; 228 | } 229 | if (args.length != 1) return false; 230 | 231 | if (args[0].equalsIgnoreCase("reload")) { 232 | loadConfig(); 233 | info.loadConfig(); 234 | sender.sendMessage("[iChat] Config Reloaded"); 235 | return true; 236 | } else if (args[0].equalsIgnoreCase("debug")) { 237 | info.debug(); 238 | 239 | // Print config informations 240 | log.info("[ichat::debug]"); 241 | log.info("iNameFormat = " + newConfig.getString("iname-format")); 242 | log.info("chatFormat = " + newConfig.getString("message-format")); 243 | log.info("dateFormat = " + newConfig.getString("date-format")); 244 | log.info("meFormat = " + newConfig.getString("me-format")); 245 | log.info("handleMe = " + newConfig.getBoolean("handle-me")); 246 | log.info("mePerm = " + newConfig.getBoolean("me-permissions")); 247 | 248 | log.info("plugins/iChat/config.yml exists: " + (new File(this.getDataFolder(), "config.yml")).exists()); 249 | log.info("plugins/iChat/variables.yml exists: " + (new File(this.getDataFolder(), "variables.yml")).exists()); 250 | return true; 251 | } 252 | return false; 253 | } 254 | 255 | // Reload player data every x seconds (Default 5) 256 | @Override 257 | public void run() { 258 | for (final Player p : getServer().getOnlinePlayers()) { 259 | info.addPlayer(p); 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ============= 2 | Updates 3 | ============= 4 | Please visit http://forum.thedgtl.net for all updates and support! 5 | 6 | ============= 7 | Description 8 | ============= 9 | Custom chat formatting. 10 | Based on the idea of iChat v1.5 by Nijikokun. 11 | Includes code and concepts from mChat by MiracleM4n 12 | 13 | Download (Direct JAR): http://thedgtl.net/bukkit/iChat.jar 14 | Source: https://github.com/TheDgtl/iChat 15 | 16 | ============= 17 | Features 18 | ============= 19 | Supports Permissions (Both 2.0 and 2.1), and SuperPerms handlers (PermissionsBukkit, bPermissions, GroupManager, Privileges and PermissionsEx). 20 | Allows you to specify a prefix/suffix/variable for users and groups. 21 | A user-specific prefix/suffix/variable will take priority over a group prefix/suffix/variable. 22 | Unlimited amount of custom variables for use in chat format. 23 | Colors are supported in all parts of the formatting and chat text. 24 | Usable health bar and health amount in the formatting. 25 | Support for formatting of /me 26 | Support for timezone offsets 27 | 28 | ============= 29 | Formatting 30 | ============= 31 | Message formatting is defined in the file plugins/iChat/config.yml 32 | The message formats can contain characters, color codes, and variables. 33 | To use colors use the standard Minecraft color codes found here: http://www.minecraftwiki.net/wiki/Classic_Server_Protocol#Color_Codes 34 | 35 | Available variables: 36 | +prefix - The prefix for this user, or this users group if they don't have one defined. 37 | +suffix - The suffix for this user, or this users group if they don't have one defined. 38 | +name - The users name 39 | +displayname - The users display name (Set by plugins such as Towny) 40 | +iname - The iChat formatted player name (Defined by iname-format) 41 | +group - The users group 42 | +healthbar - A visual health bar for this user 43 | +health - The users current health value (Between 0 and 20) 44 | +message - The message the player typed 45 | +world - What world the player is currently in 46 | +time - Timestamp, configurable in config.yml. Uses the format for SimpleDateFormat - http://bit.ly/dscw40 47 | 48 | Example (Default): 49 | iname-format: '[+prefix+group+suffix&f] +displayname' 50 | message-format: '+iname: +message' 51 | me-format: '* +name +message' 52 | date-format: 'HH:mm:ss' 53 | handle-me: true 54 | 55 | As of iChat 2.4.0 there have been a few changes in the way variables and groups are handled. 56 | 57 | ========== 58 | Groups 59 | ========== 60 | As of iChat 2.4.3 native groups are supported in Permissions 2.x/3.x, PermissionsBukkit, bPermissions, and PermissionsEx. 61 | As of iChat 2.6.2 native groups are supported in Privileges 62 | 63 | ========== 64 | Variables 65 | ========== 66 | Variables are now defined in variables.yml in the iChat directory. This includes prefixes, suffixes, and custom variables. 67 | You can define an unlimited number of custom variables for groups and users, if these variables contain the static variables such as +prefix, 68 | +suffix, +health, etc then those variables will be replaced with their respective values. 69 | If a variable does not exist then it will be replaced with a blank string. 70 | 71 | This example will show the users health where +{var1} is located: 72 | 73 | variables.yml: 74 | --------------------- 75 | groups: 76 | admins: 77 | prefix: '' 78 | suffix: '' 79 | var1: '[+health]' 80 | 81 | config.yml: 82 | --------------------- 83 | message-format: '[+group&f] +{var1} +displayname: +message 84 | 85 | ============= 86 | Configuration 87 | ============= 88 | iname-format - The format used for +iname (Default: '[+prefix+group+suffix&f] +displayname') 89 | message-format - The format used for basic chat (Default: '+iname: +message') 90 | date-format - The format used for +date (Default: 'HH:mm:ss') 91 | me-format - The format used for /me commands (Default: '* +name +message') 92 | handle-me - Whether to handle /me commands (Default: true) 93 | me-permissions - Whether to require ichat.me to use /me 94 | time-offset - If defined, will offset the time by that many hours from GMT. This is not an offset from your current timezone. 95 | For example, having the value as -1 will set the timestamps to use GMT-1. Not having this line will set the timestamps 96 | to be your servers current timezone. 97 | 98 | ============= 99 | Permissions 100 | ============= 101 | ichat.format.color - Allow this group/user to use color in their chat messages. 102 | ichat.format.formatting - Allow this group/user to use formatting in their chat messages. 103 | ichat.color - Allow this group/user to use both color and chat formatting in their chat messages (Legacy) 104 | ichat.reload - Allow reloading the iChat config 105 | 106 | ============= 107 | Commands 108 | ============= 109 | /ichat reload - Reload the iChat config file. 110 | 111 | ============= 112 | Changes 113 | ============= 114 | [Version 2.6.2] 115 | - Added support for Privileges 116 | - Added support for a timezone offset 117 | - Added MCStats metrics tracking 118 | [Version 2.6.1] 119 | - Resolve issue where I accidentally cancel all plugin tasks. Oops. 120 | [Version 2.6.0] 121 | - Implemented the AsyncPlayerChatEvent. 122 | - Resolved issues with interaction between iChat and plugins such as Factions. 123 | - More strict caching of data 124 | - Implemented a time-based cache refresh. This is due to not being able to make the calls I require in an ASync event. 125 | - The refresh rate of the cache is configurable in the config.yml. It is not recommended to set this any lower than 100 ticks (5 seconds). 126 | [Version 2.5.10] 127 | - Added more output to /debug 128 | [Version 2.5.9] 129 | - Revert changes to VariableHandler 130 | - Revert to using PlayerChatEvent as Bukkit itself is not thread safe 131 | - Add /me permission/config 132 | - Hopefully resolve NPE in loadConfig of VH 133 | - Add support for GroupManager 134 | [Version 2.5.8] 135 | - Add ichat.format.color/formatting permissions for more customization 136 | - Switch to the new "AsyncPlayerChatEvent" 137 | - Thread-safe VariableHandler 138 | - Build against 1.3.1 139 | [Version 2.5.7] 140 | - Fixed issue with players saying +m in chat duplicating chat message 141 | [Version 2.5.6] 142 | - Implemented my own dYamlConfiguration class for loading variables.yml, this is required as Bukkit's config class lacks Unicode support 143 | - Use Bukkit's implementation of addColor, as it's faster than the RegEx I was using 144 | [Version 2.5.5] 145 | - Color the rest of the "should not exist" color spectrum 146 | [Version 2.5.4] 147 | - Added new &k color code 148 | - Color codes now case insensitive 149 | [Version 2.5.3] 150 | - Updated iChatMeEvent for new event system 151 | [Version 2.5.2] 152 | - Updated for bPermissions 2.9.0 (Requires 2.8.0 or later) 153 | [Version 2.5.1] 154 | - Quick update to event handling for 1.1-R5 spec 155 | [Version 2.5.0] 156 | - Added world variables for further customization 157 | - Updated event handling to 1.1-R3 spec 158 | [Version 2.4.5] 159 | - Fixed reload command not reloading config properly 160 | [Version 2.4.4] 161 | - Updated to new FileConfiguration class 162 | - Fixed bypass exploit for colors in messages 163 | - Multi-world support for variables.yml 164 | [Version 2.4.3] 165 | - Permissions overhaul. No longer require group.{name} node unless not using a permissions handler 166 | [Version 2.4.2] 167 | - Fixed issue with inheritance in Permissions 168 | - Implemented start of online time variable. Need output format. 169 | [Version 2.4.1] 170 | - Remove plugin-specific group referencing. All groups are now managed via group.* nodes, 171 | the exception being pure Permissions 2.x/3.x 172 | - Fixed /ichat reload not reloading variables.yml 173 | - Updated /me to use BroadcastMessage 174 | [Version 2.4.0-final] 175 | - Took out variable caching, there's no hook for PermissionChange. 176 | - Updated README to include info on group.* nodes 177 | [Version 2.4.0-beta] 178 | - Merged all branches into one 179 | - Supports Perms 2.x/3.x, SuperPerms, GroupManager 180 | - Added a more advanded API based on the mChat API 181 | - Massive thanks to MiracleM4n for code and concepts 182 | - All variables are now retrieved from variables.yml instead of Permissions 183 | - Removed censor code 184 | [Version 2.3.3] 185 | - Added Permissions as a dependency 186 | - Added "/ichat reload" command 187 | [Version 2.3.1] 188 | - Added iChat.ichat.parseChat(Player, String, Format) API 189 | - Added hook for /me chat formatting using the "me-format" config option 190 | [Version 2.3.0] 191 | - Added external iChat.ichat.parseChat(Player, String) API 192 | [Version 2.2.3] 193 | - Added +displayname/+d for player.getDisplayName() 194 | [Version 2.2.2] 195 | - Updated to latest RB 196 | [Version 2.2.1] 197 | - Updated how Permissions is loaded 198 | [Version 2.2.0] 199 | - Added the ability to have an unlimited amount of variables in message-format 200 | - Changed version numbering 201 | [Version 2.11] 202 | - Now uses per-world permissions information 203 | [Version 2.10] 204 | - Allow admins to enable color on a permissions basis 205 | [Version 2.09] 206 | - Another small update to Permissions (Returned false when I should have returned true) 207 | [Version 2.08] 208 | - Updated Permissions version detection to only count the first two parts of the version (x.x, not x.x.x) 209 | [Version 2.07] 210 | - Added +time/+t tag for timestamp 211 | [Version 2.06] 212 | - Added +world/+w tag 213 | [Version 2.05] 214 | - Core GM support removed, depends on FakePermissions if you use GM. 215 | [Version 2.04] 216 | - Re-arranged variables, you can now use everything in the suffix/prefix 217 | [Version 2.03] 218 | - Verify that all available variables aren't null before calling parse 219 | - Fixed crash caused by color code at end of message (Basic fix, added a space) 220 | [Version 2.02] 221 | - Fix a NPE someone was getting 222 | [Version 2.01] 223 | - There's a bug in Permissions 2.1 in getPermissionString, switched to getUserPermissionString 224 | [Version 2.00] 225 | - Initial re-write of Niji's plugin. 226 | - Added Permissions 2.0/2.1, and GroupManager support. -------------------------------------------------------------------------------- /src/net/TheDgtl/iChat/VariableHandler.java: -------------------------------------------------------------------------------- 1 | package net.TheDgtl.iChat; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.HashMap; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | import org.bukkit.configuration.ConfigurationSection; 10 | import org.bukkit.configuration.file.YamlConfiguration; 11 | import org.bukkit.entity.Player; 12 | 13 | public class VariableHandler { 14 | private iChat ichat; 15 | private dYamlConfiguration newConfig; 16 | 17 | // Values loaded from config -- Global 18 | private HashMap> groupVars = new HashMap>(); 19 | private HashMap> userVars = new HashMap>(); 20 | 21 | // Values loaded from config -- Per World 22 | private HashMap>> wGroupVars = new HashMap>>(); 23 | private HashMap>> wUserVars = new HashMap>>(); 24 | 25 | // Values loaded from config -- World Variables 26 | private HashMap> worldVars = new HashMap>(); 27 | 28 | // Cached player data 29 | private ConcurrentHashMap> playerVars = new ConcurrentHashMap>(); 30 | private ConcurrentHashMap playerGroups = new ConcurrentHashMap(); 31 | 32 | public VariableHandler(iChat ichat) { 33 | this.ichat = ichat; 34 | checkConfig(); 35 | loadConfig(); 36 | } 37 | 38 | /* 39 | * Check to see if variables.yml exists 40 | */ 41 | public void checkConfig() { 42 | File vFile = new File(ichat.getDataFolder(), "variables.yml"); 43 | // Variables exists, don't touch it! 44 | if (vFile.exists()) return; 45 | // Copy defaults to variables.yml 46 | newConfig = dYamlConfiguration.loadConfiguration(vFile); 47 | newConfig.options().copyDefaults(true); 48 | InputStream defConfigStream = iChat.class.getClassLoader().getResourceAsStream("variables.yml"); 49 | if (defConfigStream != null) { 50 | YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream); 51 | 52 | newConfig.setDefaults(defConfig); 53 | 54 | try { 55 | newConfig.save(vFile); 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | } 61 | 62 | public void loadConfig() { 63 | groupVars.clear(); 64 | userVars.clear(); 65 | playerVars.clear(); 66 | wGroupVars.clear(); 67 | wUserVars.clear(); 68 | worldVars.clear(); 69 | 70 | reloadConfig(); 71 | 72 | // Loop through nodes, "groups" and "users" are special cases, everything else is a world 73 | for (String key : newConfig.getKeys(false)) { 74 | if (key.equals("groups")) { 75 | ConfigurationSection groups = newConfig.getConfigurationSection("groups"); 76 | loadNodes(groups, groupVars); 77 | continue; 78 | } 79 | if (key.equals("users")) { 80 | ConfigurationSection users = newConfig.getConfigurationSection("users"); 81 | loadNodes(users, userVars); 82 | continue; 83 | } 84 | 85 | // Loop through world nodes, "groups" and "users" are special cases, everything else is a world variable 86 | HashMap wVars = new HashMap(); 87 | ConfigurationSection world = newConfig.getConfigurationSection(key); 88 | if (world == null) { 89 | ichat.log.warning("[iChat::vh::loadConfig] There was an error loading configuration. world was null"); 90 | continue; 91 | } 92 | for (String wKey : world.getKeys(false)) { 93 | if (wKey.equals("groups")) { 94 | ConfigurationSection groups = world.getConfigurationSection("groups"); 95 | HashMap> wGroups = new HashMap>(); 96 | loadNodes(groups, wGroups); 97 | wGroupVars.put(key.toLowerCase(), wGroups); 98 | continue; 99 | } 100 | if (wKey.equals("users")) { 101 | ConfigurationSection users = world.getConfigurationSection("users"); 102 | HashMap> wUsers = new HashMap>(); 103 | loadNodes(users, wUsers); 104 | wUserVars.put(key.toLowerCase(), wUsers); 105 | continue; 106 | } 107 | String value = world.getString(wKey); 108 | if (value == null) continue; 109 | wVars.put(wKey.toLowerCase(), value); 110 | } 111 | worldVars.put(key.toLowerCase(), wVars); 112 | } 113 | } 114 | 115 | public void addPlayer(Player player) { 116 | HashMap tmpList = new HashMap(); 117 | 118 | String group = ichat.API.getGroup(player); 119 | String world = player.getWorld().getName().toLowerCase(); 120 | 121 | // Check if the world the player is in has variables 122 | HashMap wVars = worldVars.get(world); 123 | if (wVars != null && !wVars.isEmpty()) { 124 | tmpList.putAll(wVars); 125 | } 126 | 127 | if (group != null && !group.isEmpty()) { 128 | // Add the players cached group to the group list 129 | playerGroups.put(player.getName().toLowerCase(), group); 130 | 131 | group = group.toLowerCase(); 132 | HashMap gVars = groupVars.get(group); 133 | if (gVars != null) 134 | tmpList.putAll(gVars); 135 | 136 | // Check if this group has world-specific vars 137 | HashMap> worlds = wGroupVars.get(world); 138 | if (worlds != null) { 139 | HashMap wgVars = worlds.get(group); 140 | if (wgVars != null) 141 | tmpList.putAll(wgVars); 142 | } 143 | } else { 144 | // Remove players cached group 145 | playerGroups.remove(player.getName().toLowerCase()); 146 | } 147 | 148 | HashMap uVars = userVars.get(player.getName().toLowerCase()); 149 | if (uVars != null) 150 | tmpList.putAll(uVars); 151 | 152 | HashMap> worlds = wUserVars.get(world); 153 | if (worlds != null) { 154 | HashMap wuVars = worlds.get(player.getName().toLowerCase()); 155 | if (wuVars != null) 156 | tmpList.putAll(wuVars); 157 | } 158 | 159 | playerVars.put(player.getName().toLowerCase(), tmpList); 160 | } 161 | 162 | public void removePlayer(Player player) { 163 | playerGroups.remove(player.getName().toLowerCase()); 164 | playerVars.remove(player.getName().toLowerCase()); 165 | } 166 | 167 | public String getGroup(Player player) { 168 | String group = playerGroups.get(player.getName().toLowerCase()); 169 | if (group == null) return ""; 170 | return group; 171 | } 172 | 173 | public String getKey(Player player, String key) { 174 | HashMap pVars = playerVars.get(player.getName().toLowerCase()); 175 | if (pVars == null) return ""; 176 | String var = pVars.get(key.toLowerCase()); 177 | if (var == null) return ""; 178 | return var; 179 | } 180 | 181 | public String getKey(String group, String key) { 182 | HashMap gVars = groupVars.get(group.toLowerCase()); 183 | if (gVars == null) return ""; 184 | String var = gVars.get(key.toLowerCase()); 185 | if (var == null) return ""; 186 | return var; 187 | } 188 | 189 | private void reloadConfig() { 190 | File vFile = new File(ichat.getDataFolder(), "variables.yml"); 191 | if (!vFile.exists()) { 192 | ichat.log.info("[iChat] variables.yml does not exist. Please create it."); 193 | return; 194 | } 195 | newConfig = dYamlConfiguration.loadConfiguration(vFile); 196 | } 197 | 198 | /* 199 | * Load the nodes from root into map 200 | */ 201 | private void loadNodes(ConfigurationSection root, HashMap> map) { 202 | if (root == null) { 203 | ichat.log.warning("[iChat::vh::loadNodes] There was an error loading configuration nodes"); 204 | return; 205 | } 206 | for (String key : root.getKeys(false)) { 207 | HashMap tmpList = new HashMap(); 208 | ConfigurationSection vars = root.getConfigurationSection(key); 209 | if (vars == null) continue; 210 | // Store vars 211 | for (String vKey : vars.getKeys(false)) { 212 | String value = vars.getString(vKey); 213 | if (value == null) continue; 214 | tmpList.put(vKey.toLowerCase(), value); 215 | } 216 | map.put(key.toLowerCase(), tmpList); 217 | } 218 | } 219 | 220 | /* 221 | * DEBUG 222 | */ 223 | public void debug() { 224 | ichat.log.info("[iChat::vh::debug]"); 225 | // Print out all group variables 226 | ichat.log.info("[iChat::vh::debug] Groups"); 227 | for (String group : groupVars.keySet()) { 228 | ichat.log.info(" " + group); 229 | HashMap gVars = groupVars.get(group); 230 | for (String key : gVars.keySet()) { 231 | ichat.log.info(" " + key + " => " + gVars.get(key)); 232 | } 233 | } 234 | 235 | // Print out all user variables 236 | ichat.log.info("[iChat::vh::debug] Users"); 237 | for (String user : userVars.keySet()) { 238 | ichat.log.info(" " + user); 239 | HashMap uVars = userVars.get(user); 240 | for (String key : uVars.keySet()) { 241 | ichat.log.info(" " + key + " => " + uVars.get(key)); 242 | } 243 | } 244 | 245 | // Print all world-specific group variables 246 | ichat.log.info("[iChat::vh::debug] World-Groups"); 247 | for (String world : wGroupVars.keySet()) { 248 | ichat.log.info(" " + world); 249 | HashMap> groups = wGroupVars.get(world); 250 | for (String group : groups.keySet()) { 251 | ichat.log.info(" " + group); 252 | HashMap gVars = groups.get(group); 253 | for (String key : gVars.keySet()) { 254 | ichat.log.info(" " + key + " => " + gVars.get(key)); 255 | } 256 | } 257 | } 258 | 259 | // Print all world-specific user variables 260 | ichat.log.info("[iChat::vh::debug] World-Players"); 261 | for (String world : wUserVars.keySet()) { 262 | ichat.log.info(" " + world); 263 | HashMap> users = wUserVars.get(world); 264 | for (String user : users.keySet()) { 265 | ichat.log.info(" " + user); 266 | HashMap uVars = users.get(user); 267 | for (String key : uVars.keySet()) { 268 | ichat.log.info(" " + key + " => " + uVars.get(key)); 269 | } 270 | } 271 | } 272 | 273 | // Print all world variables 274 | ichat.log.info("[iChat::vh::debug] World Variables"); 275 | for (String world : worldVars.keySet()) { 276 | ichat.log.info(" " + world); 277 | HashMap wVars = worldVars.get(world); 278 | for (String key : wVars.keySet()) { 279 | ichat.log.info(" " + key + " => " + wVars.get(key)); 280 | } 281 | } 282 | 283 | // Print out all player variables 284 | ichat.log.info("[iChat::vh::debug] Players"); 285 | for (String player : playerVars.keySet()) { 286 | ichat.log.info(" " + player); 287 | HashMap pVars = playerVars.get(player); 288 | for (String key : pVars.keySet()) { 289 | ichat.log.info(" " + key + " => " + pVars.get(key)); 290 | } 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/net/TheDgtl/iChat/iChatAPI.java: -------------------------------------------------------------------------------- 1 | package net.TheDgtl.iChat; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.List; 6 | import java.util.Set; 7 | import java.util.TimeZone; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import org.anjocaido.groupmanager.permissions.AnjoPermissionsHandler; 12 | import org.bukkit.ChatColor; 13 | import org.bukkit.OfflinePlayer; 14 | import org.bukkit.entity.Player; 15 | import org.bukkit.permissions.PermissionAttachmentInfo; 16 | 17 | import ru.tehkode.permissions.PermissionGroup; 18 | import ru.tehkode.permissions.PermissionUser; 19 | import ru.tehkode.permissions.bukkit.PermissionsEx; 20 | 21 | import com.platymuus.bukkit.permissions.Group; 22 | 23 | import de.bananaco.bpermissions.api.ApiLayer; 24 | import de.bananaco.bpermissions.api.util.CalculableType; 25 | 26 | public class iChatAPI { 27 | private iChat ichat; 28 | 29 | iChatAPI(iChat ichat) { 30 | this.ichat = ichat; 31 | } 32 | 33 | /** 34 | * @param p - Player object that is chatting 35 | * @param msg - Message to be formatted 36 | * @param chatFormat - The requested chat format string 37 | * @return - New message format with variables parsed 38 | */ 39 | public String parseChat(Player p, String msg, String chatFormat) { 40 | return parseChat(p, msg, chatFormat, false); 41 | } 42 | 43 | public String parseChat(Player p, String msg, String chatFormat, Boolean parseName) { 44 | // Variables we can use in a message 45 | String group = ichat.info.getGroup(p); 46 | String prefix = getPrefix(p); 47 | String suffix = getSuffix(p); 48 | 49 | if (prefix == null) prefix = ""; 50 | if (suffix == null) suffix = ""; 51 | if (group == null) group = ""; 52 | 53 | String healthbar = healthBar(p); 54 | String health = String.valueOf(p.getHealth()); 55 | String world = p.getWorld().getName(); 56 | 57 | if (world.contains("_nether")) 58 | world = world.replace("_nether", " Nether"); 59 | if (world.contains("_the_end")) 60 | world = world.replace("_the_end", " End"); 61 | 62 | // Timestamp support 63 | Date now = new Date(); 64 | SimpleDateFormat dateFormat = new SimpleDateFormat(ichat.dateFormat); 65 | // Offset the time if needed 66 | if (ichat.timeOffset != null) { 67 | String offset = Integer.toString(ichat.timeOffset); 68 | if (ichat.timeOffset == 0) offset = ""; 69 | if (ichat.timeOffset > 0) offset = "+" + offset; 70 | dateFormat.setTimeZone(TimeZone.getTimeZone("GMT" + offset)); 71 | } 72 | String time = dateFormat.format(now); 73 | 74 | // We're sending this to String.format, so we need to escape those pesky % symbols 75 | msg = msg.replaceAll("%", "%%"); 76 | 77 | // Add coloring if the player has permission 78 | if (!checkPermissions(p, "ichat.color")) { 79 | boolean hasColor = true; 80 | boolean hasFormat = true; 81 | // Strip color if they lack permission 82 | if (!checkPermissions(p, "ichat.format.color")) { 83 | msg = msg.replaceAll("(&+([a-fA-F0-9]))", ""); 84 | hasColor = false; 85 | } 86 | // Strip formatting if they lack permission 87 | if (!checkPermissions(p, "ichat.format.formatting")) { 88 | msg = msg.replaceAll("(&+([k-oK-OrR]))", ""); 89 | hasFormat = false; 90 | } 91 | // Legacy: Strip all if they lack any permission 92 | if (!hasColor && !hasFormat) { 93 | msg = msg.replaceAll("(&+([a-fA-Fk-oK-OrR0-9]))", ""); 94 | } 95 | } 96 | 97 | String format = parseVars(chatFormat, p); 98 | if (format == null) return msg; 99 | 100 | String iName = p.getDisplayName(); 101 | if (!parseName) { 102 | iName = parseChat(p, "", ichat.iNameFormat, true); 103 | } 104 | 105 | // Order is important, this allows us to use all variables in the suffix and prefix! But no variables in the message 106 | String[] search = new String[] {"+suffix,+s", "+prefix,+p", "+group,+g", "+healthbar,+hb", "+health,+h", "+world,+w", "+time,+t", "+name,+n", "+displayname,+d", "+iname,+in", "+message", "+m"}; 107 | String[] replace = new String[] { suffix, prefix, group, healthbar, health, world, time, p.getName(), p.getDisplayName(), iName, "+m", msg }; 108 | return replaceVars(format, search, replace); 109 | } 110 | 111 | public Long getConnectTime(Player p) { 112 | Long conTime = ichat.connectList.get(p.getName()); 113 | if (conTime == null) return -1L; 114 | return conTime; 115 | } 116 | 117 | public Long getOnlineTime(Player p) { 118 | Long conTime = getConnectTime(p); 119 | if (conTime == null) return -1L; 120 | Long onTime = (System.currentTimeMillis() / 1000L) - conTime; 121 | return onTime; 122 | } 123 | 124 | public String prettyTime(Long time) { 125 | StringBuilder sb = new StringBuilder(); 126 | sb.append(time % 60).append("s"); 127 | if (time >= 60) { 128 | Long min = time / 60; 129 | sb.insert(0, " " + min % 60 + "m"); 130 | } 131 | if (time >= 3600) { 132 | Long hour = time / 3600; 133 | sb.insert(0, " " + hour + "h"); 134 | } 135 | return sb.toString(); 136 | } 137 | 138 | /** 139 | * Permissions handling from mChat by MiracleM4n with modification by Drakia 140 | **/ 141 | 142 | public String parseChat(Player player, String msg) { 143 | return parseChat(player, msg, ichat.chatFormat); 144 | } 145 | 146 | public String parsePlayerName(Player player) { 147 | return parseChat(player, "", ichat.iNameFormat, true); 148 | } 149 | 150 | /* 151 | * Info Stuff 152 | */ 153 | public String getRawInfo(Player player, String info) { 154 | if (info.equals("group")) { 155 | if (ichat.bPerm != null) { 156 | return getbPermGroup(player); 157 | } 158 | if (ichat.pbPlug != null) { 159 | return getPermBGroup(player); 160 | } 161 | if (ichat.pexPlug != null) { 162 | return getPexGroup(player); 163 | } 164 | if (ichat.gMan != null) { 165 | return getGManGroup(player); 166 | } 167 | if (ichat.permissions != null) { 168 | return getPermissionsGroup(player); 169 | } 170 | if (ichat.priv != null) { 171 | return getPrivilegesGroup(player); 172 | } 173 | return getSuperPermGroup(player); 174 | } 175 | 176 | return getVariable(player, info); 177 | } 178 | 179 | public String getRawPrefix(Player player) { 180 | return getRawInfo(player, "prefix"); 181 | } 182 | 183 | public String getRawSuffix(Player player) { 184 | return getRawInfo(player, "suffix"); 185 | } 186 | 187 | public String getRawGroup(Player player) { 188 | return getRawInfo(player, "group"); 189 | } 190 | 191 | public String getInfo(Player player, String info) { 192 | return addColor(getRawInfo(player, info)); 193 | } 194 | 195 | public String getPrefix(Player player) { 196 | return getInfo(player, "prefix"); 197 | } 198 | 199 | public String getSuffix(Player player) { 200 | return getInfo(player, "suffix"); 201 | } 202 | 203 | public String getGroup(Player player) { 204 | return getInfo(player, "group"); 205 | } 206 | 207 | /* 208 | * Return a health bar string. 209 | */ 210 | public String healthBar(Player player) { 211 | float maxHealth = 20; 212 | float barLength = 10; 213 | float health = player.getHealth(); 214 | int fill = Math.round( (health / maxHealth) * barLength ); 215 | String barColor = "&2"; 216 | // 0-40: Red 40-70: Yellow 70-100: Green 217 | if (fill <= 4) barColor = "&4"; 218 | else if (fill <= 7) barColor = "&e"; 219 | else barColor = "&2"; 220 | 221 | StringBuilder out = new StringBuilder(); 222 | out.append(barColor); 223 | for (int i = 0; i < barLength; i++) { 224 | if (i == fill) out.append("&8"); 225 | out.append("|"); 226 | } 227 | out.append("&f"); 228 | return out.toString(); 229 | } 230 | 231 | public String addColor(String string) { 232 | return ChatColor.translateAlternateColorCodes('&', string); 233 | } 234 | 235 | public Boolean checkPermissions(Player player, String node) { 236 | // Permissions 237 | if (ichat.permissions != null) { 238 | if (ichat.permissions.has(player, node)) 239 | return true; 240 | // SuperPerms 241 | } else if (player.hasPermission(node)) { 242 | return true; 243 | // Op Fallback 244 | } else if (player.isOp()) { 245 | return true; 246 | } 247 | return false; 248 | } 249 | 250 | /* 251 | * Private non-API functions 252 | */ 253 | /* 254 | * Bukkit Permission Stuff 255 | */ 256 | private String getVariable(Player player, String info) { 257 | return ichat.info.getKey(player, info); 258 | } 259 | 260 | private String getbPermGroup(Player player) { 261 | String[] groups = ApiLayer.getGroups(player.getWorld().getName(), CalculableType.USER, player.getName()); 262 | if (groups.length == 0) return ""; 263 | return groups[0]; 264 | } 265 | 266 | private String getPermBGroup(Player player) { 267 | // Because of a softdepend issue in Bukkit, this plugin may not be enabled 268 | if (!ichat.pbPlug.isEnabled()) return ""; 269 | List groups = ichat.pbPlug.getGroups(player.getName()); 270 | if (groups.size() == 0) return ""; 271 | return groups.get(0).getName(); 272 | } 273 | 274 | private String getPexGroup(Player player) { 275 | PermissionUser user = PermissionsEx.getUser(player); 276 | if (user == null) return ""; 277 | PermissionGroup[] groups = user.getGroups(player.getWorld().getName()); 278 | if (groups.length == 0) return ""; 279 | return groups[0].getName(); 280 | } 281 | 282 | private String getGManGroup(Player player) { 283 | if (!ichat.gMan.isEnabled()) return ""; 284 | AnjoPermissionsHandler handler = ichat.gMan.getWorldsHolder().getWorldPermissions(player); 285 | if (handler == null) return ""; 286 | return handler.getGroup(player.getName()); 287 | } 288 | 289 | private String getSuperPermGroup(Player player) { 290 | Set perms = player.getEffectivePermissions(); 291 | for (PermissionAttachmentInfo perm : perms) { 292 | if (perm.getPermission().startsWith("group.") && 293 | perm.getValue()) { 294 | String group = perm.getPermission().substring(6); 295 | return ichat.info.getKey(group, "name"); 296 | } 297 | } 298 | return ""; 299 | } 300 | 301 | /* 302 | * Permissions Stuff 303 | */ 304 | @SuppressWarnings("deprecation") 305 | private String getPermissionsGroup(Player player) { 306 | String pName = player.getName(); 307 | String world = player.getWorld().getName(); 308 | 309 | if (ichat.permissions3) { 310 | String group = ichat.permissions.getPrimaryGroup(world, pName); 311 | 312 | if (group == null) 313 | return ""; 314 | 315 | return group; 316 | } else { 317 | String group = ichat.permissions.getGroup(world, pName); 318 | 319 | if (group == null) 320 | return ""; 321 | 322 | return group; 323 | } 324 | } 325 | 326 | /* 327 | * Privileges Stuff 328 | */ 329 | private String getPrivilegesGroup(Player player) { 330 | if (!ichat.priv.isEnabled()) return ""; 331 | net.krinsoft.privileges.groups.Group group = ichat.priv.getGroupManager().getGroup((OfflinePlayer)player); 332 | if (group == null || group.getName() == null) return ""; 333 | return group.getName(); 334 | } 335 | 336 | /* 337 | * Parse given text string for permissions variables 338 | */ 339 | private String parseVars(String format, Player p) { 340 | Pattern pattern = Pattern.compile("\\+\\{(.*?)\\}"); 341 | Matcher matcher = pattern.matcher(format); 342 | StringBuffer sb = new StringBuffer(); 343 | while (matcher.find()) { 344 | String var = getRawInfo(p, matcher.group(1)); 345 | matcher.appendReplacement(sb, Matcher.quoteReplacement(var)); 346 | } 347 | matcher.appendTail(sb); 348 | return sb.toString(); 349 | } 350 | 351 | /* 352 | * Parse a given text string and replace the variables/color codes. 353 | */ 354 | private String replaceVars(String format, String[] search, String[] replace) { 355 | if (search.length != replace.length) return ""; 356 | for (int i = 0; i < search.length; i++) { 357 | if (search[i].contains(",")) { 358 | for (String s : search[i].split(",")) { 359 | if (s == null || replace[i] == null) continue; 360 | format = format.replace(s, replace[i]); 361 | } 362 | } else { 363 | format = format.replace(search[i], replace[i]); 364 | } 365 | } 366 | return addColor(format); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/net/TheDgtl/iChat/Metrics/Metrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Tyler Blair. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | * of conditions and the following disclaimer in the documentation and/or other materials 12 | * provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | * 24 | * The views and conclusions contained in the software and documentation are those of the 25 | * authors and contributors and should not be interpreted as representing official policies, 26 | * either expressed or implied, of anybody else. 27 | */ 28 | 29 | package net.TheDgtl.iChat.Metrics; 30 | 31 | import org.bukkit.Bukkit; 32 | import org.bukkit.configuration.file.YamlConfiguration; 33 | import org.bukkit.configuration.InvalidConfigurationException; 34 | import org.bukkit.plugin.Plugin; 35 | import org.bukkit.plugin.PluginDescriptionFile; 36 | 37 | import java.io.BufferedReader; 38 | import java.io.File; 39 | import java.io.IOException; 40 | import java.io.InputStreamReader; 41 | import java.io.OutputStreamWriter; 42 | import java.io.UnsupportedEncodingException; 43 | import java.net.Proxy; 44 | import java.net.URL; 45 | import java.net.URLConnection; 46 | import java.net.URLEncoder; 47 | import java.util.Collections; 48 | import java.util.HashSet; 49 | import java.util.Iterator; 50 | import java.util.LinkedHashSet; 51 | import java.util.Set; 52 | import java.util.UUID; 53 | import java.util.logging.Level; 54 | 55 | /** 56 | *

57 | * The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. 58 | *

59 | *

60 | * Public methods provided by this class: 61 | *

62 | * 63 | * Graph createGraph(String name);
64 | * void addCustomData(Metrics.Plotter plotter);
65 | * void start();
66 | *
67 | */ 68 | public class Metrics { 69 | 70 | /** 71 | * The current revision number 72 | */ 73 | private final static int REVISION = 5; 74 | 75 | /** 76 | * The base url of the metrics domain 77 | */ 78 | private static final String BASE_URL = "http://mcstats.org"; 79 | 80 | /** 81 | * The url used to report a server's status 82 | */ 83 | private static final String REPORT_URL = "/report/%s"; 84 | 85 | /** 86 | * The separator to use for custom data. This MUST NOT change unless you are hosting your own 87 | * version of metrics and want to change it. 88 | */ 89 | private static final String CUSTOM_DATA_SEPARATOR = "~~"; 90 | 91 | /** 92 | * Interval of time to ping (in minutes) 93 | */ 94 | private static final int PING_INTERVAL = 10; 95 | 96 | /** 97 | * The plugin this metrics submits for 98 | */ 99 | private final Plugin plugin; 100 | 101 | /** 102 | * All of the custom graphs to submit to metrics 103 | */ 104 | private final Set graphs = Collections.synchronizedSet(new HashSet()); 105 | 106 | /** 107 | * The default graph, used for addCustomData when you don't want a specific graph 108 | */ 109 | private final Graph defaultGraph = new Graph("Default"); 110 | 111 | /** 112 | * The plugin configuration file 113 | */ 114 | private final YamlConfiguration configuration; 115 | 116 | /** 117 | * The plugin configuration file 118 | */ 119 | private final File configurationFile; 120 | 121 | /** 122 | * Unique server id 123 | */ 124 | private final String guid; 125 | 126 | /** 127 | * Lock for synchronization 128 | */ 129 | private final Object optOutLock = new Object(); 130 | 131 | /** 132 | * Id of the scheduled task 133 | */ 134 | private volatile int taskId = -1; 135 | 136 | public Metrics(final Plugin plugin) throws IOException { 137 | if (plugin == null) { 138 | throw new IllegalArgumentException("Plugin cannot be null"); 139 | } 140 | 141 | this.plugin = plugin; 142 | 143 | // load the config 144 | configurationFile = getConfigFile(); 145 | configuration = YamlConfiguration.loadConfiguration(configurationFile); 146 | 147 | // add some defaults 148 | configuration.addDefault("opt-out", false); 149 | configuration.addDefault("guid", UUID.randomUUID().toString()); 150 | 151 | // Do we need to create the file? 152 | if (configuration.get("guid", null) == null) { 153 | configuration.options().header("http://mcstats.org").copyDefaults(true); 154 | configuration.save(configurationFile); 155 | } 156 | 157 | // Load the guid then 158 | guid = configuration.getString("guid"); 159 | } 160 | 161 | /** 162 | * Construct and create a Graph that can be used to separate specific plotters to their own graphs 163 | * on the metrics website. Plotters can be added to the graph object returned. 164 | * 165 | * @param name The name of the graph 166 | * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given 167 | */ 168 | public Graph createGraph(final String name) { 169 | if (name == null) { 170 | throw new IllegalArgumentException("Graph name cannot be null"); 171 | } 172 | 173 | // Construct the graph object 174 | final Graph graph = new Graph(name); 175 | 176 | // Now we can add our graph 177 | graphs.add(graph); 178 | 179 | // and return back 180 | return graph; 181 | } 182 | 183 | /** 184 | * Add a Graph object to Metrics that represents data for the plugin that should be sent to the backend 185 | * 186 | * @param graph The name of the graph 187 | */ 188 | public void addGraph(final Graph graph) { 189 | if (graph == null) { 190 | throw new IllegalArgumentException("Graph cannot be null"); 191 | } 192 | 193 | graphs.add(graph); 194 | } 195 | 196 | /** 197 | * Adds a custom data plotter to the default graph 198 | * 199 | * @param plotter The plotter to use to plot custom data 200 | */ 201 | public void addCustomData(final Plotter plotter) { 202 | if (plotter == null) { 203 | throw new IllegalArgumentException("Plotter cannot be null"); 204 | } 205 | 206 | // Add the plotter to the graph o/ 207 | defaultGraph.addPlotter(plotter); 208 | 209 | // Ensure the default graph is included in the submitted graphs 210 | graphs.add(defaultGraph); 211 | } 212 | 213 | /** 214 | * Start measuring statistics. This will immediately create an async repeating task as the plugin and send 215 | * the initial data to the metrics backend, and then after that it will post in increments of 216 | * PING_INTERVAL * 1200 ticks. 217 | * 218 | * @return True if statistics measuring is running, otherwise false. 219 | */ 220 | public boolean start() { 221 | synchronized (optOutLock) { 222 | // Did we opt out? 223 | if (isOptOut()) { 224 | return false; 225 | } 226 | 227 | // Is metrics already running? 228 | if (taskId >= 0) { 229 | return true; 230 | } 231 | 232 | // Begin hitting the server with glorious data 233 | taskId = plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() { 234 | 235 | private boolean firstPost = true; 236 | 237 | public void run() { 238 | try { 239 | // This has to be synchronized or it can collide with the disable method. 240 | synchronized (optOutLock) { 241 | // Disable Task, if it is running and the server owner decided to opt-out 242 | if (isOptOut() && taskId > 0) { 243 | plugin.getServer().getScheduler().cancelTask(taskId); 244 | taskId = -1; 245 | // Tell all plotters to stop gathering information. 246 | for (Graph graph : graphs){ 247 | graph.onOptOut(); 248 | } 249 | } 250 | } 251 | 252 | // We use the inverse of firstPost because if it is the first time we are posting, 253 | // it is not a interval ping, so it evaluates to FALSE 254 | // Each time thereafter it will evaluate to TRUE, i.e PING! 255 | postPlugin(!firstPost); 256 | 257 | // After the first post we set firstPost to false 258 | // Each post thereafter will be a ping 259 | firstPost = false; 260 | } catch (IOException e) { 261 | Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); 262 | } 263 | } 264 | }, 0, PING_INTERVAL * 1200); 265 | 266 | return true; 267 | } 268 | } 269 | 270 | /** 271 | * Has the server owner denied plugin metrics? 272 | * 273 | * @return true if metrics should be opted out of it 274 | */ 275 | public boolean isOptOut() { 276 | synchronized(optOutLock) { 277 | try { 278 | // Reload the metrics file 279 | configuration.load(getConfigFile()); 280 | } catch (IOException ex) { 281 | Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); 282 | return true; 283 | } catch (InvalidConfigurationException ex) { 284 | Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); 285 | return true; 286 | } 287 | return configuration.getBoolean("opt-out", false); 288 | } 289 | } 290 | 291 | /** 292 | * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. 293 | * 294 | * @throws IOException 295 | */ 296 | public void enable() throws IOException { 297 | // This has to be synchronized or it can collide with the check in the task. 298 | synchronized (optOutLock) { 299 | // Check if the server owner has already set opt-out, if not, set it. 300 | if (isOptOut()) { 301 | configuration.set("opt-out", false); 302 | configuration.save(configurationFile); 303 | } 304 | 305 | // Enable Task, if it is not running 306 | if (taskId < 0) { 307 | start(); 308 | } 309 | } 310 | } 311 | 312 | /** 313 | * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. 314 | * 315 | * @throws IOException 316 | */ 317 | public void disable() throws IOException { 318 | // This has to be synchronized or it can collide with the check in the task. 319 | synchronized (optOutLock) { 320 | // Check if the server owner has already set opt-out, if not, set it. 321 | if (!isOptOut()) { 322 | configuration.set("opt-out", true); 323 | configuration.save(configurationFile); 324 | } 325 | 326 | // Disable Task, if it is running 327 | if (taskId > 0) { 328 | this.plugin.getServer().getScheduler().cancelTask(taskId); 329 | taskId = -1; 330 | } 331 | } 332 | } 333 | 334 | /** 335 | * Gets the File object of the config file that should be used to store data such as the GUID and opt-out status 336 | * 337 | * @return the File object for the config file 338 | */ 339 | public File getConfigFile() { 340 | // I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use 341 | // is to abuse the plugin object we already have 342 | // plugin.getDataFolder() => base/plugins/PluginA/ 343 | // pluginsFolder => base/plugins/ 344 | // The base is not necessarily relative to the startup directory. 345 | File pluginsFolder = plugin.getDataFolder().getParentFile(); 346 | 347 | // return => base/plugins/PluginMetrics/config.yml 348 | return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml"); 349 | } 350 | 351 | /** 352 | * Generic method that posts a plugin to the metrics website 353 | */ 354 | private void postPlugin(final boolean isPing) throws IOException { 355 | // The plugin's description file containg all of the plugin data such as name, version, author, etc 356 | final PluginDescriptionFile description = plugin.getDescription(); 357 | 358 | // Construct the post data 359 | final StringBuilder data = new StringBuilder(); 360 | data.append(encode("guid")).append('=').append(encode(guid)); 361 | encodeDataPair(data, "version", description.getVersion()); 362 | encodeDataPair(data, "server", Bukkit.getVersion()); 363 | encodeDataPair(data, "players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length)); 364 | encodeDataPair(data, "revision", String.valueOf(REVISION)); 365 | 366 | // If we're pinging, append it 367 | if (isPing) { 368 | encodeDataPair(data, "ping", "true"); 369 | } 370 | 371 | // Acquire a lock on the graphs, which lets us make the assumption we also lock everything 372 | // inside of the graph (e.g plotters) 373 | synchronized (graphs) { 374 | final Iterator iter = graphs.iterator(); 375 | 376 | while (iter.hasNext()) { 377 | final Graph graph = iter.next(); 378 | 379 | for (Plotter plotter : graph.getPlotters()) { 380 | // The key name to send to the metrics server 381 | // The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top 382 | // Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME 383 | final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName()); 384 | 385 | // The value to send, which for the foreseeable future is just the string 386 | // value of plotter.getValue() 387 | final String value = Integer.toString(plotter.getValue()); 388 | 389 | // Add it to the http post data :) 390 | encodeDataPair(data, key, value); 391 | } 392 | } 393 | } 394 | 395 | // Create the url 396 | URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(plugin.getDescription().getName()))); 397 | 398 | // Connect to the website 399 | URLConnection connection; 400 | 401 | // Mineshafter creates a socks proxy, so we can safely bypass it 402 | // It does not reroute POST requests so we need to go around it 403 | if (isMineshafterPresent()) { 404 | connection = url.openConnection(Proxy.NO_PROXY); 405 | } else { 406 | connection = url.openConnection(); 407 | } 408 | 409 | connection.setDoOutput(true); 410 | 411 | // Write the data 412 | final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); 413 | writer.write(data.toString()); 414 | writer.flush(); 415 | 416 | // Now read the response 417 | final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); 418 | final String response = reader.readLine(); 419 | 420 | // close resources 421 | writer.close(); 422 | reader.close(); 423 | 424 | if (response == null || response.startsWith("ERR")) { 425 | throw new IOException(response); //Throw the exception 426 | } else { 427 | // Is this the first update this hour? 428 | if (response.contains("OK This is your first update this hour")) { 429 | synchronized (graphs) { 430 | final Iterator iter = graphs.iterator(); 431 | 432 | while (iter.hasNext()) { 433 | final Graph graph = iter.next(); 434 | 435 | for (Plotter plotter : graph.getPlotters()) { 436 | plotter.reset(); 437 | } 438 | } 439 | } 440 | } 441 | } 442 | } 443 | 444 | /** 445 | * Check if mineshafter is present. If it is, we need to bypass it to send POST requests 446 | * 447 | * @return true if mineshafter is installed on the server 448 | */ 449 | private boolean isMineshafterPresent() { 450 | try { 451 | Class.forName("mineshafter.MineServer"); 452 | return true; 453 | } catch (Exception e) { 454 | return false; 455 | } 456 | } 457 | 458 | /** 459 | *

Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first 460 | * key/value pair MUST be included manually, e.g:

461 | * 462 | * StringBuffer data = new StringBuffer(); 463 | * data.append(encode("guid")).append('=').append(encode(guid)); 464 | * encodeDataPair(data, "version", description.getVersion()); 465 | * 466 | * 467 | * @param buffer the stringbuilder to append the data pair onto 468 | * @param key the key value 469 | * @param value the value 470 | */ 471 | private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException { 472 | buffer.append('&').append(encode(key)).append('=').append(encode(value)); 473 | } 474 | 475 | /** 476 | * Encode text as UTF-8 477 | * 478 | * @param text the text to encode 479 | * @return the encoded text, as UTF-8 480 | */ 481 | private static String encode(final String text) throws UnsupportedEncodingException { 482 | return URLEncoder.encode(text, "UTF-8"); 483 | } 484 | 485 | /** 486 | * Represents a custom graph on the website 487 | */ 488 | public static class Graph { 489 | 490 | /** 491 | * The graph's name, alphanumeric and spaces only :) 492 | * If it does not comply to the above when submitted, it is rejected 493 | */ 494 | private final String name; 495 | 496 | /** 497 | * The set of plotters that are contained within this graph 498 | */ 499 | private final Set plotters = new LinkedHashSet(); 500 | 501 | private Graph(final String name) { 502 | this.name = name; 503 | } 504 | 505 | /** 506 | * Gets the graph's name 507 | * 508 | * @return the Graph's name 509 | */ 510 | public String getName() { 511 | return name; 512 | } 513 | 514 | /** 515 | * Add a plotter to the graph, which will be used to plot entries 516 | * 517 | * @param plotter the plotter to add to the graph 518 | */ 519 | public void addPlotter(final Plotter plotter) { 520 | plotters.add(plotter); 521 | } 522 | 523 | /** 524 | * Remove a plotter from the graph 525 | * 526 | * @param plotter the plotter to remove from the graph 527 | */ 528 | public void removePlotter(final Plotter plotter) { 529 | plotters.remove(plotter); 530 | } 531 | 532 | /** 533 | * Gets an unmodifiable set of the plotter objects in the graph 534 | * 535 | * @return an unmodifiable {@link Set} of the plotter objects 536 | */ 537 | public Set getPlotters() { 538 | return Collections.unmodifiableSet(plotters); 539 | } 540 | 541 | @Override 542 | public int hashCode() { 543 | return name.hashCode(); 544 | } 545 | 546 | @Override 547 | public boolean equals(final Object object) { 548 | if (!(object instanceof Graph)) { 549 | return false; 550 | } 551 | 552 | final Graph graph = (Graph) object; 553 | return graph.name.equals(name); 554 | } 555 | 556 | /** 557 | * Called when the server owner decides to opt-out of Metrics while the server is running. 558 | */ 559 | protected void onOptOut() { 560 | } 561 | 562 | } 563 | 564 | /** 565 | * Interface used to collect custom data for a plugin 566 | */ 567 | public static abstract class Plotter { 568 | 569 | /** 570 | * The plot's name 571 | */ 572 | private final String name; 573 | 574 | /** 575 | * Construct a plotter with the default plot name 576 | */ 577 | public Plotter() { 578 | this("Default"); 579 | } 580 | 581 | /** 582 | * Construct a plotter with a specific plot name 583 | * 584 | * @param name the name of the plotter to use, which will show up on the website 585 | */ 586 | public Plotter(final String name) { 587 | this.name = name; 588 | } 589 | 590 | /** 591 | * Get the current value for the plotted point. Since this function defers to an external function 592 | * it may or may not return immediately thus cannot be guaranteed to be thread friendly or safe. 593 | * This function can be called from any thread so care should be taken when accessing resources 594 | * that need to be synchronized. 595 | * 596 | * @return the current value for the point to be plotted. 597 | */ 598 | public abstract int getValue(); 599 | 600 | /** 601 | * Get the column name for the plotted point 602 | * 603 | * @return the plotted point's column name 604 | */ 605 | public String getColumnName() { 606 | return name; 607 | } 608 | 609 | /** 610 | * Called after the website graphs have been updated 611 | */ 612 | public void reset() { 613 | } 614 | 615 | @Override 616 | public int hashCode() { 617 | return getColumnName().hashCode(); 618 | } 619 | 620 | @Override 621 | public boolean equals(final Object object) { 622 | if (!(object instanceof Plotter)) { 623 | return false; 624 | } 625 | 626 | final Plotter plotter = (Plotter) object; 627 | return plotter.name.equals(name) && plotter.getValue() == getValue(); 628 | } 629 | 630 | } 631 | 632 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. --------------------------------------------------------------------------------