diff options
80 files changed, 10042 insertions, 83 deletions
diff --git a/.gitignore b/.gitignore index 4d5963e6b..c1c78f213 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,9 @@ /YamlAnnotations/ /EssentialsUpdate/nbproject/private/ /EssentialsRelease/ -/EssentialsUpdate/
\ No newline at end of file +/EssentialsUpdate/dist/ +/EssentialsUpdate/build/ +/WebPush/apikey.php + +/WebPush/apikey.php +/WebPush/apikey.php
\ No newline at end of file diff --git a/Essentials/nbproject/project.properties b/Essentials/nbproject/project.properties index db78855e6..bea59e4ab 100644 --- a/Essentials/nbproject/project.properties +++ b/Essentials/nbproject/project.properties @@ -1,6 +1,7 @@ annotation.processing.enabled=true annotation.processing.enabled.in.editor=false -annotation.processing.run.all.processors=true +annotation.processing.processors.list=lombok.core.AnnotationProcessor +annotation.processing.run.all.processors=false annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output application.title=Essentials application.vendor= @@ -68,6 +69,7 @@ file.reference.iCo4.jar=../lib/iCo4.jar file.reference.iCo5.jar=../lib/iCo5.jar file.reference.iCo6.jar=../lib/iCo6.jar file.reference.junit-4.5.jar=..\\lib\\junit_4\\junit-4.5.jar +file.reference.lombok-0.10.1.jar=../lib/lombok-0.10.1.jar file.reference.MultiCurrency.jar=../lib/MultiCurrency.jar file.reference.Permissions3.jar=../lib/Permissions3.jar file.reference.PermissionsBukkit-1.2.jar=../lib/PermissionsBukkit-1.2.jar @@ -86,7 +88,8 @@ javac.classpath=\ ${file.reference.BOSEconomy7.jar}:\ ${file.reference.PermissionsEx.jar}:\ ${file.reference.bPermissions.jar}:\ - ${file.reference.PermissionsBukkit-1.2.jar} + ${file.reference.PermissionsBukkit-1.2.jar}:\ + ${file.reference.lombok-0.10.1.jar} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false diff --git a/Essentials/src/com/earth2me/essentials/Essentials.java b/Essentials/src/com/earth2me/essentials/Essentials.java index 6f9b9a944..fe0c3560c 100644 --- a/Essentials/src/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/com/earth2me/essentials/Essentials.java @@ -164,7 +164,6 @@ public class Essentials extends JavaPlugin implements IEssentials pm.registerEvent(Type.PLAYER_EGG_THROW, playerListener, Priority.High, this); pm.registerEvent(Type.PLAYER_BUCKET_EMPTY, playerListener, Priority.High, this); pm.registerEvent(Type.PLAYER_ANIMATION, playerListener, Priority.High, this); - pm.registerEvent(Type.PLAYER_BED_ENTER, playerListener, Priority.Lowest, this); final EssentialsBlockListener blockListener = new EssentialsBlockListener(this); pm.registerEvent(Type.BLOCK_PLACE, blockListener, Priority.Lowest, this); diff --git a/Essentials/src/com/earth2me/essentials/EssentialsPlayerListener.java b/Essentials/src/com/earth2me/essentials/EssentialsPlayerListener.java index 3d5851d93..9147acaf0 100644 --- a/Essentials/src/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/com/earth2me/essentials/EssentialsPlayerListener.java @@ -388,16 +388,4 @@ public class EssentialsPlayerListener extends PlayerListener user.updateActivity(true); } } - - @Override - public void onPlayerBedEnter(PlayerBedEnterEvent event) - { - if (event.isCancelled()) { - return; - } - if (event.getPlayer().isSleepingIgnored()) { - event.setCancelled(true); - event.getPlayer().sendMessage("You can't go to bed, your sleep is ignored."); - } - } } diff --git a/Essentials/src/com/earth2me/essentials/Settings.java b/Essentials/src/com/earth2me/essentials/Settings.java index 9d11d675b..e59c3c2b2 100644 --- a/Essentials/src/com/earth2me/essentials/Settings.java +++ b/Essentials/src/com/earth2me/essentials/Settings.java @@ -46,7 +46,7 @@ public class Settings implements ISettings @Override public int getHomeLimit(final User user) { - final List<String> homeList = getMultipleHomes(); + final List<String> homeList = getMultipleHomes(); if (homeList == null) { //TODO: Replace this code to remove backwards compat, after settings are automatically updated @@ -56,8 +56,7 @@ public class Settings implements ISettings int limit = getHomeLimit("default"); for (String set : homeList) { - logger.log(Level.INFO, "Found home set: " + set); - if (user.hasPermission("essentials.sethome.multiple." + set) && limit < getHomeLimit(set)) + if (user.isAuthorized("essentials.sethome.multiple." + set) && (limit < getHomeLimit(set))) { limit = getHomeLimit(set); } @@ -67,7 +66,7 @@ public class Settings implements ISettings @Override public int getHomeLimit(final String set) - { + { return config.getInt("sethome-multiple." + set, config.getInt("sethome-multiple.default", 3)); } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandban.java b/Essentials/src/com/earth2me/essentials/commands/Commandban.java index d6387c074..a4a5e2839 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandban.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandban.java @@ -46,7 +46,7 @@ public class Commandban extends EssentialsCommand if (args.length > 1) { banReason = getFinalArg(args, 1); - player.setBanReason(commandLabel); + player.setBanReason(banReason); } else { diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandcompass.java b/Essentials/src/com/earth2me/essentials/commands/Commandcompass.java index 8d582a296..eae10f0a5 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandcompass.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandcompass.java @@ -15,17 +15,44 @@ public class Commandcompass extends EssentialsCommand @Override public void run(Server server, User user, String commandLabel, String[] args) throws Exception { - int r = (int)user.getLocation().getYaw(); + int r = (int)(user.getLocation().getYaw() + 180 + 360) % 360; String dir; - if (r < 23) dir = "N"; - else if (r < 68) dir = "NE"; - else if (r < 113) dir = "E"; - else if (r < 158) dir = "SE"; - else if (r < 203) dir = "S"; - else if (r < 248) dir = "SW"; - else if (r < 293) dir = "W"; - else if (r < 338) dir = "NW"; - else dir = "N"; + if (r < 23) + { + dir = "N"; + } + else if (r < 68) + { + dir = "NE"; + } + else if (r < 113) + { + dir = "E"; + } + else if (r < 158) + { + dir = "SE"; + } + else if (r < 203) + { + dir = "S"; + } + else if (r < 248) + { + dir = "SW"; + } + else if (r < 293) + { + dir = "W"; + } + else if (r < 338) + { + dir = "NW"; + } + else + { + dir = "N"; + } user.sendMessage(Util.format("compassBearing", dir, r)); } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandgetpos.java b/Essentials/src/com/earth2me/essentials/commands/Commandgetpos.java index 12eeb5182..6f1fd7d6c 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandgetpos.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandgetpos.java @@ -16,10 +16,10 @@ public class Commandgetpos extends EssentialsCommand public void run(Server server, User user, String commandLabel, String[] args) throws Exception { Location coords = user.getLocation(); - user.sendMessage("§7X: " + coords.getBlockX() + " (-North <-> +South)"); + user.sendMessage("§7X: " + coords.getBlockX() + " (+East <-> -West)"); user.sendMessage("§7Y: " + coords.getBlockY() + " (+Up <-> -Down)"); - user.sendMessage("§7Z: " + coords.getBlockZ() + " (+East <-> -West)"); - user.sendMessage("§7Yaw: " + coords.getYaw() + " (Rotation)"); + user.sendMessage("§7Z: " + coords.getBlockZ() + " (+South <-> -North)"); + user.sendMessage("§7Yaw: " + (coords.getYaw() + 180 + 360) % 360 + " (Rotation)"); user.sendMessage("§7Pitch: " + coords.getPitch() + " (Head angle)"); } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandmail.java b/Essentials/src/com/earth2me/essentials/commands/Commandmail.java index acffd57a1..ddc26aadc 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandmail.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandmail.java @@ -5,6 +5,7 @@ import org.bukkit.Server; import com.earth2me.essentials.User; import com.earth2me.essentials.Util; import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -69,4 +70,59 @@ public class Commandmail extends EssentialsCommand } throw new NotEnoughArgumentsException(); } + + @Override + protected void run(Server server, CommandSender sender, String commandLabel, String[] args) throws Exception + { + if (args.length >= 1 && "read".equalsIgnoreCase(args[0])) + { + throw new Exception(Util.format("onlyPlayers", commandLabel + " read")); + } + else if (args.length >= 1 && "clear".equalsIgnoreCase(args[0])) + { + throw new Exception(Util.format("onlyPlayers", commandLabel + " clear")); + } + else if (args.length >= 3 && "send".equalsIgnoreCase(args[0])) + { + Player player = server.getPlayer(args[1]); + User u; + if (player != null) + { + u = ess.getUser(player); + } + else + { + u = ess.getOfflineUser(args[1]); + } + if (u == null) + { + throw new Exception(Util.format("playerNeverOnServer", args[1])); + } + u.addMail("Server: " + getFinalArg(args, 2)); + sender.sendMessage(Util.i18n("mailSent")); + return; + } + else if (args.length >= 2) + { + //allow sending from console without "send" argument, since it's the only thing the console can do + Player player = server.getPlayer(args[0]); + User u; + if (player != null) + { + u = ess.getUser(player); + } + else + { + u = ess.getOfflineUser(args[0]); + } + if (u == null) + { + throw new Exception(Util.format("playerNeverOnServer", args[0])); + } + u.addMail("Server: " + getFinalArg(args, 1)); + sender.sendMessage(Util.i18n("mailSent")); + return; + } + throw new NotEnoughArgumentsException(); + } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandrealname.java b/Essentials/src/com/earth2me/essentials/commands/Commandrealname.java index 5e12c535d..5ed4ef9e6 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandrealname.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandrealname.java @@ -1,6 +1,7 @@ package com.earth2me.essentials.commands; import org.bukkit.Server; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import com.earth2me.essentials.User; import com.earth2me.essentials.Util; @@ -15,7 +16,7 @@ public class Commandrealname extends EssentialsCommand } @Override - public void run(Server server, User user, String commandLabel, String[] args) throws Exception + protected void run(Server server, CommandSender sender, String commandLabel, String[] args) throws Exception { if (args.length < 1) { @@ -36,7 +37,7 @@ public class Commandrealname extends EssentialsCommand { continue; } - user.sendMessage(u.getDisplayName() + " " + Util.i18n("is") + " " + u.getName()); + sender.sendMessage(u.getDisplayName() + " " + Util.i18n("is") + " " + u.getName()); } } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandweather.java b/Essentials/src/com/earth2me/essentials/commands/Commandweather.java index 45c62d787..9603e9f72 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandweather.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandweather.java @@ -4,6 +4,7 @@ import com.earth2me.essentials.User; import com.earth2me.essentials.Util; import org.bukkit.Server; import org.bukkit.World; +import org.bukkit.command.CommandSender; public class Commandweather extends EssentialsCommand @@ -29,16 +30,50 @@ public class Commandweather extends EssentialsCommand world.setStorm(isStorm ? true : false); world.setWeatherDuration(Integer.parseInt(args[1]) * 20); user.sendMessage(isStorm - ? Util.format("weatherStormFor", args[1]) - : Util.format("weatherSunFor", args[1])); + ? Util.format("weatherStormFor", world.getName(), args[1]) + : Util.format("weatherSunFor", world.getName(), args[1])); return; } else { world.setStorm(isStorm ? true : false); user.sendMessage(isStorm - ? Util.i18n("weatherStorm") - : Util.i18n("weatherSun")); + ? Util.format("weatherStorm", world.getName()) + : Util.format("weatherSun", world.getName())); + return; + } + } + + @Override + protected void run(Server server, CommandSender sender, String commandLabel, String[] args) throws Exception + { + if (args.length < 2) //running from console means inserting a world arg before other args + { + throw new Exception("When running from console, usage is: /" + commandLabel + " <world> <storm/sun> [duration]"); + } + + boolean isStorm = args[1].equalsIgnoreCase("storm"); + World world = server.getWorld(args[0]); + if (world == null) + { + throw new Exception("World named " + args[0] + " not found!"); + } + if (args.length > 2) + { + + world.setStorm(isStorm ? true : false); + world.setWeatherDuration(Integer.parseInt(args[2]) * 20); + sender.sendMessage(isStorm + ? Util.format("weatherStormFor", world.getName(), args[2]) + : Util.format("weatherSunFor", world.getName(), args[2])); + return; + } + else + { + world.setStorm(isStorm ? true : false); + sender.sendMessage(isStorm + ? Util.format("weatherStorm", world.getName()) + : Util.format("weatherSun", world.getName())); return; } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandworth.java b/Essentials/src/com/earth2me/essentials/commands/Commandworth.java index b59070320..2a7e107bc 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandworth.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandworth.java @@ -3,6 +3,8 @@ package com.earth2me.essentials.commands; import org.bukkit.Server; import com.earth2me.essentials.User; import com.earth2me.essentials.Util; + +import org.bukkit.command.CommandSender; import org.bukkit.inventory.ItemStack; @@ -33,7 +35,7 @@ public class Commandworth extends EssentialsCommand } catch (NumberFormatException ex) { - amount = 64; + amount = is.getType().getMaxStackSize(); } is.setAmount(amount); @@ -56,4 +58,49 @@ public class Commandworth extends EssentialsCommand amount, Util.formatCurrency(worth, ess))); } + + @Override + protected void run(Server server, CommandSender sender, String commandLabel, String[] args) throws Exception + { + if (args.length < 1) + { + throw new NotEnoughArgumentsException(); + } + + ItemStack is = ess.getItemDb().get(args[0]); + int amount = is.getAmount(); + + try + { + if (args.length > 1) + { + amount = Integer.parseInt(args[1]); + } + } + catch (NumberFormatException ex) + { + amount = is.getType().getMaxStackSize(); + } + + is.setAmount(amount); + double worth = ess.getWorth().getPrice(is); + if (Double.isNaN(worth)) + { + throw new Exception(Util.i18n("itemCannotBeSold")); + } + + sender.sendMessage(is.getDurability() != 0 + ? Util.format("worthMeta", + is.getType().toString().toLowerCase().replace("_", ""), + is.getDurability(), + Util.formatCurrency(worth * amount, ess), + amount, + Util.formatCurrency(worth, ess)) + : Util.format("worth", + is.getType().toString().toLowerCase().replace("_", ""), + Util.formatCurrency(worth * amount, ess), + amount, + Util.formatCurrency(worth, ess))); + + } } diff --git a/Essentials/src/com/earth2me/essentials/settings/Backup.java b/Essentials/src/com/earth2me/essentials/settings/Backup.java new file mode 100644 index 000000000..1b59260db --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/Backup.java @@ -0,0 +1,17 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Backup extends StorageObject +{ + @Comment("Interval in minutes") + private long interval = 60; + @Comment("Add a command that backups your data, e.g. 'rdiff-backup World1 backups/World1'") + private String command; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/Chat.java b/Essentials/src/com/earth2me/essentials/settings/Chat.java new file mode 100644 index 000000000..7c02c0e88 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/Chat.java @@ -0,0 +1,32 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Chat extends StorageObject +{ + @Comment("The character(s) to prefix all nicknames, so that you know they are not true usernames.") + private String nicknamePrefix = "~"; + + @Comment("Disable this if you have any other plugin, that modifies the displayname of a user.") + private boolean changeDisplayname = true; + + private String displaynameFormat = "{PREFIX}{NICKNAMEPREFIX}{NAME}{SUFFIX}"; + + @Comment({ + "If EssentialsChat is installed, this will define how far a player's voice travels, in blocks. Set to 0 to make all chat global.", + "Note that users with the \"essentials.chat.spy\" permission will hear everything, regardless of this setting.", + "Users with essentials.chat.shout can override this by prefixing text with an exclamation mark (!)", + "Or with essentials.chat.question can override this by prefixing text with a question mark (?)", + "You can add command costs for shout/question by adding chat-shout and chat-question to the command costs section." + }) + private int localRadius = 0; + + @Comment("Set the default chat format here, it will be overwritten by group specific chat formats.") + private String defaultFormat = "&7[{GROUP}]&f {DISPLAYNAME}&7:&f {MESSAGE}"; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/Commands.java b/Essentials/src/com/earth2me/essentials/settings/Commands.java new file mode 100644 index 000000000..771cef12b --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/Commands.java @@ -0,0 +1,47 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.settings.commands.Afk; +import com.earth2me.essentials.settings.commands.God; +import com.earth2me.essentials.settings.commands.Help; +import com.earth2me.essentials.settings.commands.Home; +import com.earth2me.essentials.settings.commands.Kit; +import com.earth2me.essentials.settings.commands.Lightning; +import com.earth2me.essentials.settings.commands.Spawnmob; +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.ListType; +import com.earth2me.essentials.storage.StorageObject; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Commands extends StorageObject +{ + private Afk afk = new Afk(); + private God god = new God(); + private Help help = new Help(); + private Home home = new Home(); + private Kit kit = new Kit(); + private Lightning lightning = new Lightning(); + private Spawnmob spawnmob = new Spawnmob(); + @ListType + @Comment( + { + "When a command conflicts with another plugin, by default, Essentials will try to force the OTHER plugin to take", + "priority. If a command is in this list, Essentials will try to give ITSELF priority. This does not always work:", + "usually whichever plugin was updated most recently wins out. However, the full name of the command will always work.", + "For example, if WorldGuard and Essentials are both enabled, and WorldGuard takes control over /god, /essentials:god", + "will still map to Essentials, whereas it might normally get forced upon WorldGuard. Commands prefixed with an \"e\",", + "such as /egod, will always grant Essentials priority.", + "We should try to take priority over /god. If this doesn't work, use /essentials:god or /egod.", + "If god is set using WorldGuard, use /ungod to remove then use whichever you see fit." + }) + private List<String> overwritten = new ArrayList<String>(); + + @ListType + @Comment("Disabled commands will be completelly unavailable on the server.") + private List<String> disabled = new ArrayList<String>(); +} diff --git a/Essentials/src/com/earth2me/essentials/settings/Economy.java b/Essentials/src/com/earth2me/essentials/settings/Economy.java new file mode 100644 index 000000000..b18f05b96 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/Economy.java @@ -0,0 +1,42 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.MapType; +import com.earth2me.essentials.storage.StorageObject; +import java.util.HashMap; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Economy extends StorageObject +{ + @Comment("Defines the balance with which new players begin. Defaults to 0.") + private double startingBalance = 0.0; + @MapType(Double.class) + @Comment("Defines the cost to use the given commands PER USE") + private Map<String, Double> commandCosts = new HashMap<String, Double>(); + @Comment("Set this to a currency symbol you want to use.") + private String currencySymbol = "$"; + + public String getCurrencySymbol() + { + return currencySymbol == null || currencySymbol.isEmpty() ? "$" : currencySymbol.substring(0, 1); + } + private final transient static double MAXMONEY = 10000000000000.0; + @Comment( + { + "Set the maximum amount of money a player can have", + "The amount is always limited to 10 trillions because of the limitations of a java double" + }) + private double maxMoney = MAXMONEY; + + public double getMaxMoney() + { + return Math.abs(maxMoney) > MAXMONEY ? MAXMONEY : Math.abs(maxMoney); + } + @Comment("Enable this to log all interactions with trade/buy/sell signs and sell command") + private boolean logEnabled = false; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/General.java b/Essentials/src/com/earth2me/essentials/settings/General.java new file mode 100644 index 000000000..77143eb1a --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/General.java @@ -0,0 +1,33 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class General extends StorageObject +{ + @Comment("Backup runs a command while saving is disabled") + private Backup backup = new Backup(); + @Comment("You can disable the death messages of minecraft.") + private boolean deathMessages = true; + @Comment("Turn this on, if you want to see more error messages, if something goes wrong.") + private boolean debug = false; + @Comment( + { + "Set the locale here, if you want to change the language of Essentials.", + "If this is not set, Essentials will use the language of your computer.", + "Available locales: da, de, en, fr, nl" + }) + private String locale; + @Comment( + { + "Should we announce to the server when someone logs in for the first time?", + "If so, use this format, replacing {DISPLAYNAME} with the player name.", + "If not, set to ''" + }) + private String newPlayerAnnouncement = "&dWelcome {DISPLAYNAME} to the server!"; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/GroupOptions.java b/Essentials/src/com/earth2me/essentials/settings/GroupOptions.java new file mode 100644 index 000000000..1e0137302 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/GroupOptions.java @@ -0,0 +1,27 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class GroupOptions extends StorageObject +{ + @Comment("Message format of chat messages") + private String messageFormat; + @Comment("Prefix for name") + private String prefix; + @Comment("Suffix for name") + private String suffix; + @Comment("Amount of homes a player can have") + private Integer homes; + @Comment("Cooldown between teleports") + private Integer teleportCooldown; + @Comment("Delay before teleport") + private Integer teleportDelay; + @Comment("Cooldown between heals") + private Integer healCooldown; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/Groups.java b/Essentials/src/com/earth2me/essentials/settings/Groups.java new file mode 100644 index 000000000..06565d376 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/Groups.java @@ -0,0 +1,27 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.MapType; +import com.earth2me.essentials.storage.StorageObject; +import java.util.LinkedHashMap; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Groups extends StorageObject +{ + public Groups() { + GroupOptions defaultOptions = new GroupOptions(); + groups.put("default", defaultOptions); + } + @Comment( + { + "The order of the groups matters, the groups are checked from top to bottom.", + "All group names have to be lower case.", + "The groups can be connected to users using the permission essentials.groups.groupname" + }) + @MapType(GroupOptions.class) + private LinkedHashMap<String, GroupOptions> groups = new LinkedHashMap<String, GroupOptions>(); +} diff --git a/Essentials/src/com/earth2me/essentials/settings/Location.java b/Essentials/src/com/earth2me/essentials/settings/Location.java new file mode 100644 index 000000000..0535fdf52 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/Location.java @@ -0,0 +1,28 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.bukkit.Server; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Location extends StorageObject +{ + private String worldName = "Test"; + private double x; + private double y; + private double z; + private Float yaw; + private Float pitch; + + public org.bukkit.Location getBukkit(Server server) + { + if (yaw == null || pitch == null) + { + return new org.bukkit.Location(server.getWorld(worldName), x, y, z); + } + return new org.bukkit.Location(server.getWorld(worldName), x, y, z, yaw, pitch); + } +} diff --git a/Essentials/src/com/earth2me/essentials/settings/Settings.java b/Essentials/src/com/earth2me/essentials/settings/Settings.java new file mode 100644 index 000000000..890bfef4e --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/Settings.java @@ -0,0 +1,58 @@ +package com.earth2me.essentials.settings; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Settings extends StorageObject +{ + @Comment( + { + "##########################################################", + "+------------------------------------------------------+ #", + "| General Settings | #", + "+------------------------------------------------------+ #", + "##########################################################" + }) + private General general = new General(); + @Comment( + { + "##########################################################", + "+------------------------------------------------------+ #", + "| Chat Settings | #", + "+------------------------------------------------------+ #", + "##########################################################" + }) + private Chat chat = new Chat(); + @Comment( + { + "##########################################################", + "+------------------------------------------------------+ #", + "| Economy Settings | #", + "+------------------------------------------------------+ #", + "##########################################################" + }) + private Economy economy = new Economy(); + @Comment( + { + "##########################################################", + "+------------------------------------------------------+ #", + "| Commands Settings | #", + "+------------------------------------------------------+ #", + "##########################################################" + }) + private Commands commands = new Commands(); + @Comment( + { + "##########################################################", + "+------------------------------------------------------+ #", + "| Group Settings | #", + "+------------------------------------------------------+ #", + "##########################################################" + }) + private Groups groups = new Groups(); +} diff --git a/Essentials/src/com/earth2me/essentials/settings/commands/Afk.java b/Essentials/src/com/earth2me/essentials/settings/commands/Afk.java new file mode 100644 index 000000000..20076c273 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/commands/Afk.java @@ -0,0 +1,36 @@ +package com.earth2me.essentials.settings.commands; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Afk extends StorageObject +{ + @Comment( + { + "After this timeout in seconds, the user will be set as afk.", + "Set to -1 for no timeout." + }) + private int autoAFK = 300; + @Comment( + { + "Auto-AFK Kick", + "After this timeout in seconds, the user will be kicked from the server.", + "Set to -1 for no timeout." + }) + private int autoAFKKick = -1; + @Comment( + { + "Set this to true, if you want to freeze the player, if he is afk.", + "Other players or monsters can't push him out of afk mode then.", + "This will also enable temporary god mode for the afk player.", + "The player has to use the command /afk to leave the afk mode.", + "You have to add a message to your welcome message or help page,", + "since the player will not get a message, if he tries to move." + }) + private boolean freezeAFKPlayers = false; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/commands/God.java b/Essentials/src/com/earth2me/essentials/settings/commands/God.java new file mode 100644 index 000000000..7740eaab1 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/commands/God.java @@ -0,0 +1,14 @@ +package com.earth2me.essentials.settings.commands; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper=false) +public class God extends StorageObject +{ + @Comment("Turn off god mode when people exit") + private boolean removeOnDisconnect = false; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/commands/Help.java b/Essentials/src/com/earth2me/essentials/settings/commands/Help.java new file mode 100644 index 000000000..03a9d5958 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/commands/Help.java @@ -0,0 +1,23 @@ +package com.earth2me.essentials.settings.commands; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Help extends StorageObject +{ + @Comment("Show other plugins commands in help") + private boolean showNonEssCommandsInHelp = true; + @Comment( + { + "Hide plugins which don't give a permission in their plugin.yml for each command.", + "You can override a true value here for a single plugin by adding a permission to a user/group.", + "The individual permission is: essentials.help.<plugin>, anyone with essentials.* or '*' will see all help this setting reguardless.", + "You can use negative permissions to remove access to just a single plugins help if the following is enabled." + }) + private boolean hidePermissionlessCommands = true; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/commands/Home.java b/Essentials/src/com/earth2me/essentials/settings/commands/Home.java new file mode 100644 index 000000000..6ec2f1339 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/commands/Home.java @@ -0,0 +1,24 @@ +package com.earth2me.essentials.settings.commands; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Home extends StorageObject +{ + @Comment("When players die, should they respawn at their homes, instead of the spawnpoint?") + private boolean respawnAtHome = false; + @Comment( + { + "When a player interacts with a bed, should their home be set to that location?", + "If you enable this and remove default player access to the /sethome command, ", + "you can make beds the only way for players to set their home location." + }) + private boolean bedSetsHome = false; + @Comment("If no home is set, should the player be send to spawn, when /home is used.") + private boolean spawnIfNoHome = false; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/commands/Kit.java b/Essentials/src/com/earth2me/essentials/settings/commands/Kit.java new file mode 100644 index 000000000..59b0b9a82 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/commands/Kit.java @@ -0,0 +1,28 @@ +package com.earth2me.essentials.settings.commands; + +import com.earth2me.essentials.storage.MapType; +import com.earth2me.essentials.storage.StorageObject; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class Kit extends StorageObject +{ + + public Kit() + { + final KitObject kit = new KitObject(); + kit.setDelay(10.0); + kit.setItems(Arrays.asList("277 1,278 1,279 1".split(","))); + kits.put("tools", kit); + } + + + @MapType(KitObject.class) + private Map<String,KitObject> kits = new HashMap<String, KitObject>(); +} diff --git a/Essentials/src/com/earth2me/essentials/settings/commands/KitObject.java b/Essentials/src/com/earth2me/essentials/settings/commands/KitObject.java new file mode 100644 index 000000000..93f6c6ade --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/commands/KitObject.java @@ -0,0 +1,18 @@ +package com.earth2me.essentials.settings.commands; + +import com.earth2me.essentials.storage.ListType; +import com.earth2me.essentials.storage.StorageObject; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class KitObject extends StorageObject +{ + @ListType + private List<String> items = new ArrayList<String>(); + private Double delay; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/commands/Lightning.java b/Essentials/src/com/earth2me/essentials/settings/commands/Lightning.java new file mode 100644 index 000000000..510857247 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/commands/Lightning.java @@ -0,0 +1,14 @@ +package com.earth2me.essentials.settings.commands; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class Lightning extends StorageObject +{ + @Comment("Shall we notify users when using /lightning") + private boolean warnPlayer = true; +} diff --git a/Essentials/src/com/earth2me/essentials/settings/commands/Spawnmob.java b/Essentials/src/com/earth2me/essentials/settings/commands/Spawnmob.java new file mode 100644 index 000000000..771da32d1 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/settings/commands/Spawnmob.java @@ -0,0 +1,14 @@ +package com.earth2me.essentials.settings.commands; + +import com.earth2me.essentials.storage.Comment; +import com.earth2me.essentials.storage.StorageObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class Spawnmob extends StorageObject +{ + @Comment("The maximum amount of monsters, a player can spawn with a call of /spawnmob.") + private int limit = 10; +} diff --git a/Essentials/src/com/earth2me/essentials/storage/Comment.java b/Essentials/src/com/earth2me/essentials/storage/Comment.java new file mode 100644 index 000000000..b43cec980 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/storage/Comment.java @@ -0,0 +1,16 @@ +package com.earth2me.essentials.storage; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Target(ElementType.FIELD) +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Comment +{ + String[] value() default ""; +}
\ No newline at end of file diff --git a/Essentials/src/com/earth2me/essentials/storage/ListType.java b/Essentials/src/com/earth2me/essentials/storage/ListType.java new file mode 100644 index 000000000..9bf6e2e64 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/storage/ListType.java @@ -0,0 +1,14 @@ +package com.earth2me.essentials.storage; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ListType +{ + Class value() default String.class; +} diff --git a/Essentials/src/com/earth2me/essentials/storage/MapType.java b/Essentials/src/com/earth2me/essentials/storage/MapType.java new file mode 100644 index 000000000..dc5636315 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/storage/MapType.java @@ -0,0 +1,14 @@ +package com.earth2me.essentials.storage; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface MapType +{ + Class value() default String.class; +}
\ No newline at end of file diff --git a/Essentials/src/com/earth2me/essentials/storage/StorageObject.java b/Essentials/src/com/earth2me/essentials/storage/StorageObject.java new file mode 100644 index 000000000..a35338516 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/storage/StorageObject.java @@ -0,0 +1,250 @@ +package com.earth2me.essentials.storage; + +import java.io.PrintWriter; +import java.io.Reader; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.TypeDescription; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.composer.Composer; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.introspector.PropertyUtils; + + +public class StorageObject +{ + protected Class<? extends StorageObject> clazz; + + protected StorageObject() + { + } + private static Map<Class, Constructor> constructors = new HashMap<Class, Constructor>(); + + public static <T extends StorageObject> T load(Class<? extends T> clazz, Reader reader) + { + Constructor constructor; + if (constructors.containsKey(clazz)) + { + constructor = constructors.get(clazz); + } + else + { + constructor = prepareConstructor(clazz); + constructors.put(clazz, constructor); + } + + final Yaml yaml = new Yaml(constructor); + T ret = (T)yaml.load(reader); + if (ret == null) + { + try + { + ret = (T)clazz.newInstance(); + } + catch (InstantiationException ex) + { + Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex); + } + catch (IllegalAccessException ex) + { + Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex); + } + } + ret.clazz = clazz; + return ret; + } + + private static Constructor prepareConstructor(final Class<?> clazz) + { + final Constructor constructor = new Constructor(clazz); + final Set<Class> classes = new HashSet<Class>(); + + prepareConstructor(constructor, classes, clazz); + return constructor; + } + + private static void prepareConstructor(final Constructor constructor, final Set<Class> classes, final Class clazz) + { + classes.add(clazz); + final TypeDescription description = new TypeDescription(clazz); + for (Field field : clazz.getDeclaredFields()) + { + final ListType listType = field.getAnnotation(ListType.class); + if (listType != null) + { + description.putListPropertyType(field.getName(), listType.value()); + if (StorageObject.class.isAssignableFrom(listType.value()) + && !classes.contains(listType.value())) + { + prepareConstructor(constructor, classes, listType.value()); + } + } + final MapType mapType = field.getAnnotation(MapType.class); + if (mapType != null) + { + description.putMapPropertyType(field.getName(), String.class, mapType.value()); + if (StorageObject.class.isAssignableFrom(mapType.value()) + && !classes.contains(mapType.value())) + { + prepareConstructor(constructor, classes, mapType.value()); + } + } + if (StorageObject.class.isAssignableFrom(field.getType()) + && !classes.contains(field.getType())) + { + prepareConstructor(constructor, classes, field.getType()); + } + } + constructor.addTypeDescription(description); + } + private transient Yaml yaml; + + public void save(final PrintWriter writer) + { + final DumperOptions ops = new DumperOptions(); + yaml = new Yaml(ops); + try + { + writeToFile(this, writer, 0, clazz); + } + catch (IllegalArgumentException ex) + { + Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex); + } + catch (IllegalAccessException ex) + { + Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private void writeToFile(final Object object, final PrintWriter writer, final int depth, final Class clazz) throws IllegalArgumentException, IllegalAccessException + { + for (Field field : clazz.getDeclaredFields()) + { + final int modifier = field.getModifiers(); + if (Modifier.isPrivate(modifier) && !Modifier.isTransient(modifier) && !Modifier.isStatic(modifier)) + { + field.setAccessible(true); + final boolean commentPresent = field.isAnnotationPresent(Comment.class); + final String name = field.getName(); + if (commentPresent) + { + final Comment comments = field.getAnnotation(Comment.class); + for (String comment : comments.value()) + { + final String trimmed = comment.trim(); + if (trimmed.isEmpty()) + { + continue; + } + writeIndention(writer, depth); + writer.print("# "); + writer.print(trimmed); + writer.println(); + } + } + + final Object data = field.get(object); + if (data == null && !commentPresent) + { + continue; + } + writeIndention(writer, depth); + if (data == null && commentPresent) + { + writer.print('#'); + } + writer.print(name); + writer.print(": "); + if (data == null && commentPresent) + { + writer.println(); + writer.println(); + continue; + } + if (data instanceof StorageObject) + { + writer.println(); + writeToFile(data, writer, depth + 1, data.getClass()); + } + else if (data instanceof Map) + { + writer.println(); + for (Entry<String, Object> entry : ((Map<String, Object>)data).entrySet()) + { + final Object value = entry.getValue(); + if (value != null) + { + writeIndention(writer, depth + 1); + writer.print(entry.getKey()); + writer.print(": "); + if (value instanceof StorageObject) + { + writer.println(); + writeToFile(value, writer, depth + 2, value.getClass()); + } + else if (value instanceof String || value instanceof Boolean || value instanceof Number) + { + yaml.dumpAll(Collections.singletonList(value).iterator(), writer); + writer.println(); + } + else + { + throw new UnsupportedOperationException(); + } + + } + } + } + else if (data instanceof Collection) + { + writer.println(); + for (Object entry : (Collection<Object>)data) + { + if (entry != null) + { + writeIndention(writer, depth + 1); + writer.print("- "); + if (entry instanceof String || entry instanceof Boolean || entry instanceof Number) + { + yaml.dumpAll(Collections.singletonList(entry).iterator(), writer); + } + else + { + throw new UnsupportedOperationException(); + } + } + } + writer.println(); + } + else if (data instanceof String || data instanceof Boolean || data instanceof Number) + { + yaml.dumpAll(Collections.singletonList(data).iterator(), writer); + writer.println(); + } + else + { + throw new UnsupportedOperationException(); + } + } + } + } + + private void writeIndention(final PrintWriter writer, final int depth) + { + for (int i = 0; i < depth; i++) + { + writer.print(" "); + } + } +} diff --git a/Essentials/src/messages.properties b/Essentials/src/messages.properties index c756d4384..0eb9f9d37 100644 --- a/Essentials/src/messages.properties +++ b/Essentials/src/messages.properties @@ -351,10 +351,10 @@ warpSet = \u00a77Warp {0} set. warpUsePermission = \u00a7cYou do not have Permission to use that warp. warpingTo = \u00a77Warping to {0}. warpsCount = \u00a77There are {0} warps. Showing page {1} of {2}. -weatherStorm = \u00a77You set the weather to storm in your world -weatherStormFor = \u00a77You set the weather to storm in your world for {0} seconds -weatherSun = \u00a77You set the weather to sun in your world -weatherSunFor = \u00a77You set the weather to sun in your world for {0} seconds +weatherStorm = \u00a77You set the weather to storm in {0} +weatherStormFor = \u00a77You set the weather to storm in {0} for {1} seconds +weatherSun = \u00a77You set the weather to sun in {0} +weatherSunFor = \u00a77You set the weather to sun in {0} for {1} seconds whoisGeoLocation = \u00a79 - Location: {0} whoisHealth = \u00a79 - Health: {0}/20 whoisIPAddress = \u00a79 - IP Address: {0} diff --git a/Essentials/src/messages_da.properties b/Essentials/src/messages_da.properties index 1f21b1a50..7526e4858 100644 --- a/Essentials/src/messages_da.properties +++ b/Essentials/src/messages_da.properties @@ -350,10 +350,10 @@ warpSet = \u00a77Warp {0} sat. warpUsePermission = \u00a7cDu har ikke tilladelse til at benytte den warp. warpingTo = \u00a77Warper til {0}. warpsCount = \u00a77There are {0} warps. Showing page {1} of {2}. -weatherStorm = \u00a77Du har sat vejret til storm i din verden -weatherStormFor = \u00a77Du har sat vejret til storm i din verden i {0} sekunder -weatherSun = \u00a77Du har sat vejret til sol -weatherSunFor = \u00a77Du har sat vejret til sol i din verden i {0} sekunder +weatherStorm = \u00a77Du har sat vejret til storm i {0} +weatherStormFor = \u00a77Du har sat vejret til storm i {0} i {1} sekunder +weatherSun = \u00a77Du har sat vejret til sol i {0} +weatherSunFor = \u00a77Du har sat vejret til sol i {0} i {1} sekunder whoisGeoLocation = \u00a79 - Placering: {0} whoisHealth = \u00a79 - Helbred: {0}/20 whoisIPAddress = \u00a79 - IP Addresse: {0} diff --git a/Essentials/src/messages_de.properties b/Essentials/src/messages_de.properties index e7c0aa8b3..5ccedd370 100644 --- a/Essentials/src/messages_de.properties +++ b/Essentials/src/messages_de.properties @@ -53,9 +53,9 @@ deleteHome = \u00a77Zuhause {0} wurde gel\u00f6scht. deleteJail = \u00a77Gef\u00e4ngnis {0} wurde gel\u00f6scht. deleteWarp = \u00a77Warp-Punkt {0} wurde gel\u00f6scht. deniedAccessCommand = {0} hat keinen Zugriff auf diesen Befehl. -dependancyDownloaded = [Essentials] Dependancy {0} downloaded successfully. -dependancyException = [Essentials] An error occured when trying to download a dependacy -dependancyNotFound = [Essentials] A required dependancy was not found, downloading now. +dependancyDownloaded = [Essentials] Abh\u00e4ngigkeit {0} erfolgreich runtergeladen. +dependancyException = [Essentials] W\u00e4hrend dem Download von einer Abh\u00e4ngigkeit ist ein Fehler aufgetreten. +dependancyNotFound = [Essentials] Eine erforderliche Abh\u00e4ngigkeit wurde nicht gefunde, Download startet jetzt.. depth = \u00a77Du bist auf Meeresh\u00f6he. depthAboveSea = \u00a77Du bist {0} Bl\u00f6cke \u00fcber Meeresh\u00f6he. depthBelowSea = \u00a77Du bist {0} Bl\u00f6cke unter Meeresh\u00f6he. @@ -87,7 +87,7 @@ generatingPortal = \u00a77Erstelle ein Ausgangsportal. geoIpUrlEmpty = GeoIP Download-URL ist leer. geoIpUrlInvalid = GeoIP Download-URL ist ung\u00fcltig. geoipJoinFormat = Spieler {0} kommt aus {1} -godDisabledFor = aktiviert f\u00fcr {0} +godDisabledFor = deaktiviert f\u00fcr {0} godEnabledFor = aktiviert f\u00fcr {0} godMode = \u00a77Unsterblichkeit {0}. haveBeenReleased = \u00a77Du wurdest frei gelassen. @@ -99,7 +99,7 @@ helpPages = Seite \u00a7c{0}\u00a7f von \u00a7c{1}\u00a7f: holeInFloor = Loch im Boden homeSet = \u00a77Zuhause gesetzt. homeSetToBed = \u00a77Dein Zuhause ist nun an diesem Bett. -homes = Homes: {0} +homes = Heime: {0} hour = Stunde hours = Stunden ignorePlayer = Du ignorierst ab jetzt Spieler {0}. @@ -132,11 +132,11 @@ itemSoldConsole = {0} verkauft {1} f\u00fcr \u00a77{2}\u00a77 ({3} Einheiten je itemSpawn = \u00a77Gebe {0}x {1} itemsCsvNotLoaded = Konnte items.csv nicht laden. jailAlreadyIncarcerated = \u00a7cPerson is already in jail: {0} -jailMessage = \u00a7cYou do the crime, you do the time. +jailMessage = \u00a7cDu hast ein Verbrechen begangen, also hast du Zeit. jailNotExist = Dieses Gef\u00e4ngnis existiert nicht. -jailReleased = \u00a77Player \u00a7e{0}\u00a77 unjailed. -jailReleasedPlayerNotify = \u00a77You have been released! -jailSentenceExtended = Jail time extend to: {0) +jailReleased = \u00a77Spieler \u00a7e{0}\u00a77 wurde freigelassen. +jailReleasedPlayerNotify = \u00a77Du wurdest freigelassen! +jailSentenceExtended = Gef\u00e4ngnisszeit erweitert auf: {0) jailSet = \u00a77Gef\u00e4ngnis {0} wurde erstellt. jumpError = Das w\u00fcrde deinen Computer \u00fcberlasten. kickDefault = Vom Server geworfen @@ -193,7 +193,7 @@ nickSet = \u00a77Dein Nickname ist nun \u00a7c{0} noAccessCommand = \u00a7cDu hast keinen Zugriff auf diesen Befehl. noAccessPermission = \u00a7cDu hast keine Rechte, den Block {0} zu \u00f6ffnen. noDestroyPermission = \u00a7cDu hast keine Rechte, den Block {0} zu zerst\u00f6ren. -noHelpFound = \u00a7cNo matching commands. +noHelpFound = \u00a7cKeine \u00fcbereinstimmenden Kommandos. noHomeSet = Du hast kein Zuhause gesetzt. noHomeSetPlayer = Spieler hat kein Zuhause gesetzt. noKitPermission = \u00a7cDu brauchst die Berechtigung \u00a7c{0}\u00a7c um diese Ausr\u00fcstung anzufordern. @@ -204,7 +204,7 @@ noMotd = \u00a7cEs existiert keine Willkommensnachricht. noNewMail = \u00a77Du hast keine Nachrichten. noPendingRequest = Du hast keine Teleportierungsanfragen. noPlacePermission = \u00a7cDu hast keine Rechte, einen Block in der N\u00e4he des Schildes zu platzieren. -noPowerTools=You have no power tools assigned. +noPowerTools = Du hast keine Powertools zugewiesen. noRules = \u00a7cEs wurden keine Regeln definiert. noWarpsDefined = Keine Warp-Punkte erstellt. none = keine @@ -225,7 +225,7 @@ pTimeOthersPermission = \u00a7cDu hast keine Berechtigung die Zeit von anderen S pTimePlayers = Diese Spieler haben ihre eigene Zeit: pTimeReset = Zeit wurde zur\u00fcgesetzt f\u00fcr: \u00a7e{0} pTimeSet = Zeit wurde f\u00fcr \u00a7e{1}\u00a7f zu \u00a73{0}\u00a7f gesetzt. -pTimeSetFixed = Player time is fixed to \u00a73{0}\u00a7f for: \u00a7e{1} +pTimeSetFixed = Spielerzeit ist festgesetzt zu \u00a73{0}\u00a7f f\u00fcr: \u00a7e{1} parseError = Fehler beim Parsen von {0} in Zeile {1} pendingTeleportCancelled = \u00a7cLaufende Teleportierung abgebrochen. permissionsError = Permissions/GroupManager fehlt; Chat-Prefixe/-Suffixe sind ausgeschaltet. @@ -244,13 +244,14 @@ possibleWorlds = \u00a77M\u00f6gliche Welten sind nummeriet von 0 bis {0}. powerToolAir = Befehl kann nicht mit Luft verbunden werden. powerToolAlreadySet = Befehl \u00a7c{0}\u00a7f ist bereits zu {1} hinzugef\u00fcgt. powerToolAttach = Befehl \u00a7c{0}\u00a7f erfolgreich zu {1} hinzugef\u00fcgt. +powerToolClearAll= Alle Powertoolkommandos wurden entfernt. powerToolList = {1} hat die folgenden Befehle: \u00a7c{0}\u00a7f. powerToolListEmpty = {0} hat keinen Befehl. powerToolNoSuchCommandAssigned = Befehl \u00a7c{0}\u00a7f wurde nicht zu {1} hinzugef\u00fcgt. powerToolRemove = Befehl \u00a7c{0}\u00a7f erfolgreich von {1} entfernt. powerToolRemoveAll = Alle Befehle von {0} entfernt. -powerToolsEnabled=All of your power tools have been enabled. -powerToolsDisabled=All of your power tools have been disabled. +powerToolsEnabled = Alle deine Powertools wurden aktiviert. +powerToolsDisabled = Alle deine Powertools wurden deaktiviert. protectionOwner = \u00a76[EssentialsProtect] Besitzer dieses Blocks: {0} questionFormat = \u00a77[Frage]\u00a7f {0} reloadAllPlugins = \u00a77Alle plugins neu geladen. @@ -275,7 +276,7 @@ shoutFormat = \u00a77[Schrei]\u00a7f {0} signFormatFail = \u00a74[{0}] signFormatSuccess = \u00a71[{0}] signFormatTemplate = [{0}] -signProtectInvalidLocation = \u00a74You are not allowed to create sign here. +signProtectInvalidLocation = \u00a74Du bist nicht befugt ein Schild hierhin zu setzen. similarWarpExist = Ein Warp-Punkt mit einem \u00e4hnlichen Namen existiert bereits. slimeMalformedSize = Ung\u00fcltige Gr\u00f6sse. soloMob = Das Monster m\u00f6chte allein sein. @@ -327,7 +328,7 @@ unknownItemInList = Unbekannter Gegenstand {0} in Liste {1}. unknownItemName = Unbekannter Gegenstand: {0} unlimitedItemPermission = \u00a7cDu hast keine Rechte f\u00fcr {0}. unlimitedItems = Unendliche Objekte: -unmutedPlayer = Player {0} ist nicht mehr stumm. +unmutedPlayer = Spieler {0} ist nicht mehr stumm. upgradingFilesError = Fehler beim Aktualisieren der Dateien userCreatedPortal = {0} benutzt ein Portal und hat ein Ausgangsportal erstellt. userDoesNotExist = Spieler {0} existiert nicht. @@ -349,10 +350,10 @@ warpSet = \u00a77Warp-Punkt {0} wurde erstellt. warpUsePermission = \u00a7cDu hast keinen Zugriff f\u00fcr diesen Warp-Punkt. warpingTo = \u00a77Teleportiere zu Warp-Punkt {0}. warpsCount = \u00a77Es gibt {0} Warp-Punkte. Zeige Seite {1} von {2}. -weatherStorm = \u00a77In deiner Welt st\u00fcrmt es nun. -weatherStormFor = \u00a77In deiner Welt st\u00fcrmt es nun f\u00fcr {0} Sekunden. -weatherSun = \u00a77In deiner Welt scheint nun die Sonne. -weatherSunFor = \u00a77In deiner Welt scheint nun f\u00fcr {0} Sekunden die Sonne. +weatherStorm = \u00a77In {0} st\u00fcrmt es nun. +weatherStormFor = \u00a77In {0} st\u00fcrmt es nun f\u00fcr {1} Sekunden. +weatherSun = \u00a77In {0} scheint nun die Sonne. +weatherSunFor = \u00a77In {0} scheint nun f\u00fcr {1} Sekunden die Sonne. whoisGeoLocation = \u00a79 - Herkunft: {0} whoisHealth = \u00a79 - Gesundheit: {0}/20 whoisIPAddress = \u00a79 - IP-Adresse: {0} @@ -368,6 +369,4 @@ year = Jahr years = Jahre youAreHealed = \u00a77Du wurdest geheilt. youHaveNewMail = \u00a7cDu hast {0} Nachrichten!\u00a7f Schreibe \u00a77/mail read\u00a7f um deine Nachrichten anzuzeigen. -powerToolClearAll= All powertool commands have been cleared. - diff --git a/Essentials/src/messages_en.properties b/Essentials/src/messages_en.properties index ea73ccc56..39fe33d83 100644 --- a/Essentials/src/messages_en.properties +++ b/Essentials/src/messages_en.properties @@ -350,10 +350,10 @@ warpSet = \u00a77Warp {0} set. warpUsePermission = \u00a7cYou do not have Permission to use that warp. warpingTo = \u00a77Warping to {0}. warpsCount = \u00a77There are {0} warps. Showing page {1} of {2}. -weatherStorm = \u00a77You set the weather to storm in your world -weatherStormFor = \u00a77You set the weather to storm in your world for {0} seconds -weatherSun = \u00a77You set the weather to sun in your world -weatherSunFor = \u00a77You set the weather to sun in your world for {0} seconds +weatherStorm = \u00a77You set the weather to storm in {0} +weatherStormFor = \u00a77You set the weather to storm in {0} for {1} seconds +weatherSun = \u00a77You set the weather to sun in {0} +weatherSunFor = \u00a77You set the weather to sun in {0} for {1} seconds whoisGeoLocation = \u00a79 - Location: {0} whoisHealth = \u00a79 - Health: {0}/20 whoisIPAddress = \u00a79 - IP Address: {0} diff --git a/Essentials/src/messages_fr.properties b/Essentials/src/messages_fr.properties index 987a41eb0..11fa2e6a4 100644 --- a/Essentials/src/messages_fr.properties +++ b/Essentials/src/messages_fr.properties @@ -350,10 +350,10 @@ warpSet = \u00a77Le warp {0} a \u00e9t\u00e9 cr\u00e9\u00e9. warpUsePermission = \u00a7cVous n''avez pas la permission d''utiliser ce warp. warpingTo = \u00a77T\u00e9l\u00e9portation au warp {0}. warpsCount = \u00a77There are {0} warps. Showing page {1} of {2}. -weatherStorm = \u00a77Vous avez d\u00e9fini l''orage dans votre monde -weatherStormFor = \u00a77Vous avez d\u00e9fini l''orage dans votre monde pour {0} secondes. -weatherSun = \u00a77Vous avez mis le beau temps dans votre monde -weatherSunFor = \u00a77Vous avez mis le beau temps dans votre monde pour {0} secondes. +weatherStorm = \u00a77Vous avez d\u00e9fini l''orage dans {0} +weatherStormFor = \u00a77Vous avez d\u00e9fini l''orage dans {0} pour {1} secondes. +weatherSun = \u00a77Vous avez mis le beau temps dans {0} +weatherSunFor = \u00a77Vous avez mis le beau temps dans {0} pour {1} secondes. whoisGeoLocation = \u00a79 - Emplacement: {0} whoisHealth = \u00a79 - Vie: {0} / 20 whoisIPAddress = \u00a79 - Adresse IP: {0} diff --git a/Essentials/src/messages_nl.properties b/Essentials/src/messages_nl.properties index e11791444..c3ce54543 100644 --- a/Essentials/src/messages_nl.properties +++ b/Essentials/src/messages_nl.properties @@ -350,10 +350,10 @@ warpSet = \u00a77Warp {0} ingesteld. warpUsePermission = \u00a7cOnbevoegd om die warp te gebruiken. warpingTo = \u00a77Aan het warpen naar {0}. warpsCount = \u00a77There are {0} warps. Showing page {1} of {2}. -weatherStorm = \u00a77Je hebt het weer naar storm gezet in de wereld -weatherStormFor = \u00a77Je hebt het weer in de wereld naar storm gezet voor {0} seconde -weatherSun = \u00a77Je hebt het weer naar zon gezet in de wereld -weatherSunFor = \u00a77Je hebt het weer in de wereld naar zon gezet voor {0} seconde +weatherStorm = \u00a77Je hebt het weer naar storm gezet in de {0} +weatherStormFor = \u00a77Je hebt het weer in de {0} naar storm gezet voor {1} seconde +weatherSun = \u00a77Je hebt het weer naar zon gezet in de {0} +weatherSunFor = \u00a77Je hebt het weer in de {0} naar zon gezet voor {1} seconde whoisGeoLocation = \u00a79 - Locatie: {0} whoisHealth = \u00a79 - Levens: {0}/20 whoisIPAddress = \u00a79 - IP Adres: {0} diff --git a/Essentials/test/com/earth2me/essentials/StorageTest.java b/Essentials/test/com/earth2me/essentials/StorageTest.java new file mode 100644 index 000000000..9ee5883e9 --- /dev/null +++ b/Essentials/test/com/earth2me/essentials/StorageTest.java @@ -0,0 +1,37 @@ +package com.earth2me.essentials; + +import junit.framework.TestCase; +import com.earth2me.essentials.settings.Settings; +import com.earth2me.essentials.storage.StorageObject; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import org.junit.Test; + + +public class StorageTest extends TestCase +{ + @Test + public void testSettings() + { + assertTrue(StorageObject.class.isAssignableFrom(Settings.class)); + final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); + final Reader reader = new InputStreamReader(bais); + final Settings settings = StorageObject.load(Settings.class, reader); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final PrintWriter writer = new PrintWriter(baos); + settings.save(writer); + writer.close(); + byte[] written = baos.toByteArray(); + System.out.println(new String(written)); + final ByteArrayInputStream bais2 = new ByteArrayInputStream(written); + final Reader reader2 = new InputStreamReader(bais2); + final Settings settings2 = StorageObject.load(Settings.class, reader2); + System.out.println(settings.toString()); + System.out.println(settings2.toString()); + //assertEquals("Default and rewritten config should be equal", settings, settings2); + //that assertion fails, because empty list and maps return as null + } +} diff --git a/EssentialsUpdate/build.xml b/EssentialsUpdate/build.xml new file mode 100644 index 000000000..dedb62afa --- /dev/null +++ b/EssentialsUpdate/build.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- You may freely edit this file. See commented blocks below for --> +<!-- some examples of how to customize the build. --> +<!-- (If you delete it and reopen the project it will be recreated.) --> +<!-- By default, only the Clean and Build commands use this build script. --> +<!-- Commands such as Run, Debug, and Test only use this build script if --> +<!-- the Compile on Save feature is turned off for the project. --> +<!-- You can turn off the Compile on Save (or Deploy on Save) setting --> +<!-- in the project's Project Properties dialog box.--> +<project name="EssentialsUpdate" default="default" basedir="."> + <description>Builds, tests, and runs the project EssentialsUpdate.</description> + <import file="nbproject/build-impl.xml"/> + <!-- + + There exist several targets which are by default empty and which can be + used for execution of your tasks. These targets are usually executed + before and after some main targets. They are: + + -pre-init: called before initialization of project properties + -post-init: called after initialization of project properties + -pre-compile: called before javac compilation + -post-compile: called after javac compilation + -pre-compile-single: called before javac compilation of single file + -post-compile-single: called after javac compilation of single file + -pre-compile-test: called before javac compilation of JUnit tests + -post-compile-test: called after javac compilation of JUnit tests + -pre-compile-test-single: called before javac compilation of single JUnit test + -post-compile-test-single: called after javac compilation of single JUunit test + -pre-jar: called before JAR building + -post-jar: called after JAR building + -post-clean: called after cleaning build products + + (Targets beginning with '-' are not intended to be called on their own.) + + Example of inserting an obfuscator after compilation could look like this: + + <target name="-post-compile"> + <obfuscate> + <fileset dir="${build.classes.dir}"/> + </obfuscate> + </target> + + For list of available properties check the imported + nbproject/build-impl.xml file. + + + Another way to customize the build is by overriding existing main targets. + The targets of interest are: + + -init-macrodef-javac: defines macro for javac compilation + -init-macrodef-junit: defines macro for junit execution + -init-macrodef-debug: defines macro for class debugging + -init-macrodef-java: defines macro for class execution + -do-jar-with-manifest: JAR building (if you are using a manifest) + -do-jar-without-manifest: JAR building (if you are not using a manifest) + run: execution of project + -javadoc-build: Javadoc generation + test-report: JUnit report generation + + An example of overriding the target for project execution could look like this: + + <target name="run" depends="EssentialsUpdate-impl.jar"> + <exec dir="bin" executable="launcher.exe"> + <arg file="${dist.jar}"/> + </exec> + </target> + + Notice that the overridden target depends on the jar target and not only on + the compile target as the regular run target does. Again, for a list of available + properties which you can use, check the target you are overriding in the + nbproject/build-impl.xml file. + + --> +</project> diff --git a/EssentialsUpdate/manifest.mf b/EssentialsUpdate/manifest.mf new file mode 100644 index 000000000..328e8e5bc --- /dev/null +++ b/EssentialsUpdate/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/EssentialsUpdate/nbproject/build-impl.xml b/EssentialsUpdate/nbproject/build-impl.xml new file mode 100644 index 000000000..7a38ac1d4 --- /dev/null +++ b/EssentialsUpdate/nbproject/build-impl.xml @@ -0,0 +1,1067 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +*** GENERATED FROM project.xml - DO NOT EDIT *** +*** EDIT ../build.xml INSTEAD *** + +For the purpose of easier reading the script +is divided into following sections: + + - initialization + - compilation + - jar + - execution + - debugging + - javadoc + - junit compilation + - junit execution + - junit debugging + - applet + - cleanup + + --> +<project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="EssentialsUpdate-impl"> + <fail message="Please build using Ant 1.7.1 or higher."> + <condition> + <not> + <antversion atleast="1.7.1"/> + </not> + </condition> + </fail> + <target depends="test,jar,javadoc" description="Build and test whole project." name="default"/> + <!-- + ====================== + INITIALIZATION SECTION + ====================== + --> + <target name="-pre-init"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="-pre-init" name="-init-private"> + <property file="nbproject/private/config.properties"/> + <property file="nbproject/private/configs/${config}.properties"/> + <property file="nbproject/private/private.properties"/> + </target> + <target name="-pre-init-libraries"> + <property location="../lib/nblibraries.properties" name="libraries.path"/> + <dirname file="${libraries.path}" property="libraries.dir.nativedirsep"/> + <pathconvert dirsep="/" property="libraries.dir"> + <path path="${libraries.dir.nativedirsep}"/> + </pathconvert> + <basename file="${libraries.path}" property="libraries.basename" suffix=".properties"/> + <available file="${libraries.dir}/${libraries.basename}-private.properties" property="private.properties.available"/> + </target> + <target depends="-pre-init-libraries" if="private.properties.available" name="-init-private-libraries"> + <loadproperties encoding="ISO-8859-1" srcfile="${libraries.dir}/${libraries.basename}-private.properties"> + <filterchain> + <replacestring from="$${base}" to="${libraries.dir}"/> + <escapeunicode/> + </filterchain> + </loadproperties> + </target> + <target depends="-pre-init,-init-private,-init-private-libraries" name="-init-libraries"> + <loadproperties encoding="ISO-8859-1" srcfile="${libraries.path}"> + <filterchain> + <replacestring from="$${base}" to="${libraries.dir}"/> + <escapeunicode/> + </filterchain> + </loadproperties> + </target> + <target depends="-pre-init,-init-private,-init-libraries" name="-init-user"> + <property file="${user.properties.file}"/> + <!-- The two properties below are usually overridden --> + <!-- by the active platform. Just a fallback. --> + <property name="default.javac.source" value="1.4"/> + <property name="default.javac.target" value="1.4"/> + </target> + <target depends="-pre-init,-init-private,-init-libraries,-init-user" name="-init-project"> + <property file="nbproject/configs/${config}.properties"/> + <property file="nbproject/project.properties"/> + </target> + <target depends="-pre-init,-init-private,-init-libraries,-init-user,-init-project,-init-macrodef-property" name="-do-init"> + <available file="${manifest.file}" property="manifest.available"/> + <condition property="splashscreen.available"> + <and> + <not> + <equals arg1="${application.splash}" arg2="" trim="true"/> + </not> + <available file="${application.splash}"/> + </and> + </condition> + <condition property="main.class.available"> + <and> + <isset property="main.class"/> + <not> + <equals arg1="${main.class}" arg2="" trim="true"/> + </not> + </and> + </condition> + <condition property="manifest.available+main.class"> + <and> + <isset property="manifest.available"/> + <isset property="main.class.available"/> + </and> + </condition> + <condition property="do.archive"> + <not> + <istrue value="${jar.archive.disabled}"/> + </not> + </condition> + <condition property="do.mkdist"> + <and> + <isset property="do.archive"/> + <isset property="libs.CopyLibs.classpath"/> + <not> + <istrue value="${mkdist.disabled}"/> + </not> + </and> + </condition> + <condition property="manifest.available+main.class+mkdist.available"> + <and> + <istrue value="${manifest.available+main.class}"/> + <isset property="do.mkdist"/> + </and> + </condition> + <condition property="do.archive+manifest.available"> + <and> + <isset property="manifest.available"/> + <istrue value="${do.archive}"/> + </and> + </condition> + <condition property="do.archive+main.class.available"> + <and> + <isset property="main.class.available"/> + <istrue value="${do.archive}"/> + </and> + </condition> + <condition property="do.archive+splashscreen.available"> + <and> + <isset property="splashscreen.available"/> + <istrue value="${do.archive}"/> + </and> + </condition> + <condition property="do.archive+manifest.available+main.class"> + <and> + <istrue value="${manifest.available+main.class}"/> + <istrue value="${do.archive}"/> + </and> + </condition> + <condition property="manifest.available-mkdist.available"> + <or> + <istrue value="${manifest.available}"/> + <isset property="do.mkdist"/> + </or> + </condition> + <condition property="manifest.available+main.class-mkdist.available"> + <or> + <istrue value="${manifest.available+main.class}"/> + <isset property="do.mkdist"/> + </or> + </condition> + <condition property="have.tests"> + <or> + <available file="${test.src.dir}"/> + </or> + </condition> + <condition property="have.sources"> + <or> + <available file="${src.dir}"/> + </or> + </condition> + <condition property="netbeans.home+have.tests"> + <and> + <isset property="netbeans.home"/> + <isset property="have.tests"/> + </and> + </condition> + <condition property="no.javadoc.preview"> + <and> + <isset property="javadoc.preview"/> + <isfalse value="${javadoc.preview}"/> + </and> + </condition> + <property name="run.jvmargs" value=""/> + <property name="javac.compilerargs" value=""/> + <property name="work.dir" value="${basedir}"/> + <condition property="no.deps"> + <and> + <istrue value="${no.dependencies}"/> + </and> + </condition> + <property name="javac.debug" value="true"/> + <property name="javadoc.preview" value="true"/> + <property name="application.args" value=""/> + <property name="source.encoding" value="${file.encoding}"/> + <property name="runtime.encoding" value="${source.encoding}"/> + <condition property="javadoc.encoding.used" value="${javadoc.encoding}"> + <and> + <isset property="javadoc.encoding"/> + <not> + <equals arg1="${javadoc.encoding}" arg2=""/> + </not> + </and> + </condition> + <property name="javadoc.encoding.used" value="${source.encoding}"/> + <property name="includes" value="**"/> + <property name="excludes" value=""/> + <property name="do.depend" value="false"/> + <condition property="do.depend.true"> + <istrue value="${do.depend}"/> + </condition> + <path id="endorsed.classpath.path" path="${endorsed.classpath}"/> + <condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'"> + <length length="0" string="${endorsed.classpath}" when="greater"/> + </condition> + <condition else="false" property="jdkBug6558476"> + <and> + <matches pattern="1\.[56]" string="${java.specification.version}"/> + <not> + <os family="unix"/> + </not> + </and> + </condition> + <property name="javac.fork" value="${jdkBug6558476}"/> + <property name="jar.index" value="false"/> + <property name="jar.index.metainf" value="${jar.index}"/> + <available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/> + </target> + <target name="-post-init"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="-pre-init,-init-private,-init-libraries,-init-user,-init-project,-do-init" name="-init-check"> + <fail unless="src.dir">Must set src.dir</fail> + <fail unless="test.src.dir">Must set test.src.dir</fail> + <fail unless="build.dir">Must set build.dir</fail> + <fail unless="dist.dir">Must set dist.dir</fail> + <fail unless="build.classes.dir">Must set build.classes.dir</fail> + <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail> + <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail> + <fail unless="build.test.results.dir">Must set build.test.results.dir</fail> + <fail unless="build.classes.excludes">Must set build.classes.excludes</fail> + <fail unless="dist.jar">Must set dist.jar</fail> + </target> + <target name="-init-macrodef-property"> + <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1"> + <attribute name="name"/> + <attribute name="value"/> + <sequential> + <property name="@{name}" value="${@{value}}"/> + </sequential> + </macrodef> + </target> + <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-macrodef-javac-with-processors"> + <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3"> + <attribute default="${src.dir}" name="srcdir"/> + <attribute default="${build.classes.dir}" name="destdir"/> + <attribute default="${javac.classpath}" name="classpath"/> + <attribute default="${javac.processorpath}" name="processorpath"/> + <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/> + <attribute default="${includes}" name="includes"/> + <attribute default="${excludes}" name="excludes"/> + <attribute default="${javac.debug}" name="debug"/> + <attribute default="${empty.dir}" name="sourcepath"/> + <attribute default="${empty.dir}" name="gensrcdir"/> + <element name="customize" optional="true"/> + <sequential> + <property location="${build.dir}/empty" name="empty.dir"/> + <mkdir dir="${empty.dir}"/> + <mkdir dir="@{apgeneratedsrcdir}"/> + <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}"> + <src> + <dirset dir="@{gensrcdir}" erroronmissingdir="false"> + <include name="*"/> + </dirset> + </src> + <classpath> + <path path="@{classpath}"/> + </classpath> + <compilerarg line="${endorsed.classpath.cmd.line.arg}"/> + <compilerarg line="${javac.compilerargs}"/> + <compilerarg value="-processorpath"/> + <compilerarg path="@{processorpath}:${empty.dir}"/> + <compilerarg line="${ap.processors.internal}"/> + <compilerarg line="${annotation.processing.processor.options}"/> + <compilerarg value="-s"/> + <compilerarg path="@{apgeneratedsrcdir}"/> + <compilerarg line="${ap.proc.none.internal}"/> + <customize/> + </javac> + </sequential> + </macrodef> + </target> + <target depends="-init-ap-cmdline-properties" name="-init-macrodef-javac-without-processors" unless="ap.supported.internal"> + <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3"> + <attribute default="${src.dir}" name="srcdir"/> + <attribute default="${build.classes.dir}" name="destdir"/> + <attribute default="${javac.classpath}" name="classpath"/> + <attribute default="${javac.processorpath}" name="processorpath"/> + <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/> + <attribute default="${includes}" name="includes"/> + <attribute default="${excludes}" name="excludes"/> + <attribute default="${javac.debug}" name="debug"/> + <attribute default="${empty.dir}" name="sourcepath"/> + <attribute default="${empty.dir}" name="gensrcdir"/> + <element name="customize" optional="true"/> + <sequential> + <property location="${build.dir}/empty" name="empty.dir"/> + <mkdir dir="${empty.dir}"/> + <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}"> + <src> + <dirset dir="@{gensrcdir}" erroronmissingdir="false"> + <include name="*"/> + </dirset> + </src> + <classpath> + <path path="@{classpath}"/> + </classpath> + <compilerarg line="${endorsed.classpath.cmd.line.arg}"/> + <compilerarg line="${javac.compilerargs}"/> + <customize/> + </javac> + </sequential> + </macrodef> + </target> + <target depends="-init-macrodef-javac-with-processors,-init-macrodef-javac-without-processors" name="-init-macrodef-javac"> + <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3"> + <attribute default="${src.dir}" name="srcdir"/> + <attribute default="${build.classes.dir}" name="destdir"/> + <attribute default="${javac.classpath}" name="classpath"/> + <sequential> + <depend cache="${build.dir}/depcache" destdir="@{destdir}" excludes="${excludes}" includes="${includes}" srcdir="@{srcdir}"> + <classpath> + <path path="@{classpath}"/> + </classpath> + </depend> + </sequential> + </macrodef> + <macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3"> + <attribute default="${build.classes.dir}" name="destdir"/> + <sequential> + <fail unless="javac.includes">Must set javac.includes</fail> + <pathconvert pathsep="${line.separator}" property="javac.includes.binary"> + <path> + <filelist dir="@{destdir}" files="${javac.includes}"/> + </path> + <globmapper from="*.java" to="*.class"/> + </pathconvert> + <tempfile deleteonexit="true" property="javac.includesfile.binary"/> + <echo file="${javac.includesfile.binary}" message="${javac.includes.binary}"/> + <delete> + <files includesfile="${javac.includesfile.binary}"/> + </delete> + <delete> + <fileset file="${javac.includesfile.binary}"/> + </delete> + </sequential> + </macrodef> + </target> + <target name="-init-macrodef-junit"> + <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3"> + <attribute default="${includes}" name="includes"/> + <attribute default="${excludes}" name="excludes"/> + <attribute default="**" name="testincludes"/> + <sequential> + <property name="junit.forkmode" value="perTest"/> + <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}"> + <batchtest todir="${build.test.results.dir}"> + <fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}"> + <filename name="@{testincludes}"/> + </fileset> + </batchtest> + <classpath> + <path path="${run.test.classpath}"/> + </classpath> + <syspropertyset> + <propertyref prefix="test-sys-prop."/> + <mapper from="test-sys-prop.*" to="*" type="glob"/> + </syspropertyset> + <formatter type="brief" usefile="false"/> + <formatter type="xml"/> + <jvmarg line="${endorsed.classpath.cmd.line.arg}"/> + <jvmarg value="-ea"/> + <jvmarg line="${run.jvmargs}"/> + </junit> + </sequential> + </macrodef> + </target> + <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile, -profile-init-check" name="profile-init"/> + <target name="-profile-pre-init"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target name="-profile-post-init"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target name="-profile-init-macrodef-profile"> + <macrodef name="resolve"> + <attribute name="name"/> + <attribute name="value"/> + <sequential> + <property name="@{name}" value="${env.@{value}}"/> + </sequential> + </macrodef> + <macrodef name="profile"> + <attribute default="${main.class}" name="classname"/> + <element name="customize" optional="true"/> + <sequential> + <property environment="env"/> + <resolve name="profiler.current.path" value="${profiler.info.pathvar}"/> + <java classname="@{classname}" dir="${profiler.info.dir}" fork="true" jvm="${profiler.info.jvm}"> + <jvmarg value="${profiler.info.jvmargs.agent}"/> + <jvmarg line="${profiler.info.jvmargs}"/> + <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/> + <arg line="${application.args}"/> + <classpath> + <path path="${run.classpath}"/> + </classpath> + <syspropertyset> + <propertyref prefix="run-sys-prop."/> + <mapper from="run-sys-prop.*" to="*" type="glob"/> + </syspropertyset> + <customize/> + </java> + </sequential> + </macrodef> + </target> + <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile" name="-profile-init-check"> + <fail unless="profiler.info.jvm">Must set JVM to use for profiling in profiler.info.jvm</fail> + <fail unless="profiler.info.jvmargs.agent">Must set profiler agent JVM arguments in profiler.info.jvmargs.agent</fail> + </target> + <target depends="-init-debug-args" name="-init-macrodef-nbjpda"> + <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1"> + <attribute default="${main.class}" name="name"/> + <attribute default="${debug.classpath}" name="classpath"/> + <attribute default="" name="stopclassname"/> + <sequential> + <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}"> + <classpath> + <path path="@{classpath}"/> + </classpath> + </nbjpdastart> + </sequential> + </macrodef> + <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1"> + <attribute default="${build.classes.dir}" name="dir"/> + <sequential> + <nbjpdareload> + <fileset dir="@{dir}" includes="${fix.classes}"> + <include name="${fix.includes}*.class"/> + </fileset> + </nbjpdareload> + </sequential> + </macrodef> + </target> + <target name="-init-debug-args"> + <property name="version-output" value="java version "${ant.java.version}"/> + <condition property="have-jdk-older-than-1.4"> + <or> + <contains string="${version-output}" substring="java version "1.0"/> + <contains string="${version-output}" substring="java version "1.1"/> + <contains string="${version-output}" substring="java version "1.2"/> + <contains string="${version-output}" substring="java version "1.3"/> + </or> + </condition> + <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none"> + <istrue value="${have-jdk-older-than-1.4}"/> + </condition> + <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem"> + <os family="windows"/> + </condition> + <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}"> + <isset property="debug.transport"/> + </condition> + </target> + <target depends="-init-debug-args" name="-init-macrodef-debug"> + <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3"> + <attribute default="${main.class}" name="classname"/> + <attribute default="${debug.classpath}" name="classpath"/> + <element name="customize" optional="true"/> + <sequential> + <java classname="@{classname}" dir="${work.dir}" fork="true"> + <jvmarg line="${endorsed.classpath.cmd.line.arg}"/> + <jvmarg line="${debug-args-line}"/> + <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/> + <jvmarg value="-Dfile.encoding=${runtime.encoding}"/> + <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/> + <jvmarg line="${run.jvmargs}"/> + <classpath> + <path path="@{classpath}"/> + </classpath> + <syspropertyset> + <propertyref prefix="run-sys-prop."/> + <mapper from="run-sys-prop.*" to="*" type="glob"/> + </syspropertyset> + <customize/> + </java> + </sequential> + </macrodef> + </target> + <target name="-init-macrodef-java"> + <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1"> + <attribute default="${main.class}" name="classname"/> + <attribute default="${run.classpath}" name="classpath"/> + <element name="customize" optional="true"/> + <sequential> + <java classname="@{classname}" dir="${work.dir}" fork="true"> + <jvmarg line="${endorsed.classpath.cmd.line.arg}"/> + <jvmarg value="-Dfile.encoding=${runtime.encoding}"/> + <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/> + <jvmarg line="${run.jvmargs}"/> + <classpath> + <path path="@{classpath}"/> + </classpath> + <syspropertyset> + <propertyref prefix="run-sys-prop."/> + <mapper from="run-sys-prop.*" to="*" type="glob"/> + </syspropertyset> + <customize/> + </java> + </sequential> + </macrodef> + </target> + <target name="-init-macrodef-copylibs"> + <macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3"> + <attribute default="${manifest.file}" name="manifest"/> + <element name="customize" optional="true"/> + <sequential> + <property location="${build.classes.dir}" name="build.classes.dir.resolved"/> + <pathconvert property="run.classpath.without.build.classes.dir"> + <path path="${run.classpath}"/> + <map from="${build.classes.dir.resolved}" to=""/> + </pathconvert> + <pathconvert pathsep=" " property="jar.classpath"> + <path path="${run.classpath.without.build.classes.dir}"/> + <chainedmapper> + <flattenmapper/> + <globmapper from="*" to="lib/*"/> + </chainedmapper> + </pathconvert> + <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/> + <copylibs compress="${jar.compress}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" runtimeclasspath="${run.classpath.without.build.classes.dir}"> + <fileset dir="${build.classes.dir}"/> + <manifest> + <attribute name="Class-Path" value="${jar.classpath}"/> + <customize/> + </manifest> + </copylibs> + </sequential> + </macrodef> + </target> + <target name="-init-presetdef-jar"> + <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1"> + <jar compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}"> + <j2seproject1:fileset dir="${build.classes.dir}"/> + </jar> + </presetdef> + </target> + <target name="-init-ap-cmdline-properties"> + <property name="annotation.processing.enabled" value="true"/> + <property name="annotation.processing.processors.list" value=""/> + <property name="annotation.processing.processor.options" value=""/> + <property name="annotation.processing.run.all.processors" value="true"/> + <property name="javac.processorpath" value="${javac.classpath}"/> + <property name="javac.test.processorpath" value="${javac.test.classpath}"/> + <condition property="ap.supported.internal" value="true"> + <not> + <matches pattern="1\.[0-5](\..*)?" string="${javac.source}"/> + </not> + </condition> + </target> + <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-ap-cmdline-supported"> + <condition else="" property="ap.processors.internal" value="-processor ${annotation.processing.processors.list}"> + <isfalse value="${annotation.processing.run.all.processors}"/> + </condition> + <condition else="" property="ap.proc.none.internal" value="-proc:none"> + <isfalse value="${annotation.processing.enabled}"/> + </condition> + </target> + <target depends="-init-ap-cmdline-properties,-init-ap-cmdline-supported" name="-init-ap-cmdline"> + <property name="ap.cmd.line.internal" value=""/> + </target> + <target depends="-pre-init,-init-private,-init-libraries,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-junit,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar,-init-ap-cmdline" name="init"/> + <!-- + =================== + COMPILATION SECTION + =================== + --> + <target name="-deps-jar-init" unless="built-jar.properties"> + <property location="${build.dir}/built-jar.properties" name="built-jar.properties"/> + <delete file="${built-jar.properties}" quiet="true"/> + </target> + <target if="already.built.jar.${basedir}" name="-warn-already-built-jar"> + <echo level="warn" message="Cycle detected: EssentialsUpdate was already built"/> + </target> + <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps"> + <mkdir dir="${build.dir}"/> + <touch file="${built-jar.properties}" verbose="false"/> + <property file="${built-jar.properties}" prefix="already.built.jar."/> + <antcall target="-warn-already-built-jar"/> + <propertyfile file="${built-jar.properties}"> + <entry key="${basedir}" value=""/> + </propertyfile> + </target> + <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/> + <target depends="init" name="-check-automatic-build"> + <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/> + </target> + <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build"> + <antcall target="clean"/> + </target> + <target depends="init,deps-jar" name="-pre-pre-compile"> + <mkdir dir="${build.classes.dir}"/> + </target> + <target name="-pre-compile"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target if="do.depend.true" name="-compile-depend"> + <pathconvert property="build.generated.subdirs"> + <dirset dir="${build.generated.sources.dir}" erroronmissingdir="false"> + <include name="*"/> + </dirset> + </pathconvert> + <j2seproject3:depend srcdir="${src.dir}:${build.generated.subdirs}"/> + </target> + <target depends="init,deps-jar,-pre-pre-compile,-pre-compile, -copy-persistence-xml,-compile-depend" if="have.sources" name="-do-compile"> + <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/> + <copy todir="${build.classes.dir}"> + <fileset dir="${src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/> + </copy> + </target> + <target if="has.persistence.xml" name="-copy-persistence-xml"> + <mkdir dir="${build.classes.dir}/META-INF"/> + <copy todir="${build.classes.dir}/META-INF"> + <fileset dir="${meta.inf.dir}" includes="persistence.xml"/> + </copy> + </target> + <target name="-post-compile"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/> + <target name="-pre-compile-single"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single"> + <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail> + <j2seproject3:force-recompile/> + <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.dir}"/> + </target> + <target name="-post-compile-single"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/> + <!-- + ==================== + JAR BUILDING SECTION + ==================== + --> + <target depends="init" name="-pre-pre-jar"> + <dirname file="${dist.jar}" property="dist.jar.dir"/> + <mkdir dir="${dist.jar.dir}"/> + </target> + <target name="-pre-jar"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive" name="-do-jar-without-manifest" unless="manifest.available-mkdist.available"> + <j2seproject1:jar/> + </target> + <target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive+manifest.available" name="-do-jar-with-manifest" unless="manifest.available+main.class-mkdist.available"> + <j2seproject1:jar manifest="${manifest.file}"/> + </target> + <target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive+manifest.available+main.class" name="-do-jar-with-mainclass" unless="manifest.available+main.class+mkdist.available"> + <j2seproject1:jar manifest="${manifest.file}"> + <j2seproject1:manifest> + <j2seproject1:attribute name="Main-Class" value="${main.class}"/> + </j2seproject1:manifest> + </j2seproject1:jar> + <echo level="info">To run this application from the command line without Ant, try:</echo> + <property location="${build.classes.dir}" name="build.classes.dir.resolved"/> + <property location="${dist.jar}" name="dist.jar.resolved"/> + <pathconvert property="run.classpath.with.dist.jar"> + <path path="${run.classpath}"/> + <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/> + </pathconvert> + <echo level="info">java -cp "${run.classpath.with.dist.jar}" ${main.class}</echo> + </target> + <target depends="init" if="do.archive" name="-do-jar-with-libraries-create-manifest" unless="manifest.available"> + <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/> + <touch file="${tmp.manifest.file}" verbose="false"/> + </target> + <target depends="init" if="do.archive+manifest.available" name="-do-jar-with-libraries-copy-manifest"> + <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/> + <copy file="${manifest.file}" tofile="${tmp.manifest.file}"/> + </target> + <target depends="init,-do-jar-with-libraries-create-manifest,-do-jar-with-libraries-copy-manifest" if="do.archive+main.class.available" name="-do-jar-with-libraries-set-main"> + <manifest file="${tmp.manifest.file}" mode="update"> + <attribute name="Main-Class" value="${main.class}"/> + </manifest> + </target> + <target depends="init,-do-jar-with-libraries-create-manifest,-do-jar-with-libraries-copy-manifest" if="do.archive+splashscreen.available" name="-do-jar-with-libraries-set-splashscreen"> + <basename file="${application.splash}" property="splashscreen.basename"/> + <mkdir dir="${build.classes.dir}/META-INF"/> + <copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/> + <manifest file="${tmp.manifest.file}" mode="update"> + <attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/> + </manifest> + </target> + <target depends="init,-init-macrodef-copylibs,compile,-pre-pre-jar,-pre-jar,-do-jar-with-libraries-create-manifest,-do-jar-with-libraries-copy-manifest,-do-jar-with-libraries-set-main,-do-jar-with-libraries-set-splashscreen" if="do.mkdist" name="-do-jar-with-libraries-pack"> + <j2seproject3:copylibs manifest="${tmp.manifest.file}"/> + <echo level="info">To run this application from the command line without Ant, try:</echo> + <property location="${dist.jar}" name="dist.jar.resolved"/> + <echo level="info">java -jar "${dist.jar.resolved}"</echo> + </target> + <target depends="-do-jar-with-libraries-pack" if="do.archive" name="-do-jar-with-libraries-delete-manifest"> + <delete> + <fileset file="${tmp.manifest.file}"/> + </delete> + </target> + <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-with-libraries-create-manifest,-do-jar-with-libraries-copy-manifest,-do-jar-with-libraries-set-main,-do-jar-with-libraries-set-splashscreen,-do-jar-with-libraries-pack,-do-jar-with-libraries-delete-manifest" name="-do-jar-with-libraries"/> + <target name="-post-jar"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,compile,-pre-jar,-do-jar-with-manifest,-do-jar-without-manifest,-do-jar-with-mainclass,-do-jar-with-libraries,-post-jar" description="Build JAR." name="jar"/> + <!-- + ================= + EXECUTION SECTION + ================= + --> + <target depends="init,compile" description="Run a main class." name="run"> + <j2seproject1:java> + <customize> + <arg line="${application.args}"/> + </customize> + </j2seproject1:java> + </target> + <target name="-do-not-recompile"> + <property name="javac.includes.binary" value=""/> + </target> + <target depends="init,compile-single" name="run-single"> + <fail unless="run.class">Must select one file in the IDE or set run.class</fail> + <j2seproject1:java classname="${run.class}"/> + </target> + <target depends="init,compile-test-single" name="run-test-with-main"> + <fail unless="run.class">Must select one file in the IDE or set run.class</fail> + <j2seproject1:java classname="${run.class}" classpath="${run.test.classpath}"/> + </target> + <!-- + ================= + DEBUGGING SECTION + ================= + --> + <target depends="init" if="netbeans.home" name="-debug-start-debugger"> + <j2seproject1:nbjpdastart name="${debug.class}"/> + </target> + <target depends="init" if="netbeans.home" name="-debug-start-debugger-main-test"> + <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${debug.class}"/> + </target> + <target depends="init,compile" name="-debug-start-debuggee"> + <j2seproject3:debug> + <customize> + <arg line="${application.args}"/> + </customize> + </j2seproject3:debug> + </target> + <target depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE." if="netbeans.home" name="debug"/> + <target depends="init" if="netbeans.home" name="-debug-start-debugger-stepinto"> + <j2seproject1:nbjpdastart stopclassname="${main.class}"/> + </target> + <target depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee" if="netbeans.home" name="debug-stepinto"/> + <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-single"> + <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail> + <j2seproject3:debug classname="${debug.class}"/> + </target> + <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single" if="netbeans.home" name="debug-single"/> + <target depends="init,compile-test-single" if="netbeans.home" name="-debug-start-debuggee-main-test"> + <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail> + <j2seproject3:debug classname="${debug.class}" classpath="${debug.test.classpath}"/> + </target> + <target depends="init,compile-test-single,-debug-start-debugger-main-test,-debug-start-debuggee-main-test" if="netbeans.home" name="debug-test-with-main"/> + <target depends="init" name="-pre-debug-fix"> + <fail unless="fix.includes">Must set fix.includes</fail> + <property name="javac.includes" value="${fix.includes}.java"/> + </target> + <target depends="init,-pre-debug-fix,compile-single" if="netbeans.home" name="-do-debug-fix"> + <j2seproject1:nbjpdareload/> + </target> + <target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/> + <!-- + ================= + PROFILING SECTION + ================= + --> + <target depends="profile-init,compile" description="Profile a project in the IDE." if="netbeans.home" name="profile"> + <nbprofiledirect> + <classpath> + <path path="${run.classpath}"/> + </classpath> + </nbprofiledirect> + <profile/> + </target> + <target depends="profile-init,compile-single" description="Profile a selected class in the IDE." if="netbeans.home" name="profile-single"> + <fail unless="profile.class">Must select one file in the IDE or set profile.class</fail> + <nbprofiledirect> + <classpath> + <path path="${run.classpath}"/> + </classpath> + </nbprofiledirect> + <profile classname="${profile.class}"/> + </target> + <!-- + ========================= + APPLET PROFILING SECTION + ========================= + --> + <target depends="profile-init,compile-single" if="netbeans.home" name="profile-applet"> + <nbprofiledirect> + <classpath> + <path path="${run.classpath}"/> + </classpath> + </nbprofiledirect> + <profile classname="sun.applet.AppletViewer"> + <customize> + <arg value="${applet.url}"/> + </customize> + </profile> + </target> + <!-- + ========================= + TESTS PROFILING SECTION + ========================= + --> + <target depends="profile-init,compile-test-single" if="netbeans.home" name="profile-test-single"> + <nbprofiledirect> + <classpath> + <path path="${run.test.classpath}"/> + </classpath> + </nbprofiledirect> + <junit dir="${profiler.info.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" jvm="${profiler.info.jvm}" showoutput="true"> + <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/> + <jvmarg value="${profiler.info.jvmargs.agent}"/> + <jvmarg line="${profiler.info.jvmargs}"/> + <test name="${profile.class}"/> + <classpath> + <path path="${run.test.classpath}"/> + </classpath> + <syspropertyset> + <propertyref prefix="test-sys-prop."/> + <mapper from="test-sys-prop.*" to="*" type="glob"/> + </syspropertyset> + <formatter type="brief" usefile="false"/> + <formatter type="xml"/> + </junit> + </target> + <!-- + =============== + JAVADOC SECTION + =============== + --> + <target depends="init" if="have.sources" name="-javadoc-build"> + <mkdir dir="${dist.javadoc.dir}"/> + <javadoc additionalparam="${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}"> + <classpath> + <path path="${javac.classpath}"/> + </classpath> + <fileset dir="${src.dir}" excludes="*.java,${excludes}" includes="${includes}"> + <filename name="**/*.java"/> + </fileset> + <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false"> + <include name="**/*.java"/> + <exclude name="*.java"/> + </fileset> + </javadoc> + <copy todir="${dist.javadoc.dir}"> + <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}"> + <filename name="**/doc-files/**"/> + </fileset> + <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false"> + <include name="**/doc-files/**"/> + </fileset> + </copy> + </target> + <target depends="init,-javadoc-build" if="netbeans.home" name="-javadoc-browse" unless="no.javadoc.preview"> + <nbbrowse file="${dist.javadoc.dir}/index.html"/> + </target> + <target depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc." name="javadoc"/> + <!-- + ========================= + JUNIT COMPILATION SECTION + ========================= + --> + <target depends="init,compile" if="have.tests" name="-pre-pre-compile-test"> + <mkdir dir="${build.test.classes.dir}"/> + </target> + <target name="-pre-compile-test"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target if="do.depend.true" name="-compile-test-depend"> + <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.src.dir}"/> + </target> + <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test"> + <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir="${test.src.dir}"/> + <copy todir="${build.test.classes.dir}"> + <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/> + </copy> + </target> + <target name="-post-compile-test"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test" name="compile-test"/> + <target name="-pre-compile-test-single"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single"> + <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail> + <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/> + <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="${test.src.dir}" srcdir="${test.src.dir}"/> + <copy todir="${build.test.classes.dir}"> + <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/> + </copy> + </target> + <target name="-post-compile-test-single"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single" name="compile-test-single"/> + <!-- + ======================= + JUNIT EXECUTION SECTION + ======================= + --> + <target depends="init" if="have.tests" name="-pre-test-run"> + <mkdir dir="${build.test.results.dir}"/> + </target> + <target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run"> + <j2seproject3:junit testincludes="**/*Test.java"/> + </target> + <target depends="init,compile-test,-pre-test-run,-do-test-run" if="have.tests" name="-post-test-run"> + <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail> + </target> + <target depends="init" if="have.tests" name="test-report"/> + <target depends="init" if="netbeans.home+have.tests" name="-test-browse"/> + <target depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests." name="test"/> + <target depends="init" if="have.tests" name="-pre-test-run-single"> + <mkdir dir="${build.test.results.dir}"/> + </target> + <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single"> + <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail> + <j2seproject3:junit excludes="" includes="${test.includes}"/> + </target> + <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single" if="have.tests" name="-post-test-run-single"> + <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail> + </target> + <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test." name="test-single"/> + <!-- + ======================= + JUNIT DEBUGGING SECTION + ======================= + --> + <target depends="init,compile-test" if="have.tests" name="-debug-start-debuggee-test"> + <fail unless="test.class">Must select one file in the IDE or set test.class</fail> + <property location="${build.test.results.dir}/TEST-${test.class}.xml" name="test.report.file"/> + <delete file="${test.report.file}"/> + <mkdir dir="${build.test.results.dir}"/> + <j2seproject3:debug classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" classpath="${ant.home}/lib/ant.jar:${ant.home}/lib/ant-junit.jar:${debug.test.classpath}"> + <customize> + <syspropertyset> + <propertyref prefix="test-sys-prop."/> + <mapper from="test-sys-prop.*" to="*" type="glob"/> + </syspropertyset> + <arg value="${test.class}"/> + <arg value="showoutput=true"/> + <arg value="formatter=org.apache.tools.ant.taskdefs.optional.junit.BriefJUnitResultFormatter"/> + <arg value="formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,${test.report.file}"/> + </customize> + </j2seproject3:debug> + </target> + <target depends="init,compile-test" if="netbeans.home+have.tests" name="-debug-start-debugger-test"> + <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${test.class}"/> + </target> + <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test" name="debug-test"/> + <target depends="init,-pre-debug-fix,compile-test-single" if="netbeans.home" name="-do-debug-fix-test"> + <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/> + </target> + <target depends="init,-pre-debug-fix,-do-debug-fix-test" if="netbeans.home" name="debug-fix-test"/> + <!-- + ========================= + APPLET EXECUTION SECTION + ========================= + --> + <target depends="init,compile-single" name="run-applet"> + <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail> + <j2seproject1:java classname="sun.applet.AppletViewer"> + <customize> + <arg value="${applet.url}"/> + </customize> + </j2seproject1:java> + </target> + <!-- + ========================= + APPLET DEBUGGING SECTION + ========================= + --> + <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-applet"> + <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail> + <j2seproject3:debug classname="sun.applet.AppletViewer"> + <customize> + <arg value="${applet.url}"/> + </customize> + </j2seproject3:debug> + </target> + <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet" if="netbeans.home" name="debug-applet"/> + <!-- + =============== + CLEANUP SECTION + =============== + --> + <target name="-deps-clean-init" unless="built-clean.properties"> + <property location="${build.dir}/built-clean.properties" name="built-clean.properties"/> + <delete file="${built-clean.properties}" quiet="true"/> + </target> + <target if="already.built.clean.${basedir}" name="-warn-already-built-clean"> + <echo level="warn" message="Cycle detected: EssentialsUpdate was already built"/> + </target> + <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps"> + <mkdir dir="${build.dir}"/> + <touch file="${built-clean.properties}" verbose="false"/> + <property file="${built-clean.properties}" prefix="already.built.clean."/> + <antcall target="-warn-already-built-clean"/> + <propertyfile file="${built-clean.properties}"> + <entry key="${basedir}" value=""/> + </propertyfile> + </target> + <target depends="init" name="-do-clean"> + <delete dir="${build.dir}"/> + <delete dir="${dist.dir}" followsymlinks="false" includeemptydirs="true"/> + </target> + <target name="-post-clean"> + <!-- Empty placeholder for easier customization. --> + <!-- You can override this target in the ../build.xml file. --> + </target> + <target depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products." name="clean"/> + <target name="-check-call-dep"> + <property file="${call.built.properties}" prefix="already.built."/> + <condition property="should.call.dep"> + <not> + <isset property="already.built.${call.subproject}"/> + </not> + </condition> + </target> + <target depends="-check-call-dep" if="should.call.dep" name="-maybe-call-dep"> + <ant antfile="${call.script}" inheritall="false" target="${call.target}"> + <propertyset> + <propertyref prefix="transfer."/> + <mapper from="transfer.*" to="*" type="glob"/> + </propertyset> + </ant> + </target> +</project> diff --git a/EssentialsUpdate/nbproject/genfiles.properties b/EssentialsUpdate/nbproject/genfiles.properties new file mode 100644 index 000000000..992379672 --- /dev/null +++ b/EssentialsUpdate/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=fd4b98a9 +build.xml.script.CRC32=334f342d +build.xml.stylesheet.CRC32=28e38971@1.44.1.45 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=fd4b98a9 +nbproject/build-impl.xml.script.CRC32=1cab9494 +nbproject/build-impl.xml.stylesheet.CRC32=0ae3a408@1.44.1.45 diff --git a/EssentialsUpdate/nbproject/project.properties b/EssentialsUpdate/nbproject/project.properties new file mode 100644 index 000000000..fadaa4839 --- /dev/null +++ b/EssentialsUpdate/nbproject/project.properties @@ -0,0 +1,75 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=EssentialsUpdate +application.vendor=essentialsteam +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/EssentialsUpdate.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +file.reference.bukkit-0.0.1-SNAPSHOT.jar=../lib/bukkit-0.0.1-SNAPSHOT.jar +includes=** +jar.compress=true +javac.classpath=\ + ${file.reference.bukkit-0.0.1-SNAPSHOT.jar}:\ + ${libs.junit_4.classpath} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.6 +javac.target=1.6 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class= +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project +# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value +# or test-sys-prop.name=value to set system properties for unit tests): +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/EssentialsUpdate/nbproject/project.xml b/EssentialsUpdate/nbproject/project.xml new file mode 100644 index 000000000..6b0efd00f --- /dev/null +++ b/EssentialsUpdate/nbproject/project.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://www.netbeans.org/ns/project/1"> + <type>org.netbeans.modules.java.j2seproject</type> + <configuration> + <data xmlns="http://www.netbeans.org/ns/j2se-project/3"> + <name>EssentialsUpdate</name> + <source-roots> + <root id="src.dir"/> + </source-roots> + <test-roots> + <root id="test.src.dir"/> + </test-roots> + </data> + <libraries xmlns="http://www.netbeans.org/ns/ant-project-libraries/1"> + <definitions>../lib/nblibraries.properties</definitions> + </libraries> + </configuration> +</project> diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/EssentialsHelp.java b/EssentialsUpdate/src/com/earth2me/essentials/update/EssentialsHelp.java new file mode 100644 index 000000000..3caa22cc3 --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/EssentialsHelp.java @@ -0,0 +1,479 @@ +package com.earth2me.essentials.update; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Event.Priority; +import org.bukkit.event.Event.Type; +import org.bukkit.event.player.PlayerChatEvent; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.jibble.pircbot.User; + + +public class EssentialsHelp extends PlayerListener +{ + private transient Player chatUser; + private final transient Server server; + private final transient Plugin plugin; + private final static Charset UTF8 = Charset.forName("utf-8"); + private transient IrcBot ircBot; + + public EssentialsHelp(final Plugin plugin) + { + super(); + this.plugin = plugin; + this.server = plugin.getServer(); + } + + public void registerEvents() + { + final PluginManager pluginManager = server.getPluginManager(); + pluginManager.registerEvent(Type.PLAYER_QUIT, this, Priority.Low, plugin); + pluginManager.registerEvent(Type.PLAYER_CHAT, this, Priority.Low, plugin); + } + + public void onCommand(CommandSender sender) + { + if (sender instanceof Player && sender.hasPermission("essentials.helpchat")) + { + if (chatUser == null) + { + chatUser = (Player)sender; + ircBot = null; + sender.sendMessage("You will be connected to the Essentials Help Chat."); + sender.sendMessage("All your chat messages will be forwarded to the channel. You can't chat with other players on your server while in help chat, but you can use commands."); + sender.sendMessage("Please be patient, if noone is available, check back later."); + sender.sendMessage("Type !help to get a list of all commands."); + sender.sendMessage("Type !quit to leave the channel."); + sender.sendMessage("Do you want to join the channel now? (yes/no)"); + } + if (!chatUser.equals(sender)) + { + sender.sendMessage("The player " + chatUser.getDisplayName() + " is already using the essentialshelp."); + } + } + else + { + sender.sendMessage("Please run the command as op from in game."); + } + } + + public void onDisable() + { + if ( ircBot != null) + { + ircBot.quit(); + ircBot = null; + } + } + + private boolean sendChatMessage(final Player player, final String message) + { + final String messageCleaned = message.trim(); + if (messageCleaned.isEmpty()) + { + return false; + } + if (ircBot == null) + { + if (messageCleaned.equalsIgnoreCase("yes")) + { + player.sendMessage("Connecting..."); + connectToIRC(player); + return true; + } + if (messageCleaned.equalsIgnoreCase("no") || message.equalsIgnoreCase("!quit")) + { + chatUser = null; + return true; + } + return false; + } + else + { + if (ircBot.isKicked()) { + chatUser = null; + ircBot.quit(); + ircBot = null; + return false; + } + final String lowMessage = messageCleaned.toLowerCase(); + if (lowMessage.startsWith("!quit")) + { + chatUser = null; + if (ircBot != null) { + ircBot.quit(); + ircBot = null; + } + player.sendMessage("Connection closed."); + return true; + } + if (!ircBot.isConnected() || ircBot.getChannels().length == 0) + { + return false; + } + if (lowMessage.startsWith("!list")) + { + final User[] members = ircBot.getUsers(); + final StringBuilder sb = new StringBuilder(); + for (User user : members) + { + if (sb.length() > 0) + { + sb.append("§f, "); + } + if (user.isOp() || user.hasVoice()) + { + sb.append("§6"); + } + else + { + sb.append("§7"); + } + sb.append(user.getPrefix()).append(user.getNick()); + } + player.sendMessage(sb.toString()); + return true; + } + if (lowMessage.startsWith("!help")) + { + player.sendMessage("Commands: (Note: Files send to the chat will be public viewable.)"); + player.sendMessage("!errors - Send the last server errors to the chat."); + player.sendMessage("!startup - Send the last startup messages to the chat."); + player.sendMessage("!config - Sends your Essentials config to the chat."); + player.sendMessage("!list - List all players in chat."); + player.sendMessage("!quit - Leave chat."); + return true; + } + if (lowMessage.startsWith("!errors")) + { + sendErrors(); + return true; + } + if (lowMessage.startsWith("!startup")) + { + sendStartup(); + return true; + } + if (lowMessage.startsWith("!config")) + { + sendConfig(); + return true; + } + ircBot.sendMessage(messageCleaned); + chatUser.sendMessage("§6" + ircBot.getNick() + ": §7" + messageCleaned); + return true; + } + } + + private String buildIrcName() + { + final StringBuilder nameBuilder = new StringBuilder(); + nameBuilder.append(chatUser.getName()); + + final Matcher versionMatch = Pattern.compile("git-Bukkit-([0-9]+).([0-9]+).([0-9]+)-[0-9]+-[0-9a-z]+-b([0-9]+)jnks.*").matcher(server.getVersion()); + if (versionMatch.matches()) + { + nameBuilder.append(" CB"); + nameBuilder.append(versionMatch.group(4)); + } + + final Plugin essentials = server.getPluginManager().getPlugin("Essentials"); + if (essentials != null) + { + nameBuilder.append(" ESS"); + nameBuilder.append(essentials.getDescription().getVersion()); + } + + final Plugin groupManager = server.getPluginManager().getPlugin("GroupManager"); + if (groupManager != null) + { + nameBuilder.append(" GM"); + if (!groupManager.isEnabled()) + { + nameBuilder.append('!'); + } + } + + final Plugin pex = server.getPluginManager().getPlugin("PermissionsEx"); + if (pex != null) + { + nameBuilder.append(" PEX"); + if (!pex.isEnabled()) + { + nameBuilder.append('!'); + } + nameBuilder.append(pex.getDescription().getVersion()); + } + + final Plugin pb = server.getPluginManager().getPlugin("PermissionsBukkit"); + if (pb != null) + { + nameBuilder.append(" PB"); + if (!pb.isEnabled()) + { + nameBuilder.append('!'); + } + nameBuilder.append(pb.getDescription().getVersion()); + } + + final Plugin bp = server.getPluginManager().getPlugin("bPermissions"); + if (bp != null) + { + nameBuilder.append(" BP"); + if (!bp.isEnabled()) + { + nameBuilder.append('!'); + } + nameBuilder.append(bp.getDescription().getVersion()); + } + + final Plugin perm = server.getPluginManager().getPlugin("Permissions"); + if (perm != null) + { + nameBuilder.append(" P"); + if (!perm.isEnabled()) + { + nameBuilder.append('!'); + } + nameBuilder.append(perm.getDescription().getVersion()); + } + + return nameBuilder.toString(); + } + + private void connectToIRC(final Player player) + { + ircBot = new IrcBot(player, "Ess_" + player.getName(), buildIrcName()); + } + + private void sendErrors() + { + BufferedReader page = null; + try + { + File bukkitFolder = plugin.getDataFolder().getAbsoluteFile().getParentFile().getParentFile(); + if (bukkitFolder == null || !bukkitFolder.exists()) + { + chatUser.sendMessage("Bukkit folder not found."); + return; + } + File logFile = new File(bukkitFolder, "server.log"); + if (!logFile.exists()) + { + chatUser.sendMessage("Server log not found."); + return; + } + FileInputStream fis = new FileInputStream(logFile); + if (logFile.length() > 1000000) + { + fis.skip(logFile.length() - 1000000); + } + page = new BufferedReader(new InputStreamReader(fis)); + final StringBuilder input = new StringBuilder(); + String line; + Pattern pattern = Pattern.compile("^[0-9 :-]+\\[INFO\\].*"); + while ((line = page.readLine()) != null) + { + if (!pattern.matcher(line).matches()) + { + input.append(line).append("\n"); + } + } + if (input.length() > 10000) + { + input.delete(0, input.length() - 10000); + } + final PastieUpload pastie = new PastieUpload(); + final String url = pastie.send(input.toString()); + String message = "Errors: " + url; + chatUser.sendMessage("§6" + ircBot.getNick() + ": §7" + message); + ircBot.sendMessage(message); + } + catch (IOException ex) + { + Bukkit.getLogger().log(Level.SEVERE, null, ex); + chatUser.sendMessage(ex.getMessage()); + } + finally + { + try + { + if (page != null) + { + page.close(); + } + } + catch (IOException ex) + { + Logger.getLogger(EssentialsHelp.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + private void sendStartup() + { + BufferedReader page = null; + try + { + File bukkitFolder = plugin.getDataFolder().getAbsoluteFile().getParentFile().getParentFile(); + if (bukkitFolder == null || !bukkitFolder.exists()) + { + chatUser.sendMessage("Bukkit folder not found."); + return; + } + File logFile = new File(bukkitFolder, "server.log"); + if (!logFile.exists()) + { + chatUser.sendMessage("Server log not found."); + return; + } + FileInputStream fis = new FileInputStream(logFile); + if (logFile.length() > 1000000) + { + fis.skip(logFile.length() - 1000000); + } + page = new BufferedReader(new InputStreamReader(fis)); + final StringBuilder input = new StringBuilder(); + String line; + Pattern patternStart = Pattern.compile("^[0-9 :-]+\\[INFO\\] Starting minecraft server version.*"); + Pattern patternEnd = Pattern.compile("^[0-9 :-]+\\[INFO\\] Done \\([0-9.,]+s\\)! For help, type \"help\".*"); + boolean log = false; + while ((line = page.readLine()) != null) + { + if (patternStart.matcher(line).matches()) + { + if (input.length() > 0) + { + input.delete(0, input.length()); + } + log = true; + } + if (log) + { + input.append(line).append("\n"); + } + if (patternEnd.matcher(line).matches()) + { + log = false; + } + } + if (input.length() > 10000) + { + input.delete(0, input.length() - 10000); + } + final PastieUpload pastie = new PastieUpload(); + final String url = pastie.send(input.toString()); + String message = "Startup: " + url; + chatUser.sendMessage("§6" + ircBot.getNick() + ": §7" + message); + ircBot.sendMessage(message); + } + catch (IOException ex) + { + Bukkit.getLogger().log(Level.SEVERE, null, ex); + chatUser.sendMessage(ex.getMessage()); + } + finally + { + try + { + if (page != null) + { + page.close(); + } + } + catch (IOException ex) + { + Logger.getLogger(EssentialsHelp.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + private void sendConfig() + { + BufferedReader page = null; + try + { + File configFolder = new File(plugin.getDataFolder().getParentFile(), "Essentials"); + if (!configFolder.exists()) + { + chatUser.sendMessage("Essentials plugin folder not found."); + return; + } + File configFile = new File(configFolder, "config.yml"); + if (!configFile.exists()) + { + chatUser.sendMessage("Essentials config file not found."); + return; + } + page = new BufferedReader(new InputStreamReader(new FileInputStream(configFile), UTF8)); + final StringBuilder input = new StringBuilder(); + String line; + while ((line = page.readLine()) != null) + { + input.append(line).append("\n"); + } + final PastieUpload pastie = new PastieUpload(); + final String url = pastie.send(input.toString()); + String message = "Essentials config.yml: " + url; + chatUser.sendMessage("§6" + ircBot.getNick() + ": §7" + message); + ircBot.sendMessage(message); + + } + catch (IOException ex) + { + Bukkit.getLogger().log(Level.SEVERE, null, ex); + chatUser.sendMessage(ex.getMessage()); + } + finally + { + try + { + if (page != null) + { + page.close(); + } + } + catch (IOException ex) + { + Logger.getLogger(EssentialsHelp.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + @Override + public void onPlayerChat(PlayerChatEvent event) + { + if (event.getPlayer() == chatUser) + { + boolean success = sendChatMessage(event.getPlayer(), event.getMessage()); + event.setCancelled(success); + return; + } + } + + @Override + public void onPlayerQuit(PlayerQuitEvent event) + { + chatUser = null; + if (ircBot != null) { + ircBot.quit(); + ircBot = null; + } + return; + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/EssentialsUpdate.java b/EssentialsUpdate/src/com/earth2me/essentials/update/EssentialsUpdate.java new file mode 100644 index 000000000..d4ee6c0fc --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/EssentialsUpdate.java @@ -0,0 +1,66 @@ +package com.earth2me.essentials.update; + +import com.earth2me.essentials.update.UpdateCheck.CheckResult; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; + + +public class EssentialsUpdate extends JavaPlugin +{ + private transient EssentialsHelp essentialsHelp; + private transient UpdateProcess updateProcess; + + @Override + public void onEnable() + { + if (!getDataFolder().exists() && !getDataFolder().mkdirs() ) { + Bukkit.getLogger().severe("Could not create data folder:"+getDataFolder().getPath()); + } + essentialsHelp = new EssentialsHelp(this); + essentialsHelp.registerEvents(); + + final UpdateCheck updateCheck = new UpdateCheck(this); + updateProcess = new UpdateProcess(this, updateCheck); + updateProcess.registerEvents(); + + Bukkit.getLogger().info("EssentialsUpdate " + getDescription().getVersion() + " loaded."); + + if (updateCheck.isEssentialsInstalled()) + { + updateCheck.checkForUpdates(); + final Version myVersion = new Version(getDescription().getVersion()); + if (updateCheck.getResult() == CheckResult.NEW_ESS && myVersion.equals(updateCheck.getNewVersion())) + { + Bukkit.getLogger().info("Versions of EssentialsUpdate and Essentials do not match. Starting automatic update."); + updateProcess.doAutomaticUpdate(); + } + updateCheck.scheduleUpdateTask(); + } + else + { + Bukkit.getLogger().info("Essentials is ready for installation. Join the game and follow the instructions."); + } + } + + @Override + public void onDisable() + { + essentialsHelp.onDisable(); + } + + @Override + public boolean onCommand(final CommandSender sender, final Command command, final String label, final String[] args) + { + if (command.getName().equalsIgnoreCase("essentialsupdate")) + { + updateProcess.onCommand(sender); + } + if (command.getName().equalsIgnoreCase("essentialshelp")) + { + essentialsHelp.onCommand(sender); + } + return true; + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/GetFile.java b/EssentialsUpdate/src/com/earth2me/essentials/update/GetFile.java new file mode 100644 index 000000000..888950f34 --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/GetFile.java @@ -0,0 +1,112 @@ +package com.earth2me.essentials.update; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.logging.Logger; + + +public class GetFile +{ + private transient URLConnection connection; + private transient MessageDigest digest; + + public GetFile(final String urlString) throws MalformedURLException, IOException + { + final URL url = new URL(urlString); + this.connection = url.openConnection(); + this.connection.setConnectTimeout(1000); + this.connection.setReadTimeout(5000); + this.connection.setUseCaches(false); + this.connection.connect(); + final int respCode = ((HttpURLConnection)this.connection).getResponseCode(); + if (respCode >= 300 && respCode < 400 && this.connection.getHeaderField("Location") != null) + { + connection.getInputStream().close(); + final URL redirect = new URL(this.connection.getHeaderField("Location")); + this.connection = redirect.openConnection(); + this.connection.setConnectTimeout(1000); + this.connection.setReadTimeout(5000); + this.connection.setUseCaches(false); + this.connection.connect(); + } + } + + public void saveTo(final File file) throws IOException + { + try + { + saveTo(file, null); + } + catch (NoSuchAlgorithmException ex) + { + // Ignore because the code is never called + } + } + + public void saveTo(final File file, final String key) throws IOException, NoSuchAlgorithmException + { + if (key != null) + { + digest = MessageDigest.getInstance("SHA256"); + } + final byte[] buffer = new byte[1024 * 8]; + boolean brokenFile = false; + final BufferedInputStream input = new BufferedInputStream(connection.getInputStream()); + try + { + final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(file)); + try + { + int length; + do + { + length = input.read(buffer); + if (length >= 0) + { + if (key != null) + { + digest.update(buffer, 0, length); + } + output.write(buffer, 0, length); + } + } + while (length >= 0); + if (key != null) + { + final byte[] checksum = digest.digest(); + final String checksumString = new BigInteger(checksum).toString(36); + if (!checksumString.equals(key)) + { + brokenFile = true; + } + } + } + finally + { + output.close(); + } + if (brokenFile && !file.delete()) + { + Logger.getLogger("Minecraft").severe("Could not delete file " + file.getPath()); + } + } + finally + { + input.close(); + } + if (brokenFile) + { + throw new IOException("Checksum check failed."); + } + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/IrcBot.java b/EssentialsUpdate/src/com/earth2me/essentials/update/IrcBot.java new file mode 100644 index 000000000..a314df15d --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/IrcBot.java @@ -0,0 +1,188 @@ +package com.earth2me.essentials.update; + +import java.io.IOException; +import java.util.logging.Level; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jibble.pircbot.Colors; +import org.jibble.pircbot.IrcException; +import org.jibble.pircbot.PircBot; +import org.jibble.pircbot.User; + + +public class IrcBot extends PircBot +{ + private static final String CHANNEL = "#essentials"; + private static final int PORT = 6667; + private static final String SERVER = "irc.esper.net"; + private transient boolean reconnect = true; + private final transient Player player; + private transient boolean kicked = false; + + public IrcBot(final Player player, final String nickName, final String versionString) + { + super(); + this.player = player; + setName(nickName); + setLogin("esshelp"); + setVersion(versionString); + connect(); + joinChannel(CHANNEL); + } + + private void connect() + { + try + { + connect(SERVER, PORT); + return; + } + catch (IOException ex) + { + Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex); + } + catch (IrcException ex) + { + Bukkit.getLogger().log(Level.SEVERE, ex.getMessage(), ex); + } + } + + public void quit() + { + reconnect = false; + disconnect(); + } + + @Override + protected void onConnect() + { + reconnect = true; + } + + @Override + protected void onDisconnect() + { + if (reconnect) + { + int tries = 10; + while (!isConnected()) + { + try + { + tries--; + reconnect(); + } + catch (Exception e) + { + Bukkit.getLogger().log(Level.WARNING, e.getMessage(), e); + try + { + Thread.sleep(10000); + } + catch (InterruptedException ex) + { + Bukkit.getLogger().log(Level.WARNING, e.getMessage(), e); + } + } + if (tries <= 0) + { + player.sendMessage("Connection lost to server."); + kicked = true; + break; + } + } + } + } + + @Override + protected void onKick(String channel, String kickerNick, String kickerLogin, String kickerHostname, String recipientNick, String reason) + { + if (recipientNick.equals(getNick())) + { + player.sendMessage("You have been kicked from the channel: " + reason); + quit(); + kicked = true; + } + } + + public boolean isKicked() + { + return kicked; + } + + @Override + protected void onMessage(String channel, String sender, String login, String hostname, String message) + { + player.sendMessage(formatChatMessage(sender, message, false)); + } + + @Override + protected void onAction(String sender, String login, String hostname, String target, String action) + { + player.sendMessage(formatChatMessage(sender, action, true)); + } + + @Override + protected void onNotice(String sourceNick, String sourceLogin, String sourceHostname, String target, String notice) + { + player.sendMessage(formatChatMessage(sourceNick, notice, false)); + } + + @Override + protected void onTopic(String channel, String topic, String setBy, long date, boolean changed) + { + player.sendMessage(formatChatMessage(channel, topic, false)); + } + + public String formatChatMessage(final String nick, final String message, final boolean action) + { + final StringBuilder builder = new StringBuilder(); + builder.append("§6"); + if (action) + { + builder.append('*'); + } + builder.append(nick); + if (!action) + { + builder.append(':'); + } + builder.append(" §7"); + builder.append(replaceColors(message)); + return builder.toString(); + } + + private String replaceColors(final String message) + { + String m = Colors.removeFormatting(message); + m = m.replaceAll("\u000310(,(0?[0-9]|1[0-5]))?", "§b"); + m = m.replaceAll("\u000311(,(0?[0-9]|1[0-5]))?", "§f"); + m = m.replaceAll("\u000312(,(0?[0-9]|1[0-5]))?", "§9"); + m = m.replaceAll("\u000313(,(0?[0-9]|1[0-5]))?", "§d"); + m = m.replaceAll("\u000314(,(0?[0-9]|1[0-5]))?", "§8"); + m = m.replaceAll("\u000315(,(0?[0-9]|1[0-5]))?", "§7"); + m = m.replaceAll("\u00030?1(,(0?[0-9]|1[0-5]))?", "§0"); + m = m.replaceAll("\u00030?2(,(0?[0-9]|1[0-5]))?", "§1"); + m = m.replaceAll("\u00030?3(,(0?[0-9]|1[0-5]))?", "§2"); + m = m.replaceAll("\u00030?4(,(0?[0-9]|1[0-5]))?", "§c"); + m = m.replaceAll("\u00030?5(,(0?[0-9]|1[0-5]))?", "§4"); + m = m.replaceAll("\u00030?6(,(0?[0-9]|1[0-5]))?", "§5"); + m = m.replaceAll("\u00030?7(,(0?[0-9]|1[0-5]))?", "§6"); + m = m.replaceAll("\u00030?8(,(0?[0-9]|1[0-5]))?", "§e"); + m = m.replaceAll("\u00030?9(,(0?[0-9]|1[0-5]))?", "§a"); + m = m.replaceAll("\u00030?0(,(0?[0-9]|1[0-5]))?", "§f"); + m = m.replace("\u000f", "§7"); + m = Colors.removeColors(m); + return m; + } + + public void sendMessage(final String message) + { + sendMessage(CHANNEL, message); + } + + public User[] getUsers() + { + return getUsers(CHANNEL); + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/ModuleInfo.java b/EssentialsUpdate/src/com/earth2me/essentials/update/ModuleInfo.java new file mode 100644 index 000000000..722fca3e1 --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/ModuleInfo.java @@ -0,0 +1,35 @@ +package com.earth2me.essentials.update; + +import java.net.MalformedURLException; +import java.net.URL; +import org.bukkit.configuration.Configuration; + + +public class ModuleInfo +{ + private final transient String url; + private final transient String version; + private final transient String hash; + + public ModuleInfo(final Configuration updateConfig, final String path) + { + url = updateConfig.getString(path + ".url", null); + version = updateConfig.getString(path + ".version", null); + hash = updateConfig.getString(path + ".hash", null); + } + + public URL getUrl() throws MalformedURLException + { + return new URL(url); + } + + public String getVersion() + { + return version; + } + + public String getHash() + { + return hash; + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/PastieUpload.java b/EssentialsUpdate/src/com/earth2me/essentials/update/PastieUpload.java new file mode 100644 index 000000000..6cad44e4d --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/PastieUpload.java @@ -0,0 +1,40 @@ +package com.earth2me.essentials.update; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class PastieUpload +{ + private final transient PostToUrl connection; + + public PastieUpload() throws MalformedURLException + { + connection = new PostToUrl(new URL("http://pastie.org/pastes")); + } + + public String send(final String data) throws IOException + { + final Map<String, Object> map = new HashMap<String, Object>(); + map.put("paste[parser_id]", "19"); + map.put("paste[authorization]", "burger"); + map.put("paste[body]", data); + map.put("paste[restricted]", "1"); + final String html = connection.send(map); + final Matcher matcher = Pattern.compile("(?s).*\\?key=([a-z0-9]+).*").matcher(html); + if (matcher.matches()) + { + final String key = matcher.group(1); + return "http://pastie.org/private/" + key; + } + else + { + throw new IOException("Failed to upload to pastie.org"); + } + } +}
\ No newline at end of file diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/PostToUrl.java b/EssentialsUpdate/src/com/earth2me/essentials/update/PostToUrl.java new file mode 100644 index 000000000..c8978961b --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/PostToUrl.java @@ -0,0 +1,66 @@ +package com.earth2me.essentials.update; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.Charset; +import java.util.Map; +import java.util.Random; + + +public class PostToUrl +{ + private final transient URL url; + private final transient String boundary; + private final transient Random random = new Random(); + private final static String CRLF = "\r\n"; + private final static Charset UTF8 = Charset.forName("utf-8"); + + public PostToUrl(final URL url) + { + this.url = url; + final byte[] bytes = new byte[32]; + random.nextBytes(bytes); + this.boundary = "----------" + new BigInteger(bytes).toString(Character.MAX_RADIX) + "_$"; + } + + public String send(final Map<String, Object> data) throws IOException + { + final URLConnection connection = url.openConnection(); + connection.setRequestProperty("content-type", "multipart/form-data; boundary=" + boundary); + final StringBuilder dataBuilder = new StringBuilder(); + for (Map.Entry<String, Object> entry : data.entrySet()) + { + if (entry.getValue() instanceof String) + { + dataBuilder.append("--").append(boundary).append(CRLF); + dataBuilder.append("Content-Disposition: form-data; name=\"").append(entry.getKey()).append('"').append(CRLF); + dataBuilder.append(CRLF); + dataBuilder.append(entry.getValue()).append(CRLF); + } + // TODO: Add support for file upload + } + dataBuilder.append("--").append(boundary).append("--").append(CRLF); + dataBuilder.append(CRLF); + connection.setDoOutput(true); + final byte[] message = dataBuilder.toString().getBytes(UTF8); + connection.setRequestProperty("content-length", Integer.toString(message.length)); + connection.connect(); + final OutputStream stream = connection.getOutputStream(); + stream.write(message); + stream.close(); + final BufferedReader page = new BufferedReader(new InputStreamReader(connection.getInputStream(), UTF8)); + final StringBuilder input = new StringBuilder(); + String line; + while ((line = page.readLine()) != null) + { + input.append(line).append("\n"); + } + page.close(); + return input.toString(); + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateCheck.java b/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateCheck.java new file mode 100644 index 000000000..dcda252a0 --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateCheck.java @@ -0,0 +1,203 @@ +package com.earth2me.essentials.update; + +import java.io.File; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; + + +public class UpdateCheck +{ + private transient CheckResult result = CheckResult.UNKNOWN; + private transient Version currentVersion; + private transient Version newVersion = null; + private transient int bukkitResult = 0; + private transient UpdateFile updateFile; + private final static int CHECK_INTERVAL = 20 * 60 * 60 * 6; + private final transient Plugin plugin; + private transient boolean essentialsInstalled; + + public UpdateCheck(Plugin plugin) + { + this.plugin = plugin; + updateFile = new UpdateFile(plugin); + checkForEssentials(); + } + + private void checkForEssentials() + { + PluginManager pm = plugin.getServer().getPluginManager(); + Plugin essentials = pm.getPlugin("Essentials"); + if (essentials == null) + { + essentialsInstalled = false; + if (new File(plugin.getDataFolder().getParentFile(), "Essentials.jar").exists()) + { + Bukkit.getLogger().severe("Essentials.jar found, but not recognized by Bukkit. Broken download?"); + } + } + else + { + essentialsInstalled = true; + currentVersion = new Version(essentials.getDescription().getVersion()); + } + } + + public void scheduleUpdateTask() + { + plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() + { + @Override + public void run() + { + updateFile = new UpdateFile(plugin); + checkForUpdates(); + } + }, CHECK_INTERVAL, CHECK_INTERVAL); + } + + public boolean isEssentialsInstalled() + { + return essentialsInstalled; + } + + public CheckResult getResult() + { + return result; + } + + int getNewBukkitVersion() + { + return bukkitResult; + } + + VersionInfo getNewVersionInfo() + { + return updateFile.getVersions().get(newVersion); + } + + public enum CheckResult + { + NEW_ESS, NEW_ESS_BUKKIT, NEW_BUKKIT, OK, UNKNOWN + } + + public void checkForUpdates() + { + if (currentVersion == null) + { + return; + } + final Map<Version, VersionInfo> versions = updateFile.getVersions(); + final int bukkitVersion = getBukkitVersion(); + Version higher = null; + Version found = null; + Version lower = null; + int bukkitHigher = 0; + int bukkitLower = 0; + for (Entry<Version, VersionInfo> entry : versions.entrySet()) + { + final int minBukkit = entry.getValue().getMinBukkit(); + final int maxBukkit = entry.getValue().getMaxBukkit(); + if (minBukkit == 0 || maxBukkit == 0) + { + continue; + } + if (bukkitVersion <= maxBukkit) + { + if (bukkitVersion < minBukkit) + { + if (higher == null || higher.compareTo(entry.getKey()) < 0) + { + + higher = entry.getKey(); + bukkitHigher = minBukkit; + } + } + else + { + if (found == null || found.compareTo(entry.getKey()) < 0) + { + found = entry.getKey(); + } + } + } + else + { + if (lower == null || lower.compareTo(entry.getKey()) < 0) + { + lower = entry.getKey(); + bukkitLower = minBukkit; + } + } + } + if (found != null) + { + if (found.compareTo(currentVersion) > 0) + { + result = CheckResult.NEW_ESS; + newVersion = found; + } + else + { + result = CheckResult.OK; + } + } + else if (higher != null) + { + if (higher.compareTo(currentVersion) > 0) + { + newVersion = higher; + result = CheckResult.NEW_ESS_BUKKIT; + bukkitResult = bukkitHigher; + } + else if (higher.compareTo(currentVersion) < 0) + { + result = CheckResult.UNKNOWN; + } + else + { + result = CheckResult.NEW_BUKKIT; + bukkitResult = bukkitHigher; + } + } + else if (lower != null) + { + if (lower.compareTo(currentVersion) > 0) + { + result = CheckResult.NEW_ESS_BUKKIT; + newVersion = lower; + bukkitResult = bukkitLower; + } + else if (lower.compareTo(currentVersion) < 0) + { + result = CheckResult.UNKNOWN; + } + else + { + result = CheckResult.NEW_BUKKIT; + bukkitResult = bukkitLower; + } + } + + } + + private int getBukkitVersion() + { + final Matcher versionMatch = Pattern.compile("git-Bukkit-([0-9]+).([0-9]+).([0-9]+)-[0-9]+-[0-9a-z]+-b([0-9]+)jnks.*").matcher(plugin.getServer().getVersion()); + if (versionMatch.matches()) + { + return Integer.parseInt(versionMatch.group(4)); + } + throw new NumberFormatException("Bukkit Version changed!"); + } + + public Version getNewVersion() + { + return newVersion; + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateFile.java b/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateFile.java new file mode 100644 index 000000000..8f34bffc4 --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateFile.java @@ -0,0 +1,204 @@ +package com.earth2me.essentials.update; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.plugin.Plugin; +import org.bukkit.configuration.file.YamlConfiguration; + + +public class UpdateFile +{ + private final static Logger LOGGER = Logger.getLogger("Minecraft"); + private final static String UPDATE_URL = "http://goo.gl/67jev"; + private final static BigInteger PUBLIC_KEY = new BigInteger("5ha6a2d4qdy17ttkg8evh74sl5a87djojwenu12k1lvy8ui6003e6l06rntczpoh99mhc3txj8mqlxw111oyy9yl7s7qpyluyzix3j1odxrxx4u52gxvyu6qiteapczkzvi7rxgeqsozz7b19rdx73a7quo9ybwpz1cr82r7x5k0pg2a73pjjsv2j1awr13azo7klrcxp9y5xxwf5qv1s3tw4zqftli18u0ek5qkbzfbgk1v5n2f11pkwwk6p0mibrn26wnjbv11vyiqgu95o7busmt6vf5q7grpcenl637w83mbin56s3asj1131b2mscj9xep3cbj7la9tgsxl5bj87vzy8sk2d34kzwqdqgh9nry43nqqus12l1stmiv184r8r3jcy8w43e8h1u1mzklldb5eytkuhayqik8l3ns04hwt8sgacvw534be8sx26qrn5s1", 36); + private final transient File file; + private transient YamlConfiguration updateConfig; + private final transient Plugin plugin; + private final transient TreeMap<Version, VersionInfo> versions = new TreeMap<Version, VersionInfo>(); + + public UpdateFile(final Plugin plugin) + { + this.plugin = plugin; + final long lastUpdate = Long.parseLong(plugin.getConfig().getString("lastupdate", "0")); + file = new File(plugin.getDataFolder(), "update.yml"); + if (lastUpdate < System.currentTimeMillis() - 1000 * 60 * 60 * 6 || !file.exists()) + { + if (file.exists() && !file.delete()) + { + LOGGER.log(Level.SEVERE, "Could not delete file update.yml!"); + return; + } + if (!downloadFile() || !checkFile()) + { + LOGGER.log(Level.SEVERE, "Could not download and verify file update.yml!"); + return; + } + } + try + { + readVersions(); + } + catch (Exception ex) + { + LOGGER.log(Level.SEVERE, "Could not load update.yml!"); + return; + } + } + + private boolean downloadFile() + { + GetFile getFile; + try + { + getFile = new GetFile(UPDATE_URL); + getFile.saveTo(file); + plugin.getConfig().set("lastupdate", System.currentTimeMillis()); + plugin.getConfig().save(new File(plugin.getDataFolder(),"config.yml")); + return true; + } + catch (IOException ex) + { + LOGGER.log(Level.SEVERE, "Error while downloading update.yml", ex); + return false; + } + } + + private boolean checkFile() + { + BufferedInputStream bis = null; + try + { + bis = new BufferedInputStream(new FileInputStream(file)); + if (bis.read() != '#') + { + throw new IOException("File has to start with #"); + } + final StringBuilder length = new StringBuilder(); + final StringBuilder signature = new StringBuilder(); + boolean isSignature = false; + do + { + final int cur = bis.read(); + if (cur == -1) + { + break; + } + if (cur == ':') + { + isSignature = true; + } + else if (cur == '\n') + { + break; + } + else if ((cur >= '0' && cur <= '9') + || (cur >= 'a' && cur <= 'z')) + { + if (isSignature) + { + signature.append((char)cur); + } + else + { + length.append((char)cur); + } + } + else + { + throw new IOException("Illegal character in signature!"); + } + } + while (true); + if (length.length() == 0 || signature.length() == 0) + { + throw new IOException("Broken signature!"); + } + final int sigLength = new BigInteger(length.toString(), 36).intValue(); + if (sigLength < 0 || sigLength > 2048) + { + throw new IOException("Invalid signature length!"); + } + final byte[] sigBytes = new BigInteger(signature.toString(), 36).toByteArray(); + if (sigLength < sigBytes.length) + { + throw new IOException("Length is less then available bytes."); + } + byte[] realBytes; + if (sigLength == sigBytes.length) + { + realBytes = sigBytes; + } + else + { + realBytes = new byte[sigLength]; + System.arraycopy(sigBytes, 0, realBytes, sigLength - sigBytes.length, sigBytes.length); + } + final X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(PUBLIC_KEY.toByteArray()); + final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + final PublicKey pubKey = keyFactory.generatePublic(pubKeySpec); + final Signature rsa = Signature.getInstance("SHA256withRSA"); + rsa.initVerify(pubKey); + final byte[] buffer = new byte[2048]; + int readLength; + do + { + readLength = bis.read(buffer); + if (readLength >= 0) + { + rsa.update(buffer, 0, readLength); + } + } + while (readLength >= 0); + return rsa.verify(realBytes); + } + catch (Exception ex) + { + LOGGER.log(Level.SEVERE, ex.getMessage(), ex); + } + finally + { + try + { + if (bis != null) + { + bis.close(); + } + } + catch (IOException ex) + { + LOGGER.log(Level.SEVERE, ex.getMessage(), ex); + } + } + return false; + } + + private void readVersions() throws Exception + { + updateConfig = new YamlConfiguration(); + updateConfig.load(file); + versions.clear(); + for (String versionString : updateConfig.getKeys(false)) + { + final Version version = new Version(versionString); + final VersionInfo info = new VersionInfo(updateConfig, versionString); + versions.put(version, info); + } + } + + public Map<Version, VersionInfo> getVersions() + { + return Collections.unmodifiableMap(versions.descendingMap()); + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateProcess.java b/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateProcess.java new file mode 100644 index 000000000..95898bcb6 --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/UpdateProcess.java @@ -0,0 +1,128 @@ +package com.earth2me.essentials.update; + +import java.util.List; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Event.Priority; +import org.bukkit.event.Event.Type; +import org.bukkit.event.player.PlayerChatEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; + + +public class UpdateProcess extends PlayerListener +{ + private transient Player currentPlayer; + private final transient Plugin plugin; + private final transient UpdateCheck updateCheck; + + public UpdateProcess(final Plugin plugin, final UpdateCheck updateCheck) + { + this.plugin = plugin; + this.updateCheck = updateCheck; + } + + public void registerEvents() + { + final PluginManager pluginManager = plugin.getServer().getPluginManager(); + pluginManager.registerEvent(Type.PLAYER_QUIT, this, Priority.Low, plugin); + pluginManager.registerEvent(Type.PLAYER_CHAT, this, Priority.Lowest, plugin); + } + + @Override + public void onPlayerChat(final PlayerChatEvent event) + { + if (event.getPlayer() == currentPlayer) + { + reactOnMessage(event.getMessage()); + event.setCancelled(true); + return; + } + } + + @Override + public void onPlayerJoin(final PlayerJoinEvent event) + { + final Player player = event.getPlayer(); + if (player.hasPermission("essentials.update") && !updateCheck.isEssentialsInstalled()) + { + player.sendMessage("Hello " + player.getDisplayName()); + player.sendMessage("Please type /essentialsupdate into the chat to start the installation of Essentials."); + } + if (player.hasPermission("essentials.update")) + { + final UpdateCheck.CheckResult result = updateCheck.getResult(); + switch (result) + { + case NEW_ESS: + player.sendMessage("The new version " + updateCheck.getNewVersion().toString() + " for Essentials is available. Please type /essentialsupdate to update."); + break; + case NEW_BUKKIT: + player.sendMessage("Your bukkit version is not the recommended build for Essentials, please update to version " + updateCheck.getNewBukkitVersion() + "."); + break; + case NEW_ESS_BUKKIT: + player.sendMessage("There is a new version " + updateCheck.getNewVersion().toString() + " of Essentials for Bukkit " + updateCheck.getNewBukkitVersion()); + break; + default: + } + } + } + + void doAutomaticUpdate() + { + final UpdatesDownloader downloader = new UpdatesDownloader(); + final VersionInfo info = updateCheck.getNewVersionInfo(); + final List<String> changelog = info.getChangelog(); + Bukkit.getLogger().info("Essentials changelog " + updateCheck.getNewVersion().toString()); + for (String line : changelog) + { + Bukkit.getLogger().info(" - "+line); + } + downloader.start(plugin.getServer().getUpdateFolderFile(), info); + } + + void doManualUpdate() + { + + } + + void onCommand(CommandSender sender) + { + if (sender instanceof Player && sender.hasPermission("essentials.install")) + { + if (currentPlayer == null) + { + currentPlayer = (Player)sender; + if (updateCheck.isEssentialsInstalled()) + { + doManualUpdate(); + } + else + { + sender.sendMessage("Thank you for choosing Essentials."); + sender.sendMessage("The following installation wizard will guide you through the installation of Essentials."); + sender.sendMessage("Your answers will be saved for a later update."); + sender.sendMessage("Please answer the messages with yes or no, if not otherwise stated."); + sender.sendMessage("Write bye/exit/quit if you want to exit the wizard at anytime."); + + } + } + if (!currentPlayer.equals(sender)) + { + sender.sendMessage("The player " + currentPlayer.getDisplayName() + " is already using the wizard."); + } + } + else + { + sender.sendMessage("Please run the command as op from in game."); + } + } + + private void reactOnMessage(String message) + { + throw new UnsupportedOperationException("Not yet implemented"); + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/UpdatesDownloader.java b/EssentialsUpdate/src/com/earth2me/essentials/update/UpdatesDownloader.java new file mode 100644 index 000000000..28ffdfe3c --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/UpdatesDownloader.java @@ -0,0 +1,19 @@ +package com.earth2me.essentials.update; + +import java.io.File; + + +public class UpdatesDownloader +{ + + UpdatesDownloader() + { + + } + + void start(File updateFolderFile, VersionInfo newVersion) + { + + } + +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/Version.java b/EssentialsUpdate/src/com/earth2me/essentials/update/Version.java new file mode 100644 index 000000000..8e6cbc97f --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/Version.java @@ -0,0 +1,173 @@ +package com.earth2me.essentials.update; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class Version implements Comparable<Version> +{ + public enum Type + { + STABLE, PREVIEW, DEVELOPER + } + + public int getMajor() + { + return major; + } + + public int getMinor() + { + return minor; + } + + public int getBuild() + { + return build; + } + + public Type getType() + { + return type; + } + private final transient int major; + private final transient int minor; + private final transient int build; + private final transient Type type; + + public Version(final String versionString) + { + final Matcher matcher = Pattern.compile("(Pre|Dev)?([0-9]+)[_\\.]([0-9]+)[_\\.]([0-9]+).*").matcher(versionString); + if (!matcher.matches() || matcher.groupCount() < 4) + { + type = Type.DEVELOPER; + major = 99; + minor = build = 0; + return; + } + if (versionString.startsWith("Pre")) + { + type = Type.PREVIEW; + } + else if (versionString.startsWith("Dev")) + { + type = Type.DEVELOPER; + } + else + { + type = Type.STABLE; + } + major = Integer.parseInt(matcher.group(2)); + minor = Integer.parseInt(matcher.group(3)); + build = Integer.parseInt(matcher.group(4)); + } + + @Override + public int compareTo(final Version other) + { + int ret = 0; + if (other.getType() == Type.DEVELOPER && getType() != Type.DEVELOPER) + { + ret = -1; + } + else if (getType() == Type.DEVELOPER && other.getType() != Type.DEVELOPER) + { + ret = 1; + } + else if (other.getMajor() > getMajor()) + { + ret = -1; + } + else if (getMajor() > other.getMajor()) + { + ret = 1; + } + else if (other.getMinor() > getMinor()) + { + ret = -1; + } + else if (getMinor() > other.getMinor()) + { + ret = 1; + } + else if (other.getBuild() > getBuild()) + { + ret = -1; + } + else if (getBuild() > other.getBuild()) + { + ret = 1; + } + else if (other.getType() == Type.STABLE && getType() == Type.PREVIEW) + { + ret = -1; + } + else if (getType() == Type.STABLE && other.getType() == Type.PREVIEW) + { + ret = 1; + } + return ret; + } + + @Override + public boolean equals(final Object obj) + { + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + final Version other = (Version)obj; + if (this.major != other.major) + { + return false; + } + if (this.minor != other.minor) + { + return false; + } + if (this.build != other.build) + { + return false; + } + if (this.type != other.type) + { + return false; + } + return true; + } + + @Override + public int hashCode() + { + int hash = 5; + hash = 71 * hash + this.major; + hash = 71 * hash + this.minor; + hash = 71 * hash + this.build; + hash = 71 * hash + (this.type != null ? this.type.hashCode() : 0); + return hash; + } + + @Override + public String toString() + { + final StringBuilder builder = new StringBuilder(); + if (type == Type.DEVELOPER) + { + builder.append("Dev"); + } + if (type == Type.PREVIEW) + { + builder.append("Pre"); + } + builder.append(major); + builder.append('.'); + builder.append(minor); + builder.append('.'); + builder.append(build); + return builder.toString(); + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/VersionInfo.java b/EssentialsUpdate/src/com/earth2me/essentials/update/VersionInfo.java new file mode 100644 index 000000000..9cd1e5edb --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/VersionInfo.java @@ -0,0 +1,48 @@ +package com.earth2me.essentials.update; + +import java.util.ArrayList; +import org.bukkit.configuration.Configuration; +import java.util.Collections; +import java.util.List; + + +public class VersionInfo +{ + private final transient List<String> changelog; + private final transient int minBukkit; + private final transient int maxBukkit; + private final transient List<ModuleInfo> modules; + + public VersionInfo(final Configuration updateConfig, final String path) + { + changelog = updateConfig.getList(path + ".changelog", Collections.<String>emptyList()); + minBukkit = updateConfig.getInt(path + ".min-bukkit", 0); + maxBukkit = updateConfig.getInt(path + ".max-bukkit", 0); + modules = new ArrayList<ModuleInfo>(); + final String modulesPath = path + ".modules"; + for (String module : updateConfig.getKeys(false)) + { + modules.add(new ModuleInfo(updateConfig, modulesPath + module)); + } + } + + public List<String> getChangelog() + { + return Collections.unmodifiableList(changelog); + } + + public int getMinBukkit() + { + return minBukkit; + } + + public int getMaxBukkit() + { + return maxBukkit; + } + + public List<ModuleInfo> getModules() + { + return Collections.unmodifiableList(modules); + } +} diff --git a/EssentialsUpdate/src/com/earth2me/essentials/update/states/Modules.java b/EssentialsUpdate/src/com/earth2me/essentials/update/states/Modules.java new file mode 100644 index 000000000..56d2445c9 --- /dev/null +++ b/EssentialsUpdate/src/com/earth2me/essentials/update/states/Modules.java @@ -0,0 +1,7 @@ +package com.earth2me.essentials.update.states; + + +public class Modules +{ + +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/Colors.java b/EssentialsUpdate/src/org/jibble/pircbot/Colors.java new file mode 100755 index 000000000..c763cba22 --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/Colors.java @@ -0,0 +1,293 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + + +package org.jibble.pircbot; + +/** + * The Colors class provides several static fields and methods that you may + * find useful when writing an IRC Bot. + * <p> + * This class contains constants that are useful for formatting lines + * sent to IRC servers. These constants allow you to apply various + * formatting to the lines, such as colours, boldness, underlining + * and reverse text. + * <p> + * The class contains static methods to remove colours and formatting + * from lines of IRC text. + * <p> + * Here are some examples of how to use the contants from within a + * class that extends PircBot and imports org.jibble.pircbot.*; + * + * <pre> sendMessage("#cs", Colors.BOLD + "A bold hello!"); + * <b>A bold hello!</b> + * sendMessage("#cs", Colors.RED + "Red" + Colors.NORMAL + " text"); + * <font color="red">Red</font> text + * sendMessage("#cs", Colors.BOLD + Colors.RED + "Bold and red"); + * <b><font color="red">Bold and red</font></b></pre> + * + * Please note that some IRC channels may be configured to reject any + * messages that use colours. Also note that older IRC clients may be + * unable to correctly display lines that contain colours and other + * control characters. + * <p> + * Note that this class name has been spelt in the American style in + * order to remain consistent with the rest of the Java API. + * + * + * @since 0.9.12 + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public class Colors { + + + /** + * Removes all previously applied color and formatting attributes. + */ + public static final String NORMAL = "\u000f"; + + + /** + * Bold text. + */ + public static final String BOLD = "\u0002"; + + + /** + * Underlined text. + */ + public static final String UNDERLINE = "\u001f"; + + + /** + * Reversed text (may be rendered as italic text in some clients). + */ + public static final String REVERSE = "\u0016"; + + + /** + * White coloured text. + */ + public static final String WHITE = "\u000300"; + + + /** + * Black coloured text. + */ + public static final String BLACK = "\u000301"; + + + /** + * Dark blue coloured text. + */ + public static final String DARK_BLUE = "\u000302"; + + + /** + * Dark green coloured text. + */ + public static final String DARK_GREEN = "\u000303"; + + + /** + * Red coloured text. + */ + public static final String RED = "\u000304"; + + + /** + * Brown coloured text. + */ + public static final String BROWN = "\u000305"; + + + /** + * Purple coloured text. + */ + public static final String PURPLE = "\u000306"; + + + /** + * Olive coloured text. + */ + public static final String OLIVE = "\u000307"; + + + /** + * Yellow coloured text. + */ + public static final String YELLOW = "\u000308"; + + + /** + * Green coloured text. + */ + public static final String GREEN = "\u000309"; + + + /** + * Teal coloured text. + */ + public static final String TEAL = "\u000310"; + + + /** + * Cyan coloured text. + */ + public static final String CYAN = "\u000311"; + + + /** + * Blue coloured text. + */ + public static final String BLUE = "\u000312"; + + + /** + * Magenta coloured text. + */ + public static final String MAGENTA = "\u000313"; + + + /** + * Dark gray coloured text. + */ + public static final String DARK_GRAY = "\u000314"; + + + /** + * Light gray coloured text. + */ + public static final String LIGHT_GRAY = "\u000315"; + + + /** + * This class should not be constructed. + */ + private Colors() { + + } + + + /** + * Removes all colours from a line of IRC text. + * + * @since PircBot 1.2.0 + * + * @param line the input text. + * + * @return the same text, but with all colours removed. + */ + public static String removeColors(String line) { + int length = line.length(); + StringBuffer buffer = new StringBuffer(); + int i = 0; + while (i < length) { + char ch = line.charAt(i); + if (ch == '\u0003') { + i++; + // Skip "x" or "xy" (foreground color). + if (i < length) { + ch = line.charAt(i); + if (Character.isDigit(ch)) { + i++; + if (i < length) { + ch = line.charAt(i); + if (Character.isDigit(ch)) { + i++; + } + } + // Now skip ",x" or ",xy" (background color). + if (i < length) { + ch = line.charAt(i); + if (ch == ',') { + i++; + if (i < length) { + ch = line.charAt(i); + if (Character.isDigit(ch)) { + i++; + if (i < length) { + ch = line.charAt(i); + if (Character.isDigit(ch)) { + i++; + } + } + } + else { + // Keep the comma. + i--; + } + } + else { + // Keep the comma. + i--; + } + } + } + } + } + } + else if (ch == '\u000f') { + i++; + } + else { + buffer.append(ch); + i++; + } + } + return buffer.toString(); + } + + + /** + * Remove formatting from a line of IRC text. + * + * @since PircBot 1.2.0 + * + * @param line the input text. + * + * @return the same text, but without any bold, underlining, reverse, etc. + */ + public static String removeFormatting(String line) { + int length = line.length(); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < length; i++) { + char ch = line.charAt(i); + if (ch == '\u000f' || ch == '\u0002' || ch == '\u001f' || ch == '\u0016') { + // Don't add this character. + } + else { + buffer.append(ch); + } + } + return buffer.toString(); + } + + + /** + * Removes all formatting and colours from a line of IRC text. + * + * @since PircBot 1.2.0 + * + * @param line the input text. + * + * @return the same text, but without formatting and colour characters. + * + */ + public static String removeFormattingAndColors(String line) { + return removeFormatting(removeColors(line)); + } + +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/InputThread.java b/EssentialsUpdate/src/org/jibble/pircbot/InputThread.java new file mode 100755 index 000000000..1c30ad815 --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/InputThread.java @@ -0,0 +1,169 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + + +package org.jibble.pircbot; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * A Thread which reads lines from the IRC server. It then + * passes these lines to the PircBot without changing them. + * This running Thread also detects disconnection from the server + * and is thus used by the OutputThread to send lines to the server. + * + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public class InputThread extends Thread { + + /** + * The InputThread reads lines from the IRC server and allows the + * PircBot to handle them. + * + * @param bot An instance of the underlying PircBot. + * @param breader The BufferedReader that reads lines from the server. + * @param bwriter The BufferedWriter that sends lines to the server. + */ + InputThread(PircBot bot, Socket socket, BufferedReader breader, BufferedWriter bwriter) { + _bot = bot; + _socket = socket; + _breader = breader; + _bwriter = bwriter; + this.setName(this.getClass() + "-Thread"); + } + + + /** + * Sends a raw line to the IRC server as soon as possible, bypassing the + * outgoing message queue. + * + * @param line The raw line to send to the IRC server. + */ + void sendRawLine(String line) { + OutputThread.sendRawLine(_bot, _bwriter, line); + } + + + /** + * Returns true if this InputThread is connected to an IRC server. + * The result of this method should only act as a rough guide, + * as the result may not be valid by the time you act upon it. + * + * @return True if still connected. + */ + boolean isConnected() { + return _isConnected; + } + + + /** + * Called to start this Thread reading lines from the IRC server. + * When a line is read, this method calls the handleLine method + * in the PircBot, which may subsequently call an 'onXxx' method + * in the PircBot subclass. If any subclass of Throwable (i.e. + * any Exception or Error) is thrown by your method, then this + * method will print the stack trace to the standard output. It + * is probable that the PircBot may still be functioning normally + * after such a problem, but the existance of any uncaught exceptions + * in your code is something you should really fix. + */ + public void run() { + try { + boolean running = true; + while (running) { + try { + String line = null; + while ((line = _breader.readLine()) != null) { + try { + _bot.handleLine(line); + } + catch (Throwable t) { + // Stick the whole stack trace into a String so we can output it nicely. + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + pw.flush(); + StringTokenizer tokenizer = new StringTokenizer(sw.toString(), "\r\n"); + synchronized (_bot) { + _bot.log("### Your implementation of PircBot is faulty and you have"); + _bot.log("### allowed an uncaught Exception or Error to propagate in your"); + _bot.log("### code. It may be possible for PircBot to continue operating"); + _bot.log("### normally. Here is the stack trace that was produced: -"); + _bot.log("### "); + while (tokenizer.hasMoreTokens()) { + _bot.log("### " + tokenizer.nextToken()); + } + } + } + } + if (line == null) { + // The server must have disconnected us. + running = false; + } + } + catch (InterruptedIOException iioe) { + // This will happen if we haven't received anything from the server for a while. + // So we shall send it a ping to check that we are still connected. + this.sendRawLine("PING " + (System.currentTimeMillis() / 1000)); + // Now we go back to listening for stuff from the server... + } + } + } + catch (Exception e) { + // Do nothing. + } + + // If we reach this point, then we must have disconnected. + try { + _socket.close(); + } + catch (Exception e) { + // Just assume the socket was already closed. + } + + if (!_disposed) { + _bot.log("*** Disconnected."); + _isConnected = false; + _bot.onDisconnect(); + } + + } + + + /** + * Closes the socket without onDisconnect being called subsequently. + */ + public void dispose () { + try { + _disposed = true; + _socket.close(); + } + catch (Exception e) { + // Do nothing. + } + } + + private PircBot _bot = null; + private Socket _socket = null; + private BufferedReader _breader = null; + private BufferedWriter _bwriter = null; + private boolean _isConnected = true; + private boolean _disposed = false; + + public static final int MAX_LINE_LENGTH = 512; + +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/IrcException.java b/EssentialsUpdate/src/org/jibble/pircbot/IrcException.java new file mode 100755 index 000000000..d07d8a956 --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/IrcException.java @@ -0,0 +1,35 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + +package org.jibble.pircbot; + +/** + * An IrcException class. + * + * @since 0.9 + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public class IrcException extends Exception { + + /** + * Constructs a new IrcException. + * + * @param e The error message to report. + */ + public IrcException(String e) { + super(e); + } + +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/NickAlreadyInUseException.java b/EssentialsUpdate/src/org/jibble/pircbot/NickAlreadyInUseException.java new file mode 100755 index 000000000..33887be34 --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/NickAlreadyInUseException.java @@ -0,0 +1,38 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + + +package org.jibble.pircbot; + +/** + * A NickAlreadyInUseException class. This exception is + * thrown when the PircBot attempts to join an IRC server + * with a user name that is already in use. + * + * @since 0.9 + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public class NickAlreadyInUseException extends IrcException { + + /** + * Constructs a new IrcException. + * + * @param e The error message to report. + */ + public NickAlreadyInUseException(String e) { + super(e); + } + +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/OutputThread.java b/EssentialsUpdate/src/org/jibble/pircbot/OutputThread.java new file mode 100755 index 000000000..86ac404d2 --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/OutputThread.java @@ -0,0 +1,105 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + + +package org.jibble.pircbot; + +import java.io.*; +import java.net.*; + +/** + * A Thread which is responsible for sending messages to the IRC server. + * Messages are obtained from the outgoing message queue and sent + * immediately if possible. If there is a flood of messages, then to + * avoid getting kicked from a channel, we put a small delay between + * each one. + * + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public class OutputThread extends Thread { + + + /** + * Constructs an OutputThread for the underlying PircBot. All messages + * sent to the IRC server are sent by this OutputThread to avoid hammering + * the server. Messages are sent immediately if possible. If there are + * multiple messages queued, then there is a delay imposed. + * + * @param bot The underlying PircBot instance. + * @param outQueue The Queue from which we will obtain our messages. + */ + OutputThread(PircBot bot, Queue outQueue) { + _bot = bot; + _outQueue = outQueue; + this.setName(this.getClass() + "-Thread"); + } + + + /** + * A static method to write a line to a BufferedOutputStream and then pass + * the line to the log method of the supplied PircBot instance. + * + * @param bot The underlying PircBot instance. + * @param out The BufferedOutputStream to write to. + * @param line The line to be written. "\r\n" is appended to the end. + * @param encoding The charset to use when encoing this string into a + * byte array. + */ + static void sendRawLine(PircBot bot, BufferedWriter bwriter, String line) { + if (line.length() > bot.getMaxLineLength() - 2) { + line = line.substring(0, bot.getMaxLineLength() - 2); + } + synchronized(bwriter) { + try { + bwriter.write(line + "\r\n"); + bwriter.flush(); + bot.log(">>>" + line); + } + catch (Exception e) { + // Silent response - just lose the line. + } + } + } + + + /** + * This method starts the Thread consuming from the outgoing message + * Queue and sending lines to the server. + */ + public void run() { + try { + boolean running = true; + while (running) { + // Small delay to prevent spamming of the channel + Thread.sleep(_bot.getMessageDelay()); + + String line = (String) _outQueue.next(); + if (line != null) { + _bot.sendRawLine(line); + } + else { + running = false; + } + } + } + catch (InterruptedException e) { + // Just let the method return naturally... + } + } + + private PircBot _bot = null; + private Queue _outQueue = null; + +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/PircBot.java b/EssentialsUpdate/src/org/jibble/pircbot/PircBot.java new file mode 100755 index 000000000..a5f049cde --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/PircBot.java @@ -0,0 +1,2808 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + + +package org.jibble.pircbot; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * PircBot is a Java framework for writing IRC bots quickly and easily. + * <p> + * It provides an event-driven architecture to handle common IRC + * events, flood protection, DCC support, ident support, and more. + * The comprehensive logfile format is suitable for use with pisg to generate + * channel statistics. + * <p> + * Methods of the PircBot class can be called to send events to the IRC server + * that it connects to. For example, calling the sendMessage method will + * send a message to a channel or user on the IRC server. Multiple servers + * can be supported using multiple instances of PircBot. + * <p> + * To perform an action when the PircBot receives a normal message from the IRC + * server, you would override the onMessage method defined in the PircBot + * class. All on<i>XYZ</i> methods in the PircBot class are automatically called + * when the event <i>XYZ</i> happens, so you would override these if you wish + * to do something when it does happen. + * <p> + * Some event methods, such as onPing, should only really perform a specific + * function (i.e. respond to a PING from the server). For your convenience, such + * methods are already correctly implemented in the PircBot and should not + * normally need to be overridden. Please read the full documentation for each + * method to see which ones are already implemented by the PircBot class. + * <p> + * Please visit the PircBot homepage at + * <a href="http://www.jibble.org/pircbot.php">http://www.jibble.org/pircbot.php</a> + * for full revision history, a beginners guide to creating your first PircBot + * and a list of some existing Java IRC bots and clients that use the PircBot + * framework. + * + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public abstract class PircBot implements ReplyConstants { + + + /** + * The definitive version number of this release of PircBot. + * (Note: Change this before automatically building releases) + */ + public static final String VERSION = "1.5.0"; + + + private static final int OP_ADD = 1; + private static final int OP_REMOVE = 2; + private static final int VOICE_ADD = 3; + private static final int VOICE_REMOVE = 4; + + + /** + * Constructs a PircBot with the default settings. Your own constructors + * in classes which extend the PircBot abstract class should be responsible + * for changing the default settings if required. + */ + public PircBot() {} + + + /** + * Attempt to connect to the specified IRC server. + * The onConnect method is called upon success. + * + * @param hostname The hostname of the server to connect to. + * + * @throws IOException if it was not possible to connect to the server. + * @throws IrcException if the server would not let us join it. + * @throws NickAlreadyInUseException if our nick is already in use on the server. + */ + public final synchronized void connect(String hostname) throws IOException, IrcException, NickAlreadyInUseException { + this.connect(hostname, 6667, null); + } + + + /** + * Attempt to connect to the specified IRC server and port number. + * The onConnect method is called upon success. + * + * @param hostname The hostname of the server to connect to. + * @param port The port number to connect to on the server. + * + * @throws IOException if it was not possible to connect to the server. + * @throws IrcException if the server would not let us join it. + * @throws NickAlreadyInUseException if our nick is already in use on the server. + */ + public final synchronized void connect(String hostname, int port) throws IOException, IrcException, NickAlreadyInUseException { + this.connect(hostname, port, null); + } + + + /** + * Attempt to connect to the specified IRC server using the supplied + * password. + * The onConnect method is called upon success. + * + * @param hostname The hostname of the server to connect to. + * @param port The port number to connect to on the server. + * @param password The password to use to join the server. + * + * @throws IOException if it was not possible to connect to the server. + * @throws IrcException if the server would not let us join it. + * @throws NickAlreadyInUseException if our nick is already in use on the server. + */ + public final synchronized void connect(String hostname, int port, String password) throws IOException, IrcException, NickAlreadyInUseException { + + _server = hostname; + _port = port; + _password = password; + + if (isConnected()) { + throw new IOException("The PircBot is already connected to an IRC server. Disconnect first."); + } + + // Don't clear the outqueue - there might be something important in it! + + // Clear everything we may have know about channels. + this.removeAllChannels(); + + // Connect to the server. + Socket socket = new Socket(hostname, port); + this.log("*** Connected to server."); + + _inetAddress = socket.getLocalAddress(); + + InputStreamReader inputStreamReader = null; + OutputStreamWriter outputStreamWriter = null; + if (getEncoding() != null) { + // Assume the specified encoding is valid for this JVM. + inputStreamReader = new InputStreamReader(socket.getInputStream(), getEncoding()); + outputStreamWriter = new OutputStreamWriter(socket.getOutputStream(), getEncoding()); + } + else { + // Otherwise, just use the JVM's default encoding. + inputStreamReader = new InputStreamReader(socket.getInputStream()); + outputStreamWriter = new OutputStreamWriter(socket.getOutputStream()); + } + + BufferedReader breader = new BufferedReader(inputStreamReader); + BufferedWriter bwriter = new BufferedWriter(outputStreamWriter); + + // Attempt to join the server. + if (password != null && !password.equals("")) { + OutputThread.sendRawLine(this, bwriter, "PASS " + password); + } + String nick = this.getName(); + OutputThread.sendRawLine(this, bwriter, "NICK " + nick); + OutputThread.sendRawLine(this, bwriter, "USER " + this.getLogin() + " 8 * :" + this.getVersion()); + + _inputThread = new InputThread(this, socket, breader, bwriter); + + // Read stuff back from the server to see if we connected. + String line = null; + int tries = 1; + while ((line = breader.readLine()) != null) { + + this.handleLine(line); + + int firstSpace = line.indexOf(" "); + int secondSpace = line.indexOf(" ", firstSpace + 1); + if (secondSpace >= 0) { + String code = line.substring(firstSpace + 1, secondSpace); + + if (code.equals("004")) { + // We're connected to the server. + break; + } + else if (code.equals("433")) { + if (_autoNickChange) { + tries++; + nick = getName() + tries; + OutputThread.sendRawLine(this, bwriter, "NICK " + nick); + } + else { + socket.close(); + _inputThread = null; + throw new NickAlreadyInUseException(line); + } + } + else if (code.equals("439")) { + // No action required. + } + else if (code.startsWith("5") || code.startsWith("4")) { + socket.close(); + _inputThread = null; + throw new IrcException("Could not log into the IRC server: " + line); + } + } + this.setNick(nick); + + } + + this.log("*** Logged onto server."); + + // This makes the socket timeout on read operations after 5 minutes. + // Maybe in some future version I will let the user change this at runtime. + socket.setSoTimeout(5 * 60 * 1000); + + // Now start the InputThread to read all other lines from the server. + _inputThread.start(); + + // Now start the outputThread that will be used to send all messages. + if (_outputThread == null) { + _outputThread = new OutputThread(this, _outQueue); + _outputThread.start(); + } + + this.onConnect(); + + } + + + /** + * Reconnects to the IRC server that we were previously connected to. + * If necessary, the appropriate port number and password will be used. + * This method will throw an IrcException if we have never connected + * to an IRC server previously. + * + * @since PircBot 0.9.9 + * + * @throws IOException if it was not possible to connect to the server. + * @throws IrcException if the server would not let us join it. + * @throws NickAlreadyInUseException if our nick is already in use on the server. + */ + public final synchronized void reconnect() throws IOException, IrcException, NickAlreadyInUseException{ + if (getServer() == null) { + throw new IrcException("Cannot reconnect to an IRC server because we were never connected to one previously!"); + } + connect(getServer(), getPort(), getPassword()); + } + + + /** + * This method disconnects from the server cleanly by calling the + * quitServer() method. Providing the PircBot was connected to an + * IRC server, the onDisconnect() will be called as soon as the + * disconnection is made by the server. + * + * @see #quitServer() quitServer + * @see #quitServer(String) quitServer + */ + public final synchronized void disconnect() { + this.quitServer(); + } + + + /** + * When you connect to a server and your nick is already in use and + * this is set to true, a new nick will be automatically chosen. + * This is done by adding numbers to the end of the nick until an + * available nick is found. + * + * @param autoNickChange Set to true if you want automatic nick changes + * during connection. + */ + public void setAutoNickChange(boolean autoNickChange) { + _autoNickChange = autoNickChange; + } + + /** + * Joins a channel. + * + * @param channel The name of the channel to join (eg "#cs"). + */ + public final void joinChannel(String channel) { + this.sendRawLine("JOIN " + channel); + } + + + /** + * Joins a channel with a key. + * + * @param channel The name of the channel to join (eg "#cs"). + * @param key The key that will be used to join the channel. + */ + public final void joinChannel(String channel, String key) { + this.joinChannel(channel + " " + key); + } + + + /** + * Parts a channel. + * + * @param channel The name of the channel to leave. + */ + public final void partChannel(String channel) { + this.sendRawLine("PART " + channel); + } + + + /** + * Parts a channel, giving a reason. + * + * @param channel The name of the channel to leave. + * @param reason The reason for parting the channel. + */ + public final void partChannel(String channel, String reason) { + this.sendRawLine("PART " + channel + " :" + reason); + } + + + /** + * Quits from the IRC server. + * Providing we are actually connected to an IRC server, the + * onDisconnect() method will be called as soon as the IRC server + * disconnects us. + */ + public final void quitServer() { + this.quitServer(""); + } + + + /** + * Quits from the IRC server with a reason. + * Providing we are actually connected to an IRC server, the + * onDisconnect() method will be called as soon as the IRC server + * disconnects us. + * + * @param reason The reason for quitting the server. + */ + public final void quitServer(String reason) { + this.sendRawLine("QUIT :" + reason); + } + + + /** + * Sends a raw line to the IRC server as soon as possible, bypassing the + * outgoing message queue. + * + * @param line The raw line to send to the IRC server. + */ + public final synchronized void sendRawLine(String line) { + if (isConnected()) { + _inputThread.sendRawLine(line); + } + } + + /** + * Sends a raw line through the outgoing message queue. + * + * @param line The raw line to send to the IRC server. + */ + public final synchronized void sendRawLineViaQueue(String line) { + if (line == null) { + throw new NullPointerException("Cannot send null messages to server"); + } + if (isConnected()) { + _outQueue.add(line); + } + } + + + /** + * Sends a message to a channel or a private message to a user. These + * messages are added to the outgoing message queue and sent at the + * earliest possible opportunity. + * <p> + * Some examples: - + * <pre> // Send the message "Hello!" to the channel #cs. + * sendMessage("#cs", "Hello!"); + * + * // Send a private message to Paul that says "Hi". + * sendMessage("Paul", "Hi");</pre> + * + * You may optionally apply colours, boldness, underlining, etc to + * the message by using the <code>Colors</code> class. + * + * @param target The name of the channel or user nick to send to. + * @param message The message to send. + * + * @see Colors + */ + public final void sendMessage(String target, String message) { + _outQueue.add("PRIVMSG " + target + " :" + message); + } + + + /** + * Sends an action to the channel or to a user. + * + * @param target The name of the channel or user nick to send to. + * @param action The action to send. + * + * @see Colors + */ + public final void sendAction(String target, String action) { + sendCTCPCommand(target, "ACTION " + action); + } + + + /** + * Sends a notice to the channel or to a user. + * + * @param target The name of the channel or user nick to send to. + * @param notice The notice to send. + */ + public final void sendNotice(String target, String notice) { + _outQueue.add("NOTICE " + target + " :" + notice); + } + + + /** + * Sends a CTCP command to a channel or user. (Client to client protocol). + * Examples of such commands are "PING <number>", "FINGER", "VERSION", etc. + * For example, if you wish to request the version of a user called "Dave", + * then you would call <code>sendCTCPCommand("Dave", "VERSION");</code>. + * The type of response to such commands is largely dependant on the target + * client software. + * + * @since PircBot 0.9.5 + * + * @param target The name of the channel or user to send the CTCP message to. + * @param command The CTCP command to send. + */ + public final void sendCTCPCommand(String target, String command) { + _outQueue.add("PRIVMSG " + target + " :\u0001" + command + "\u0001"); + } + + + /** + * Attempt to change the current nick (nickname) of the bot when it + * is connected to an IRC server. + * After confirmation of a successful nick change, the getNick method + * will return the new nick. + * + * @param newNick The new nick to use. + */ + public final void changeNick(String newNick) { + this.sendRawLine("NICK " + newNick); + } + + + /** + * Identify the bot with NickServ, supplying the appropriate password. + * Some IRC Networks (such as freenode) require users to <i>register</i> and + * <i>identify</i> with NickServ before they are able to send private messages + * to other users, thus reducing the amount of spam. If you are using + * an IRC network where this kind of policy is enforced, you will need + * to make your bot <i>identify</i> itself to NickServ before you can send + * private messages. Assuming you have already registered your bot's + * nick with NickServ, this method can be used to <i>identify</i> with + * the supplied password. It usually makes sense to identify with NickServ + * immediately after connecting to a server. + * <p> + * This method issues a raw NICKSERV command to the server, and is therefore + * safer than the alternative approach of sending a private message to + * NickServ. The latter approach is considered dangerous, as it may cause + * you to inadvertently transmit your password to an untrusted party if you + * connect to a network which does not run a NickServ service and where the + * untrusted party has assumed the nick "NickServ". However, if your IRC + * network is only compatible with the private message approach, you may + * typically identify like so: + * <pre>sendMessage("NickServ", "identify PASSWORD");</pre> + * + * @param password The password which will be used to identify with NickServ. + */ + public final void identify(String password) { + this.sendRawLine("NICKSERV IDENTIFY " + password); + } + + + /** + * Set the mode of a channel. + * This method attempts to set the mode of a channel. This + * may require the bot to have operator status on the channel. + * For example, if the bot has operator status, we can grant + * operator status to "Dave" on the #cs channel + * by calling setMode("#cs", "+o Dave"); + * An alternative way of doing this would be to use the op method. + * + * @param channel The channel on which to perform the mode change. + * @param mode The new mode to apply to the channel. This may include + * zero or more arguments if necessary. + * + * @see #op(String,String) op + */ + public final void setMode(String channel, String mode) { + this.sendRawLine("MODE " + channel + " " + mode); + } + + + /** + * Sends an invitation to join a channel. Some channels can be marked + * as "invite-only", so it may be useful to allow a bot to invite people + * into it. + * + * @param nick The nick of the user to invite + * @param channel The channel you are inviting the user to join. + * + */ + public final void sendInvite(String nick, String channel) { + this.sendRawLine("INVITE " + nick + " :" + channel); + } + + + /** + * Bans a user from a channel. An example of a valid hostmask is + * "*!*compu@*.18hp.net". This may be used in conjunction with the + * kick method to permanently remove a user from a channel. + * Successful use of this method may require the bot to have operator + * status itself. + * + * @param channel The channel to ban the user from. + * @param hostmask A hostmask representing the user we're banning. + */ + public final void ban(String channel, String hostmask) { + this.sendRawLine("MODE " + channel + " +b " + hostmask); + } + + + /** + * Unbans a user from a channel. An example of a valid hostmask is + * "*!*compu@*.18hp.net". + * Successful use of this method may require the bot to have operator + * status itself. + * + * @param channel The channel to unban the user from. + * @param hostmask A hostmask representing the user we're unbanning. + */ + public final void unBan(String channel, String hostmask) { + this.sendRawLine("MODE " + channel + " -b " + hostmask); + } + + + /** + * Grants operator privilidges to a user on a channel. + * Successful use of this method may require the bot to have operator + * status itself. + * + * @param channel The channel we're opping the user on. + * @param nick The nick of the user we are opping. + */ + public final void op(String channel, String nick) { + this.setMode(channel, "+o " + nick); + } + + + /** + * Removes operator privilidges from a user on a channel. + * Successful use of this method may require the bot to have operator + * status itself. + * + * @param channel The channel we're deopping the user on. + * @param nick The nick of the user we are deopping. + */ + public final void deOp(String channel, String nick) { + this.setMode(channel, "-o " + nick); + } + + + /** + * Grants voice privilidges to a user on a channel. + * Successful use of this method may require the bot to have operator + * status itself. + * + * @param channel The channel we're voicing the user on. + * @param nick The nick of the user we are voicing. + */ + public final void voice(String channel, String nick) { + this.setMode(channel, "+v " + nick); + } + + + /** + * Removes voice privilidges from a user on a channel. + * Successful use of this method may require the bot to have operator + * status itself. + * + * @param channel The channel we're devoicing the user on. + * @param nick The nick of the user we are devoicing. + */ + public final void deVoice(String channel, String nick) { + this.setMode(channel, "-v " + nick); + } + + + /** + * Set the topic for a channel. + * This method attempts to set the topic of a channel. This + * may require the bot to have operator status if the topic + * is protected. + * + * @param channel The channel on which to perform the mode change. + * @param topic The new topic for the channel. + * + */ + public final void setTopic(String channel, String topic) { + this.sendRawLine("TOPIC " + channel + " :" + topic); + } + + + /** + * Kicks a user from a channel. + * This method attempts to kick a user from a channel and + * may require the bot to have operator status in the channel. + * + * @param channel The channel to kick the user from. + * @param nick The nick of the user to kick. + */ + public final void kick(String channel, String nick) { + this.kick(channel, nick, ""); + } + + + /** + * Kicks a user from a channel, giving a reason. + * This method attempts to kick a user from a channel and + * may require the bot to have operator status in the channel. + * + * @param channel The channel to kick the user from. + * @param nick The nick of the user to kick. + * @param reason A description of the reason for kicking a user. + */ + public final void kick(String channel, String nick, String reason) { + this.sendRawLine("KICK " + channel + " " + nick + " :" + reason); + } + + + /** + * Issues a request for a list of all channels on the IRC server. + * When the PircBot receives information for each channel, it will + * call the onChannelInfo method, which you will need to override + * if you want it to do anything useful. + * + * @see #onChannelInfo(String,int,String) onChannelInfo + */ + public final void listChannels() { + this.listChannels(null); + } + + + /** + * Issues a request for a list of all channels on the IRC server. + * When the PircBot receives information for each channel, it will + * call the onChannelInfo method, which you will need to override + * if you want it to do anything useful. + * <p> + * Some IRC servers support certain parameters for LIST requests. + * One example is a parameter of ">10" to list only those channels + * that have more than 10 users in them. Whether these parameters + * are supported or not will depend on the IRC server software. + * + * @param parameters The parameters to supply when requesting the + * list. + * + * @see #onChannelInfo(String,int,String) onChannelInfo + */ + public final void listChannels(String parameters) { + if (parameters == null) { + this.sendRawLine("LIST"); + } + else { + this.sendRawLine("LIST " + parameters); + } + } + + /** + * Adds a line to the log. This log is currently output to the standard + * output and is in the correct format for use by tools such as pisg, the + * Perl IRC Statistics Generator. You may override this method if you wish + * to do something else with log entries. + * Each line in the log begins with a number which + * represents the logging time (as the number of milliseconds since the + * epoch). This timestamp and the following log entry are separated by + * a single space character, " ". Outgoing messages are distinguishable + * by a log entry that has ">>>" immediately following the space character + * after the timestamp. DCC events use "+++" and warnings about unhandled + * Exceptions and Errors use "###". + * <p> + * This implementation of the method will only cause log entries to be + * output if the PircBot has had its verbose mode turned on by calling + * setVerbose(true); + * + * @param line The line to add to the log. + */ + public void log(String line) { + if (_verbose) { + System.out.println(System.currentTimeMillis() + " " + line); + } + } + + + /** + * This method handles events when any line of text arrives from the server, + * then calling the appropriate method in the PircBot. This method is + * protected and only called by the InputThread for this instance. + * <p> + * This method may not be overridden! + * + * @param line The raw line of text from the server. + */ + protected void handleLine(String line) { + this.log(line); + + // Check for server pings. + if (line.startsWith("PING ")) { + // Respond to the ping and return immediately. + this.onServerPing(line.substring(5)); + return; + } + + String sourceNick = ""; + String sourceLogin = ""; + String sourceHostname = ""; + + StringTokenizer tokenizer = new StringTokenizer(line); + String senderInfo = tokenizer.nextToken(); + String command = tokenizer.nextToken(); + String target = null; + + int exclamation = senderInfo.indexOf("!"); + int at = senderInfo.indexOf("@"); + if (senderInfo.startsWith(":")) { + if (exclamation > 0 && at > 0 && exclamation < at) { + sourceNick = senderInfo.substring(1, exclamation); + sourceLogin = senderInfo.substring(exclamation + 1, at); + sourceHostname = senderInfo.substring(at + 1); + } + else { + + if (tokenizer.hasMoreTokens()) { + String token = command; + + int code = -1; + try { + code = Integer.parseInt(token); + } + catch (NumberFormatException e) { + // Keep the existing value. + } + + if (code != -1) { + String errorStr = token; + String response = line.substring(line.indexOf(errorStr, senderInfo.length()) + 4, line.length()); + this.processServerResponse(code, response); + // Return from the method. + return; + } + else { + // This is not a server response. + // It must be a nick without login and hostname. + // (or maybe a NOTICE or suchlike from the server) + sourceNick = senderInfo; + target = token; + } + } + else { + // We don't know what this line means. + this.onUnknown(line); + // Return from the method; + return; + } + + } + } + + command = command.toUpperCase(); + if (sourceNick.startsWith(":")) { + sourceNick = sourceNick.substring(1); + } + if (target == null) { + target = tokenizer.nextToken(); + } + if (target.startsWith(":")) { + target = target.substring(1); + } + + // Check for CTCP requests. + if (command.equals("PRIVMSG") && line.indexOf(":\u0001") > 0 && line.endsWith("\u0001")) { + String request = line.substring(line.indexOf(":\u0001") + 2, line.length() - 1); + if (request.equals("VERSION")) { + // VERSION request + this.onVersion(sourceNick, sourceLogin, sourceHostname, target); + } + else if (request.startsWith("ACTION ")) { + // ACTION request + this.onAction(sourceNick, sourceLogin, sourceHostname, target, request.substring(7)); + } + else if (request.startsWith("PING ")) { + // PING request + this.onPing(sourceNick, sourceLogin, sourceHostname, target, request.substring(5)); + } + else if (request.equals("TIME")) { + // TIME request + this.onTime(sourceNick, sourceLogin, sourceHostname, target); + } + else if (request.equals("FINGER")) { + // FINGER request + this.onFinger(sourceNick, sourceLogin, sourceHostname, target); + } + else { + // An unknown CTCP message - ignore it. + this.onUnknown(line); + } + } + else if (command.equals("PRIVMSG") && _channelPrefixes.indexOf(target.charAt(0)) >= 0) { + // This is a normal message to a channel. + this.onMessage(target, sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2)); + } + else if (command.equals("PRIVMSG")) { + // This is a private message to us. + this.onPrivateMessage(sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2)); + } + else if (command.equals("JOIN")) { + // Someone is joining a channel. + String channel = target; + this.addUser(channel, new User("", sourceNick)); + this.onJoin(channel, sourceNick, sourceLogin, sourceHostname); + } + else if (command.equals("PART")) { + // Someone is parting from a channel. + this.removeUser(target, sourceNick); + if (sourceNick.equals(this.getNick())) { + this.removeChannel(target); + } + this.onPart(target, sourceNick, sourceLogin, sourceHostname); + } + else if (command.equals("NICK")) { + // Somebody is changing their nick. + String newNick = target; + this.renameUser(sourceNick, newNick); + if (sourceNick.equals(this.getNick())) { + // Update our nick if it was us that changed nick. + this.setNick(newNick); + } + this.onNickChange(sourceNick, sourceLogin, sourceHostname, newNick); + } + else if (command.equals("NOTICE")) { + // Someone is sending a notice. + this.onNotice(sourceNick, sourceLogin, sourceHostname, target, line.substring(line.indexOf(" :") + 2)); + } + else if (command.equals("QUIT")) { + // Someone has quit from the IRC server. + if (sourceNick.equals(this.getNick())) { + this.removeAllChannels(); + } + else { + this.removeUser(sourceNick); + } + this.onQuit(sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2)); + } + else if (command.equals("KICK")) { + // Somebody has been kicked from a channel. + String recipient = tokenizer.nextToken(); + if (recipient.equals(this.getNick())) { + this.removeChannel(target); + } + this.removeUser(target, recipient); + this.onKick(target, sourceNick, sourceLogin, sourceHostname, recipient, line.substring(line.indexOf(" :") + 2)); + } + else if (command.equals("MODE")) { + // Somebody is changing the mode on a channel or user. + String mode = line.substring(line.indexOf(target, 2) + target.length() + 1); + if (mode.startsWith(":")) { + mode = mode.substring(1); + } + this.processMode(target, sourceNick, sourceLogin, sourceHostname, mode); + } + else if (command.equals("TOPIC")) { + // Someone is changing the topic. + this.onTopic(target, line.substring(line.indexOf(" :") + 2), sourceNick, System.currentTimeMillis(), true); + } + else if (command.equals("INVITE")) { + // Somebody is inviting somebody else into a channel. + this.onInvite(target, sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2)); + } + else { + // If we reach this point, then we've found something that the PircBot + // Doesn't currently deal with. + this.onUnknown(line); + } + + } + + + /** + * This method is called once the PircBot has successfully connected to + * the IRC server. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.6 + */ + protected void onConnect() {} + + + /** + * This method carries out the actions to be performed when the PircBot + * gets disconnected. This may happen if the PircBot quits from the + * server, or if the connection is unexpectedly lost. + * <p> + * Disconnection from the IRC server is detected immediately if either + * we or the server close the connection normally. If the connection to + * the server is lost, but neither we nor the server have explicitly closed + * the connection, then it may take a few minutes to detect (this is + * commonly referred to as a "ping timeout"). + * <p> + * If you wish to get your IRC bot to automatically rejoin a server after + * the connection has been lost, then this is probably the ideal method to + * override to implement such functionality. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + */ + protected void onDisconnect() {} + + + /** + * This method is called by the PircBot when a numeric response + * is received from the IRC server. We use this method to + * allow PircBot to process various responses from the server + * before then passing them on to the onServerResponse method. + * <p> + * Note that this method is private and should not appear in any + * of the javadoc generated documenation. + * + * @param code The three-digit numerical code for the response. + * @param response The full response from the IRC server. + */ + private final void processServerResponse(int code, String response) { + + if (code == RPL_LIST) { + // This is a bit of information about a channel. + int firstSpace = response.indexOf(' '); + int secondSpace = response.indexOf(' ', firstSpace + 1); + int thirdSpace = response.indexOf(' ', secondSpace + 1); + int colon = response.indexOf(':'); + String channel = response.substring(firstSpace + 1, secondSpace); + int userCount = 0; + try { + userCount = Integer.parseInt(response.substring(secondSpace + 1, thirdSpace)); + } + catch (NumberFormatException e) { + // Stick with the value of zero. + } + String topic = response.substring(colon + 1); + this.onChannelInfo(channel, userCount, topic); + } + else if (code == RPL_TOPIC) { + // This is topic information about a channel we've just joined. + int firstSpace = response.indexOf(' '); + int secondSpace = response.indexOf(' ', firstSpace + 1); + int colon = response.indexOf(':'); + String channel = response.substring(firstSpace + 1, secondSpace); + String topic = response.substring(colon + 1); + + _topics.put(channel, topic); + + // For backwards compatibility only - this onTopic method is deprecated. + this.onTopic(channel, topic); + } + else if (code == RPL_TOPICINFO) { + StringTokenizer tokenizer = new StringTokenizer(response); + tokenizer.nextToken(); + String channel = tokenizer.nextToken(); + String setBy = tokenizer.nextToken(); + long date = 0; + try { + date = Long.parseLong(tokenizer.nextToken()) * 1000; + } + catch (NumberFormatException e) { + // Stick with the default value of zero. + } + + String topic = (String) _topics.get(channel); + _topics.remove(channel); + + this.onTopic(channel, topic, setBy, date, false); + } + else if (code == RPL_NAMREPLY) { + // This is a list of nicks in a channel that we've just joined. + int channelEndIndex = response.indexOf(" :"); + String channel = response.substring(response.lastIndexOf(' ', channelEndIndex - 1) + 1, channelEndIndex); + + StringTokenizer tokenizer = new StringTokenizer(response.substring(response.indexOf(" :") + 2)); + while (tokenizer.hasMoreTokens()) { + String nick = tokenizer.nextToken(); + String prefix = ""; + if (nick.startsWith("@")) { + // User is an operator in this channel. + prefix = "@"; + } + else if (nick.startsWith("+")) { + // User is voiced in this channel. + prefix = "+"; + } + else if (nick.startsWith(".")) { + // Some wibbly status I've never seen before... + prefix = "."; + } + nick = nick.substring(prefix.length()); + this.addUser(channel, new User(prefix, nick)); + } + } + else if (code == RPL_ENDOFNAMES) { + // This is the end of a NAMES list, so we know that we've got + // the full list of users in the channel that we just joined. + String channel = response.substring(response.indexOf(' ') + 1, response.indexOf(" :")); + User[] users = this.getUsers(channel); + this.onUserList(channel, users); + } + + this.onServerResponse(code, response); + } + + + /** + * This method is called when we receive a numeric response from the + * IRC server. + * <p> + * Numerics in the range from 001 to 099 are used for client-server + * connections only and should never travel between servers. Replies + * generated in response to commands are found in the range from 200 + * to 399. Error replies are found in the range from 400 to 599. + * <p> + * For example, we can use this method to discover the topic of a + * channel when we join it. If we join the channel #test which + * has a topic of "I am King of Test" then the response + * will be "<code>PircBot #test :I Am King of Test</code>" + * with a code of 332 to signify that this is a topic. + * (This is just an example - note that overriding the + * <code>onTopic</code> method is an easier way of finding the + * topic for a channel). Check the IRC RFC for the full list of other + * command response codes. + * <p> + * PircBot implements the interface ReplyConstants, which contains + * contstants that you may find useful here. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param code The three-digit numerical code for the response. + * @param response The full response from the IRC server. + * + * @see ReplyConstants + */ + protected void onServerResponse(int code, String response) {} + + + /** + * This method is called when we receive a user list from the server + * after joining a channel. + * <p> + * Shortly after joining a channel, the IRC server sends a list of all + * users in that channel. The PircBot collects this information and + * calls this method as soon as it has the full list. + * <p> + * To obtain the nick of each user in the channel, call the getNick() + * method on each User object in the array. + * <p> + * At a later time, you may call the getUsers method to obtain an + * up to date list of the users in the channel. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 1.0.0 + * + * @param channel The name of the channel. + * @param users An array of User objects belonging to this channel. + * + * @see User + */ + protected void onUserList(String channel, User[] users) {} + + + /** + * This method is called whenever a message is sent to a channel. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param channel The channel to which the message was sent. + * @param sender The nick of the person who sent the message. + * @param login The login of the person who sent the message. + * @param hostname The hostname of the person who sent the message. + * @param message The actual message sent to the channel. + */ + protected void onMessage(String channel, String sender, String login, String hostname, String message) {} + + + /** + * This method is called whenever a private message is sent to the PircBot. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param sender The nick of the person who sent the private message. + * @param login The login of the person who sent the private message. + * @param hostname The hostname of the person who sent the private message. + * @param message The actual message. + */ + protected void onPrivateMessage(String sender, String login, String hostname, String message) {} + + + /** + * This method is called whenever an ACTION is sent from a user. E.g. + * such events generated by typing "/me goes shopping" in most IRC clients. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param sender The nick of the user that sent the action. + * @param login The login of the user that sent the action. + * @param hostname The hostname of the user that sent the action. + * @param target The target of the action, be it a channel or our nick. + * @param action The action carried out by the user. + */ + protected void onAction(String sender, String login, String hostname, String target, String action) {} + + + /** + * This method is called whenever we receive a notice. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param sourceNick The nick of the user that sent the notice. + * @param sourceLogin The login of the user that sent the notice. + * @param sourceHostname The hostname of the user that sent the notice. + * @param target The target of the notice, be it our nick or a channel name. + * @param notice The notice message. + */ + protected void onNotice(String sourceNick, String sourceLogin, String sourceHostname, String target, String notice) {} + + + /** + * This method is called whenever someone (possibly us) joins a channel + * which we are on. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param channel The channel which somebody joined. + * @param sender The nick of the user who joined the channel. + * @param login The login of the user who joined the channel. + * @param hostname The hostname of the user who joined the channel. + */ + protected void onJoin(String channel, String sender, String login, String hostname) {} + + + /** + * This method is called whenever someone (possibly us) parts a channel + * which we are on. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param channel The channel which somebody parted from. + * @param sender The nick of the user who parted from the channel. + * @param login The login of the user who parted from the channel. + * @param hostname The hostname of the user who parted from the channel. + */ + protected void onPart(String channel, String sender, String login, String hostname) {} + + + /** + * This method is called whenever someone (possibly us) changes nick on any + * of the channels that we are on. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param oldNick The old nick. + * @param login The login of the user. + * @param hostname The hostname of the user. + * @param newNick The new nick. + */ + protected void onNickChange(String oldNick, String login, String hostname, String newNick) {} + + + /** + * This method is called whenever someone (possibly us) is kicked from + * any of the channels that we are in. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param channel The channel from which the recipient was kicked. + * @param kickerNick The nick of the user who performed the kick. + * @param kickerLogin The login of the user who performed the kick. + * @param kickerHostname The hostname of the user who performed the kick. + * @param recipientNick The unfortunate recipient of the kick. + * @param reason The reason given by the user who performed the kick. + */ + protected void onKick(String channel, String kickerNick, String kickerLogin, String kickerHostname, String recipientNick, String reason) {} + + + /** + * This method is called whenever someone (possibly us) quits from the + * server. We will only observe this if the user was in one of the + * channels to which we are connected. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param sourceNick The nick of the user that quit from the server. + * @param sourceLogin The login of the user that quit from the server. + * @param sourceHostname The hostname of the user that quit from the server. + * @param reason The reason given for quitting the server. + */ + protected void onQuit(String sourceNick, String sourceLogin, String sourceHostname, String reason) {} + + + /** + * This method is called whenever a user sets the topic, or when + * PircBot joins a new channel and discovers its topic. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param channel The channel that the topic belongs to. + * @param topic The topic for the channel. + * + * @deprecated As of 1.2.0, replaced by {@link #onTopic(String,String,String,long,boolean)} + */ + protected void onTopic(String channel, String topic) {} + + + /** + * This method is called whenever a user sets the topic, or when + * PircBot joins a new channel and discovers its topic. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param channel The channel that the topic belongs to. + * @param topic The topic for the channel. + * @param setBy The nick of the user that set the topic. + * @param date When the topic was set (milliseconds since the epoch). + * @param changed True if the topic has just been changed, false if + * the topic was already there. + * + */ + protected void onTopic(String channel, String topic, String setBy, long date, boolean changed) {} + + + /** + * After calling the listChannels() method in PircBot, the server + * will start to send us information about each channel on the + * server. You may override this method in order to receive the + * information about each channel as soon as it is received. + * <p> + * Note that certain channels, such as those marked as hidden, + * may not appear in channel listings. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param channel The name of the channel. + * @param userCount The number of users visible in this channel. + * @param topic The topic for this channel. + * + * @see #listChannels() listChannels + */ + protected void onChannelInfo(String channel, int userCount, String topic) {} + + + /** + * Called when the mode of a channel is set. We process this in + * order to call the appropriate onOp, onDeop, etc method before + * finally calling the override-able onMode method. + * <p> + * Note that this method is private and is not intended to appear + * in the javadoc generated documentation. + * + * @param target The channel or nick that the mode operation applies to. + * @param sourceNick The nick of the user that set the mode. + * @param sourceLogin The login of the user that set the mode. + * @param sourceHostname The hostname of the user that set the mode. + * @param mode The mode that has been set. + */ + private final void processMode(String target, String sourceNick, String sourceLogin, String sourceHostname, String mode) { + + if (_channelPrefixes.indexOf(target.charAt(0)) >= 0) { + // The mode of a channel is being changed. + String channel = target; + StringTokenizer tok = new StringTokenizer(mode); + String[] params = new String[tok.countTokens()]; + + int t = 0; + while (tok.hasMoreTokens()) { + params[t] = tok.nextToken(); + t++; + } + + char pn = ' '; + int p = 1; + + // All of this is very large and ugly, but it's the only way of providing + // what the users want :-/ + for (int i = 0; i < params[0].length(); i++) { + char atPos = params[0].charAt(i); + + if (atPos == '+' || atPos == '-') { + pn = atPos; + } + else if (atPos == 'o') { + if (pn == '+') { + this.updateUser(channel, OP_ADD, params[p]); + onOp(channel, sourceNick, sourceLogin, sourceHostname, params[p]); + } + else { + this.updateUser(channel, OP_REMOVE, params[p]); + onDeop(channel, sourceNick, sourceLogin, sourceHostname, params[p]); + } + p++; + } + else if (atPos == 'v') { + if (pn == '+') { + this.updateUser(channel, VOICE_ADD, params[p]); + onVoice(channel, sourceNick, sourceLogin, sourceHostname, params[p]); + } + else { + this.updateUser(channel, VOICE_REMOVE, params[p]); + onDeVoice(channel, sourceNick, sourceLogin, sourceHostname, params[p]); + } + p++; + } + else if (atPos == 'k') { + if (pn == '+') { + onSetChannelKey(channel, sourceNick, sourceLogin, sourceHostname, params[p]); + } + else { + onRemoveChannelKey(channel, sourceNick, sourceLogin, sourceHostname, params[p]); + } + p++; + } + else if (atPos == 'l') { + if (pn == '+') { + onSetChannelLimit(channel, sourceNick, sourceLogin, sourceHostname, Integer.parseInt(params[p])); + p++; + } + else { + onRemoveChannelLimit(channel, sourceNick, sourceLogin, sourceHostname); + } + } + else if (atPos == 'b') { + if (pn == '+') { + onSetChannelBan(channel, sourceNick, sourceLogin, sourceHostname,params[p]); + } + else { + onRemoveChannelBan(channel, sourceNick, sourceLogin, sourceHostname, params[p]); + } + p++; + } + else if (atPos == 't') { + if (pn == '+') { + onSetTopicProtection(channel, sourceNick, sourceLogin, sourceHostname); + } + else { + onRemoveTopicProtection(channel, sourceNick, sourceLogin, sourceHostname); + } + } + else if (atPos == 'n') { + if (pn == '+') { + onSetNoExternalMessages(channel, sourceNick, sourceLogin, sourceHostname); + } + else { + onRemoveNoExternalMessages(channel, sourceNick, sourceLogin, sourceHostname); + } + } + else if (atPos == 'i') { + if (pn == '+') { + onSetInviteOnly(channel, sourceNick, sourceLogin, sourceHostname); + } + else { + onRemoveInviteOnly(channel, sourceNick, sourceLogin, sourceHostname); + } + } + else if (atPos == 'm') { + if (pn == '+') { + onSetModerated(channel, sourceNick, sourceLogin, sourceHostname); + } + else { + onRemoveModerated(channel, sourceNick, sourceLogin, sourceHostname); + } + } + else if (atPos == 'p') { + if (pn == '+') { + onSetPrivate(channel, sourceNick, sourceLogin, sourceHostname); + } + else { + onRemovePrivate(channel, sourceNick, sourceLogin, sourceHostname); + } + } + else if (atPos == 's') { + if (pn == '+') { + onSetSecret(channel, sourceNick, sourceLogin, sourceHostname); + } + else { + onRemoveSecret(channel, sourceNick, sourceLogin, sourceHostname); + } + } + } + + this.onMode(channel, sourceNick, sourceLogin, sourceHostname, mode); + } + else { + // The mode of a user is being changed. + String nick = target; + this.onUserMode(nick, sourceNick, sourceLogin, sourceHostname, mode); + } + } + + + /** + * Called when the mode of a channel is set. + * <p> + * You may find it more convenient to decode the meaning of the mode + * string by overriding the onOp, onDeOp, onVoice, onDeVoice, + * onChannelKey, onDeChannelKey, onChannelLimit, onDeChannelLimit, + * onChannelBan or onDeChannelBan methods as appropriate. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param channel The channel that the mode operation applies to. + * @param sourceNick The nick of the user that set the mode. + * @param sourceLogin The login of the user that set the mode. + * @param sourceHostname The hostname of the user that set the mode. + * @param mode The mode that has been set. + * + */ + protected void onMode(String channel, String sourceNick, String sourceLogin, String sourceHostname, String mode) {} + + + /** + * Called when the mode of a user is set. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 1.2.0 + * + * @param targetNick The nick that the mode operation applies to. + * @param sourceNick The nick of the user that set the mode. + * @param sourceLogin The login of the user that set the mode. + * @param sourceHostname The hostname of the user that set the mode. + * @param mode The mode that has been set. + * + */ + protected void onUserMode(String targetNick, String sourceNick, String sourceLogin, String sourceHostname, String mode) {} + + + + /** + * Called when a user (possibly us) gets granted operator status for a channel. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param recipient The nick of the user that got 'opped'. + */ + protected void onOp(String channel, String sourceNick, String sourceLogin, String sourceHostname, String recipient) {} + + + /** + * Called when a user (possibly us) gets operator status taken away. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param recipient The nick of the user that got 'deopped'. + */ + protected void onDeop(String channel, String sourceNick, String sourceLogin, String sourceHostname, String recipient) {} + + + /** + * Called when a user (possibly us) gets voice status granted in a channel. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param recipient The nick of the user that got 'voiced'. + */ + protected void onVoice(String channel, String sourceNick, String sourceLogin, String sourceHostname, String recipient) {} + + + /** + * Called when a user (possibly us) gets voice status removed. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param recipient The nick of the user that got 'devoiced'. + */ + protected void onDeVoice(String channel, String sourceNick, String sourceLogin, String sourceHostname, String recipient) {} + + + /** + * Called when a channel key is set. When the channel key has been set, + * other users may only join that channel if they know the key. Channel keys + * are sometimes referred to as passwords. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param key The new key for the channel. + */ + protected void onSetChannelKey(String channel, String sourceNick, String sourceLogin, String sourceHostname, String key) {} + + + /** + * Called when a channel key is removed. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param key The key that was in use before the channel key was removed. + */ + protected void onRemoveChannelKey(String channel, String sourceNick, String sourceLogin, String sourceHostname, String key) {} + + + /** + * Called when a user limit is set for a channel. The number of users in + * the channel cannot exceed this limit. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param limit The maximum number of users that may be in this channel at the same time. + */ + protected void onSetChannelLimit(String channel, String sourceNick, String sourceLogin, String sourceHostname, int limit) {} + + + /** + * Called when the user limit is removed for a channel. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onRemoveChannelLimit(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a user (possibly us) gets banned from a channel. Being + * banned from a channel prevents any user with a matching hostmask from + * joining the channel. For this reason, most bans are usually directly + * followed by the user being kicked :-) + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param hostmask The hostmask of the user that has been banned. + */ + protected void onSetChannelBan(String channel, String sourceNick, String sourceLogin, String sourceHostname, String hostmask) {} + + + /** + * Called when a hostmask ban is removed from a channel. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + * @param hostmask + */ + protected void onRemoveChannelBan(String channel, String sourceNick, String sourceLogin, String sourceHostname, String hostmask) {} + + + /** + * Called when topic protection is enabled for a channel. Topic protection + * means that only operators in a channel may change the topic. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onSetTopicProtection(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when topic protection is removed for a channel. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onRemoveTopicProtection(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel is set to only allow messages from users that + * are in the channel. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onSetNoExternalMessages(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel is set to allow messages from any user, even + * if they are not actually in the channel. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onRemoveNoExternalMessages(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel is set to 'invite only' mode. A user may only + * join the channel if they are invited by someone who is already in the + * channel. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onSetInviteOnly(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel has 'invite only' removed. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onRemoveInviteOnly(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel is set to 'moderated' mode. If a channel is + * moderated, then only users who have been 'voiced' or 'opped' may speak + * or change their nicks. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onSetModerated(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel has moderated mode removed. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onRemoveModerated(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel is marked as being in private mode. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onSetPrivate(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel is marked as not being in private mode. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onRemovePrivate(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel is set to be in 'secret' mode. Such channels + * typically do not appear on a server's channel listing. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onSetSecret(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when a channel has 'secret' mode removed. + * <p> + * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param channel The channel in which the mode change took place. + * @param sourceNick The nick of the user that performed the mode change. + * @param sourceLogin The login of the user that performed the mode change. + * @param sourceHostname The hostname of the user that performed the mode change. + */ + protected void onRemoveSecret(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} + + + /** + * Called when we are invited to a channel by a user. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @since PircBot 0.9.5 + * + * @param targetNick The nick of the user being invited - should be us! + * @param sourceNick The nick of the user that sent the invitation. + * @param sourceLogin The login of the user that sent the invitation. + * @param sourceHostname The hostname of the user that sent the invitation. + * @param channel The channel that we're being invited to. + */ + protected void onInvite(String targetNick, String sourceNick, String sourceLogin, String sourceHostname, String channel) {} + + + /** + * This method used to be called when a DCC SEND request was sent to the PircBot. + * Please use the onIncomingFileTransfer method to receive files, as it + * has better functionality and supports resuming. + * + * @deprecated As of PircBot 1.2.0, use {@link #onIncomingFileTransfer(DccFileTransfer)} + */ + protected void onDccSendRequest(String sourceNick, String sourceLogin, String sourceHostname, String filename, long address, int port, int size) {} + + + /** + * This method used to be called when a DCC CHAT request was sent to the PircBot. + * Please use the onIncomingChatRequest method to accept chats, as it + * has better functionality. + * + * @deprecated As of PircBot 1.2.0, use {@link #onIncomingChatRequest(DccChat)} + */ + protected void onDccChatRequest(String sourceNick, String sourceLogin, String sourceHostname, long address, int port) {} + + + /** + * This method is called whenever we receive a VERSION request. + * This abstract implementation responds with the PircBot's _version string, + * so if you override this method, be sure to either mimic its functionality + * or to call super.onVersion(...); + * + * @param sourceNick The nick of the user that sent the VERSION request. + * @param sourceLogin The login of the user that sent the VERSION request. + * @param sourceHostname The hostname of the user that sent the VERSION request. + * @param target The target of the VERSION request, be it our nick or a channel name. + */ + protected void onVersion(String sourceNick, String sourceLogin, String sourceHostname, String target) { + this.sendRawLine("NOTICE " + sourceNick + " :\u0001VERSION " + _version + "\u0001"); + } + + + /** + * This method is called whenever we receive a PING request from another + * user. + * <p> + * This abstract implementation responds correctly, so if you override this + * method, be sure to either mimic its functionality or to call + * super.onPing(...); + * + * @param sourceNick The nick of the user that sent the PING request. + * @param sourceLogin The login of the user that sent the PING request. + * @param sourceHostname The hostname of the user that sent the PING request. + * @param target The target of the PING request, be it our nick or a channel name. + * @param pingValue The value that was supplied as an argument to the PING command. + */ + protected void onPing(String sourceNick, String sourceLogin, String sourceHostname, String target, String pingValue) { + this.sendRawLine("NOTICE " + sourceNick + " :\u0001PING " + pingValue + "\u0001"); + } + + + /** + * The actions to perform when a PING request comes from the server. + * <p> + * This sends back a correct response, so if you override this method, + * be sure to either mimic its functionality or to call + * super.onServerPing(response); + * + * @param response The response that should be given back in your PONG. + */ + protected void onServerPing(String response) { + this.sendRawLine("PONG " + response); + } + + + /** + * This method is called whenever we receive a TIME request. + * <p> + * This abstract implementation responds correctly, so if you override this + * method, be sure to either mimic its functionality or to call + * super.onTime(...); + * + * @param sourceNick The nick of the user that sent the TIME request. + * @param sourceLogin The login of the user that sent the TIME request. + * @param sourceHostname The hostname of the user that sent the TIME request. + * @param target The target of the TIME request, be it our nick or a channel name. + */ + protected void onTime(String sourceNick, String sourceLogin, String sourceHostname, String target) { + this.sendRawLine("NOTICE " + sourceNick + " :\u0001TIME " + new Date().toString() + "\u0001"); + } + + + /** + * This method is called whenever we receive a FINGER request. + * <p> + * This abstract implementation responds correctly, so if you override this + * method, be sure to either mimic its functionality or to call + * super.onFinger(...); + * + * @param sourceNick The nick of the user that sent the FINGER request. + * @param sourceLogin The login of the user that sent the FINGER request. + * @param sourceHostname The hostname of the user that sent the FINGER request. + * @param target The target of the FINGER request, be it our nick or a channel name. + */ + protected void onFinger(String sourceNick, String sourceLogin, String sourceHostname, String target) { + this.sendRawLine("NOTICE " + sourceNick + " :\u0001FINGER " + _finger + "\u0001"); + } + + + /** + * This method is called whenever we receive a line from the server that + * the PircBot has not been programmed to recognise. + * <p> + * The implementation of this method in the PircBot abstract class + * performs no actions and may be overridden as required. + * + * @param line The raw line that was received from the server. + */ + protected void onUnknown(String line) { + // And then there were none :) + } + + + /** + * Sets the verbose mode. If verbose mode is set to true, then log entries + * will be printed to the standard output. The default value is false and + * will result in no output. For general development, we strongly recommend + * setting the verbose mode to true. + * + * @param verbose true if verbose mode is to be used. Default is false. + */ + public final void setVerbose(boolean verbose) { + _verbose = verbose; + } + + + /** + * Sets the name of the bot, which will be used as its nick when it + * tries to join an IRC server. This should be set before joining + * any servers, otherwise the default nick will be used. You would + * typically call this method from the constructor of the class that + * extends PircBot. + * <p> + * The changeNick method should be used if you wish to change your nick + * when you are connected to a server. + * + * @param name The new name of the Bot. + */ + protected final void setName(String name) { + _name = name; + } + + + /** + * Sets the internal nick of the bot. This is only to be called by the + * PircBot class in response to notification of nick changes that apply + * to us. + * + * @param nick The new nick. + */ + private final void setNick(String nick) { + _nick = nick; + } + + + /** + * Sets the internal login of the Bot. This should be set before joining + * any servers. + * + * @param login The new login of the Bot. + */ + protected final void setLogin(String login) { + _login = login; + } + + + /** + * Sets the internal version of the Bot. This should be set before joining + * any servers. + * + * @param version The new version of the Bot. + */ + protected final void setVersion(String version) { + _version = version; + } + + + /** + * Sets the interal finger message. This should be set before joining + * any servers. + * + * @param finger The new finger message for the Bot. + */ + protected final void setFinger(String finger) { + _finger = finger; + } + + + /** + * Gets the name of the PircBot. This is the name that will be used as + * as a nick when we try to join servers. + * + * @return The name of the PircBot. + */ + public final String getName() { + return _name; + } + + + /** + * Returns the current nick of the bot. Note that if you have just changed + * your nick, this method will still return the old nick until confirmation + * of the nick change is received from the server. + * <p> + * The nick returned by this method is maintained only by the PircBot + * class and is guaranteed to be correct in the context of the IRC server. + * + * @since PircBot 1.0.0 + * + * @return The current nick of the bot. + */ + public String getNick() { + return _nick; + } + + + /** + * Gets the internal login of the PircBot. + * + * @return The login of the PircBot. + */ + public final String getLogin() { + return _login; + } + + + /** + * Gets the internal version of the PircBot. + * + * @return The version of the PircBot. + */ + public final String getVersion() { + return _version; + } + + + /** + * Gets the internal finger message of the PircBot. + * + * @return The finger message of the PircBot. + */ + public final String getFinger() { + return _finger; + } + + + /** + * Returns whether or not the PircBot is currently connected to a server. + * The result of this method should only act as a rough guide, + * as the result may not be valid by the time you act upon it. + * + * @return True if and only if the PircBot is currently connected to a server. + */ + public final synchronized boolean isConnected() { + return _inputThread != null && _inputThread.isConnected(); + } + + + /** + * Sets the number of milliseconds to delay between consecutive + * messages when there are multiple messages waiting in the + * outgoing message queue. This has a default value of 1000ms. + * It is a good idea to stick to this default value, as it will + * prevent your bot from spamming servers and facing the subsequent + * wrath! However, if you do need to change this delay value (<b>not + * recommended</b>), then this is the method to use. + * + * @param delay The number of milliseconds between each outgoing message. + * + */ + public final void setMessageDelay(long delay) { + if (delay < 0) { + throw new IllegalArgumentException("Cannot have a negative time."); + } + _messageDelay = delay; + } + + + /** + * Returns the number of milliseconds that will be used to separate + * consecutive messages to the server from the outgoing message queue. + * + * @return Number of milliseconds. + */ + public final long getMessageDelay() { + return _messageDelay; + } + + + /** + * Gets the maximum length of any line that is sent via the IRC protocol. + * The IRC RFC specifies that line lengths, including the trailing \r\n + * must not exceed 512 bytes. Hence, there is currently no option to + * change this value in PircBot. All lines greater than this length + * will be truncated before being sent to the IRC server. + * + * @return The maximum line length (currently fixed at 512) + */ + public final int getMaxLineLength() { + return InputThread.MAX_LINE_LENGTH; + } + + + /** + * Gets the number of lines currently waiting in the outgoing message Queue. + * If this returns 0, then the Queue is empty and any new message is likely + * to be sent to the IRC server immediately. + * + * @since PircBot 0.9.9 + * + * @return The number of lines in the outgoing message Queue. + */ + public final int getOutgoingQueueSize() { + return _outQueue.size(); + } + + + /** + * Returns the name of the last IRC server the PircBot tried to connect to. + * This does not imply that the connection attempt to the server was + * successful (we suggest you look at the onConnect method). + * A value of null is returned if the PircBot has never tried to connect + * to a server. + * + * @return The name of the last machine we tried to connect to. Returns + * null if no connection attempts have ever been made. + */ + public final String getServer() { + return _server; + } + + + /** + * Returns the port number of the last IRC server that the PircBot tried + * to connect to. + * This does not imply that the connection attempt to the server was + * successful (we suggest you look at the onConnect method). + * A value of -1 is returned if the PircBot has never tried to connect + * to a server. + * + * @since PircBot 0.9.9 + * + * @return The port number of the last IRC server we connected to. + * Returns -1 if no connection attempts have ever been made. + */ + public final int getPort() { + return _port; + } + + + /** + * Returns the last password that we used when connecting to an IRC server. + * This does not imply that the connection attempt to the server was + * successful (we suggest you look at the onConnect method). + * A value of null is returned if the PircBot has never tried to connect + * to a server using a password. + * + * @since PircBot 0.9.9 + * + * @return The last password that we used when connecting to an IRC server. + * Returns null if we have not previously connected using a password. + */ + public final String getPassword() { + return _password; + } + + + /** + * A convenient method that accepts an IP address represented as a + * long and returns an integer array of size 4 representing the same + * IP address. + * + * @since PircBot 0.9.4 + * + * @param address the long value representing the IP address. + * + * @return An int[] of size 4. + */ + public int[] longToIp(long address) { + int[] ip = new int[4]; + for (int i = 3; i >= 0; i--) { + ip[i] = (int) (address % 256); + address = address / 256; + } + return ip; + } + + + /** + * A convenient method that accepts an IP address represented by a byte[] + * of size 4 and returns this as a long representation of the same IP + * address. + * + * @since PircBot 0.9.4 + * + * @param address the byte[] of size 4 representing the IP address. + * + * @return a long representation of the IP address. + */ + public long ipToLong(byte[] address) { + if (address.length != 4) { + throw new IllegalArgumentException("byte array must be of length 4"); + } + long ipNum = 0; + long multiplier = 1; + for (int i = 3; i >= 0; i--) { + int byteVal = (address[i] + 256) % 256; + ipNum += byteVal*multiplier; + multiplier *= 256; + } + return ipNum; + } + + + /** + * Sets the encoding charset to be used when sending or receiving lines + * from the IRC server. If set to null, then the platform's default + * charset is used. You should only use this method if you are + * trying to send text to an IRC server in a different charset, e.g. + * "GB2312" for Chinese encoding. If a PircBot is currently connected + * to a server, then it must reconnect before this change takes effect. + * + * @since PircBot 1.0.4 + * + * @param charset The new encoding charset to be used by PircBot. + * + * @throws UnsupportedEncodingException If the named charset is not + * supported. + */ + public void setEncoding(String charset) throws UnsupportedEncodingException { + // Just try to see if the charset is supported first... + "".getBytes(charset); + + _charset = charset; + } + + + /** + * Returns the encoding used to send and receive lines from + * the IRC server, or null if not set. Use the setEncoding + * method to change the encoding charset. + * + * @since PircBot 1.0.4 + * + * @return The encoding used to send outgoing messages, or + * null if not set. + */ + public String getEncoding() { + return _charset; + } + + /** + * Returns the InetAddress used by the PircBot. + * This can be used to find the I.P. address from which the PircBot is + * connected to a server. + * + * @since PircBot 1.4.4 + * + * @return The current local InetAddress, or null if never connected. + */ + public InetAddress getInetAddress() { + return _inetAddress; + } + + + /** + * Sets the InetAddress to be used when sending DCC chat or file transfers. + * This can be very useful when you are running a bot on a machine which + * is behind a firewall and you need to tell receiving clients to connect + * to a NAT/router, which then forwards the connection. + * + * @since PircBot 1.4.4 + * + * @param dccInetAddress The new InetAddress, or null to use the default. + */ + public void setDccInetAddress(InetAddress dccInetAddress) { + _dccInetAddress = dccInetAddress; + } + + + /** + * Returns the InetAddress used when sending DCC chat or file transfers. + * If this is null, the default InetAddress will be used. + * + * @since PircBot 1.4.4 + * + * @return The current DCC InetAddress, or null if left as default. + */ + public InetAddress getDccInetAddress() { + return _dccInetAddress; + } + + + /** + * Returns the set of port numbers to be used when sending a DCC chat + * or file transfer. This is useful when you are behind a firewall and + * need to set up port forwarding. The array of port numbers is traversed + * in sequence until a free port is found to listen on. A DCC tranfer will + * fail if all ports are already in use. + * If set to null, <i>any</i> free port number will be used. + * + * @since PircBot 1.4.4 + * + * @return An array of port numbers that PircBot can use to send DCC + * transfers, or null if any port is allowed. + */ + public int[] getDccPorts() { + if (_dccPorts == null || _dccPorts.length == 0) { + return null; + } + // Clone the array to prevent external modification. + return (int[]) _dccPorts.clone(); + } + + + /** + * Sets the choice of port numbers that can be used when sending a DCC chat + * or file transfer. This is useful when you are behind a firewall and + * need to set up port forwarding. The array of port numbers is traversed + * in sequence until a free port is found to listen on. A DCC tranfer will + * fail if all ports are already in use. + * If set to null, <i>any</i> free port number will be used. + * + * @since PircBot 1.4.4 + * + * @param ports The set of port numbers that PircBot may use for DCC + * transfers, or null to let it use any free port (default). + * + */ + public void setDccPorts(int[] ports) { + if (ports == null || ports.length == 0) { + _dccPorts = null; + } + else { + // Clone the array to prevent external modification. + _dccPorts = (int[]) ports.clone(); + } + } + + + /** + * Returns true if and only if the object being compared is the exact + * same instance as this PircBot. This may be useful if you are writing + * a multiple server IRC bot that uses more than one instance of PircBot. + * + * @since PircBot 0.9.9 + * + * @return true if and only if Object o is a PircBot and equal to this. + */ + public boolean equals(Object o) { + // This probably has the same effect as Object.equals, but that may change... + if (o instanceof PircBot) { + PircBot other = (PircBot) o; + return other == this; + } + return false; + } + + + /** + * Returns the hashCode of this PircBot. This method can be called by hashed + * collection classes and is useful for managing multiple instances of + * PircBots in such collections. + * + * @since PircBot 0.9.9 + * + * @return the hash code for this instance of PircBot. + */ + public int hashCode() { + return super.hashCode(); + } + + + /** + * Returns a String representation of this object. + * You may find this useful for debugging purposes, particularly + * if you are using more than one PircBot instance to achieve + * multiple server connectivity. The format of + * this String may change between different versions of PircBot + * but is currently something of the form + * <code> + * Version{PircBot x.y.z Java IRC Bot - www.jibble.org} + * Connected{true} + * Server{irc.dal.net} + * Port{6667} + * Password{} + * </code> + * + * @since PircBot 0.9.10 + * + * @return a String representation of this object. + */ + public String toString() { + return "Version{" + _version + "}" + + " Connected{" + isConnected() + "}" + + " Server{" + _server + "}" + + " Port{" + _port + "}" + + " Password{" + _password + "}"; + } + + + /** + * Returns an array of all users in the specified channel. + * <p> + * There are some important things to note about this method:- + * <ul> + * <li>This method may not return a full list of users if you call it + * before the complete nick list has arrived from the IRC server. + * </li> + * <li>If you wish to find out which users are in a channel as soon + * as you join it, then you should override the onUserList method + * instead of calling this method, as the onUserList method is only + * called as soon as the full user list has been received. + * </li> + * <li>This method will return immediately, as it does not require any + * interaction with the IRC server. + * </li> + * <li>The bot must be in a channel to be able to know which users are + * in it. + * </li> + * </ul> + * + * @since PircBot 1.0.0 + * + * @param channel The name of the channel to list. + * + * @return An array of User objects. This array is empty if we are not + * in the channel. + * + * @see #onUserList(String,User[]) onUserList + */ + public final User[] getUsers(String channel) { + channel = channel.toLowerCase(); + User[] userArray = new User[0]; + synchronized (_channels) { + Hashtable users = (Hashtable) _channels.get(channel); + if (users != null) { + userArray = new User[users.size()]; + Enumeration enumeration = users.elements(); + for (int i = 0; i < userArray.length; i++) { + User user = (User) enumeration.nextElement(); + userArray[i] = user; + } + } + } + return userArray; + } + + + /** + * Returns an array of all channels that we are in. Note that if you + * call this method immediately after joining a new channel, the new + * channel may not appear in this array as it is not possible to tell + * if the join was successful until a response is received from the + * IRC server. + * + * @since PircBot 1.0.0 + * + * @return A String array containing the names of all channels that we + * are in. + */ + public final String[] getChannels() { + String[] channels = new String[0]; + synchronized (_channels) { + channels = new String[_channels.size()]; + Enumeration enumeration = _channels.keys(); + for (int i = 0; i < channels.length; i++) { + channels[i] = (String) enumeration.nextElement(); + } + } + return channels; + } + + + /** + * Disposes of all thread resources used by this PircBot. This may be + * useful when writing bots or clients that use multiple servers (and + * therefore multiple PircBot instances) or when integrating a PircBot + * with an existing program. + * <p> + * Each PircBot runs its own threads for dispatching messages from its + * outgoing message queue and receiving messages from the server. + * Calling dispose() ensures that these threads are + * stopped, thus freeing up system resources and allowing the PircBot + * object to be garbage collected if there are no other references to + * it. + * <p> + * Once a PircBot object has been disposed, it should not be used again. + * Attempting to use a PircBot that has been disposed may result in + * unpredictable behaviour. + * + * @since 1.2.2 + */ + public synchronized void dispose() { + //System.out.println("disposing..."); + _outputThread.interrupt(); + _inputThread.dispose(); + } + + + /** + * Add a user to the specified channel in our memory. + * Overwrite the existing entry if it exists. + */ + private final void addUser(String channel, User user) { + channel = channel.toLowerCase(); + synchronized (_channels) { + Hashtable users = (Hashtable) _channels.get(channel); + if (users == null) { + users = new Hashtable(); + _channels.put(channel, users); + } + users.put(user, user); + } + } + + + /** + * Remove a user from the specified channel in our memory. + */ + private final User removeUser(String channel, String nick) { + channel = channel.toLowerCase(); + User user = new User("", nick); + synchronized (_channels) { + Hashtable users = (Hashtable) _channels.get(channel); + if (users != null) { + return (User) users.remove(user); + } + } + return null; + } + + + /** + * Remove a user from all channels in our memory. + */ + private final void removeUser(String nick) { + synchronized (_channels) { + Enumeration enumeration = _channels.keys(); + while (enumeration.hasMoreElements()) { + String channel = (String) enumeration.nextElement(); + this.removeUser(channel, nick); + } + } + } + + + /** + * Rename a user if they appear in any of the channels we know about. + */ + private final void renameUser(String oldNick, String newNick) { + synchronized (_channels) { + Enumeration enumeration = _channels.keys(); + while (enumeration.hasMoreElements()) { + String channel = (String) enumeration.nextElement(); + User user = this.removeUser(channel, oldNick); + if (user != null) { + user = new User(user.getPrefix(), newNick); + this.addUser(channel, user); + } + } + } + } + + + /** + * Removes an entire channel from our memory of users. + */ + private final void removeChannel(String channel) { + channel = channel.toLowerCase(); + synchronized (_channels) { + _channels.remove(channel); + } + } + + + /** + * Removes all channels from our memory of users. + */ + private final void removeAllChannels() { + synchronized(_channels) { + _channels = new Hashtable(); + } + } + + + private final void updateUser(String channel, int userMode, String nick) { + channel = channel.toLowerCase(); + synchronized (_channels) { + Hashtable users = (Hashtable) _channels.get(channel); + User newUser = null; + if (users != null) { + Enumeration enumeration = users.elements(); + while(enumeration.hasMoreElements()) { + User userObj = (User) enumeration.nextElement(); + if (userObj.getNick().equalsIgnoreCase(nick)) { + if (userMode == OP_ADD) { + if (userObj.hasVoice()) { + newUser = new User("@+", nick); + } + else { + newUser = new User("@", nick); + } + } + else if (userMode == OP_REMOVE) { + if(userObj.hasVoice()) { + newUser = new User("+", nick); + } + else { + newUser = new User("", nick); + } + } + else if (userMode == VOICE_ADD) { + if(userObj.isOp()) { + newUser = new User("@+", nick); + } + else { + newUser = new User("+", nick); + } + } + else if (userMode == VOICE_REMOVE) { + if(userObj.isOp()) { + newUser = new User("@", nick); + } + else { + newUser = new User("", nick); + } + } + } + } + } + if (newUser != null) { + users.put(newUser, newUser); + } + else { + // just in case ... + newUser = new User("", nick); + users.put(newUser, newUser); + } + } + } + + + // Connection stuff. + private InputThread _inputThread = null; + private OutputThread _outputThread = null; + private String _charset = null; + private InetAddress _inetAddress = null; + + // Details about the last server that we connected to. + private String _server = null; + private int _port = -1; + private String _password = null; + + // Outgoing message stuff. + private Queue _outQueue = new Queue(); + private long _messageDelay = 1000; + + // A Hashtable of channels that points to a selfreferential Hashtable of + // User objects (used to remember which users are in which channels). + private Hashtable _channels = new Hashtable(); + + // A Hashtable to temporarily store channel topics when we join them + // until we find out who set that topic. + private Hashtable _topics = new Hashtable(); + + private int[] _dccPorts = null; + private InetAddress _dccInetAddress = null; + + // Default settings for the PircBot. + private boolean _autoNickChange = false; + private boolean _verbose = false; + private String _name = "PircBot"; + private String _nick = _name; + private String _login = "PircBot"; + private String _version = "PircBot " + VERSION + " Java IRC Bot - www.jibble.org"; + private String _finger = "You ought to be arrested for fingering a bot!"; + + private String _channelPrefixes = "#&+!"; +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/Queue.java b/EssentialsUpdate/src/org/jibble/pircbot/Queue.java new file mode 100755 index 000000000..fd8d47781 --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/Queue.java @@ -0,0 +1,146 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + + +package org.jibble.pircbot; + +import java.util.Vector; + +/** + * Queue is a definition of a data structure that may + * act as a queue - that is, data can be added to one end of the + * queue and data can be requested from the head end of the queue. + * This class is thread safe for multiple producers and a single + * consumer. The next() method will block until there is data in + * the queue. + * + * This has now been modified so that it is compatible with + * the earlier JDK1.1 in order to be suitable for running on + * mobile appliances. This means replacing the LinkedList with + * a Vector, which is hardly ideal, but this Queue is typically + * only polled every second before dispatching messages. + * + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public class Queue { + + + /** + * Constructs a Queue object of unlimited size. + */ + public Queue() { + + } + + + /** + * Adds an Object to the end of the Queue. + * + * @param o The Object to be added to the Queue. + */ + public void add(Object o) { + synchronized(_queue) { + _queue.addElement(o); + _queue.notify(); + } + } + + + /** + * Adds an Object to the front of the Queue. + * + * @param o The Object to be added to the Queue. + */ + public void addFront(Object o) { + synchronized(_queue) { + _queue.insertElementAt(o, 0); + _queue.notify(); + } + } + + + /** + * Returns the Object at the front of the Queue. This + * Object is then removed from the Queue. If the Queue + * is empty, then this method shall block until there + * is an Object in the Queue to return. + * + * @return The next item from the front of the queue. + */ + public Object next() { + + Object o = null; + + // Block if the Queue is empty. + synchronized(_queue) { + if (_queue.size() == 0) { + try { + _queue.wait(); + } + catch (InterruptedException e) { + return null; + } + } + + // Return the Object. + try { + o = _queue.firstElement(); + _queue.removeElementAt(0); + } + catch (ArrayIndexOutOfBoundsException e) { + throw new InternalError("Race hazard in Queue object."); + } + } + + return o; + } + + + /** + * Returns true if the Queue is not empty. If another + * Thread empties the Queue before <b>next()</b> is + * called, then the call to <b>next()</b> shall block + * until the Queue has been populated again. + * + * @return True only if the Queue not empty. + */ + public boolean hasNext() { + return (this.size() != 0); + } + + + /** + * Clears the contents of the Queue. + */ + public void clear() { + synchronized(_queue) { + _queue.removeAllElements(); + } + } + + + /** + * Returns the size of the Queue. + * + * @return The current size of the queue. + */ + public int size() { + return _queue.size(); + } + + + private Vector _queue = new Vector(); + +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/ReplyConstants.java b/EssentialsUpdate/src/org/jibble/pircbot/ReplyConstants.java new file mode 100755 index 000000000..2d8e696d5 --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/ReplyConstants.java @@ -0,0 +1,176 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + + +package org.jibble.pircbot; + +/** + * This interface contains the values of all numeric replies specified + * in section 6 of RFC 1459. Refer to RFC 1459 for further information. + * <p> + * If you override the onServerResponse method in the PircBot class, + * you may find these constants useful when comparing the numeric + * value of a given code. + * + * @since 1.0.0 + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public interface ReplyConstants { + + + // Error Replies. + public static final int ERR_NOSUCHNICK = 401; + public static final int ERR_NOSUCHSERVER = 402; + public static final int ERR_NOSUCHCHANNEL = 403; + public static final int ERR_CANNOTSENDTOCHAN = 404; + public static final int ERR_TOOMANYCHANNELS = 405; + public static final int ERR_WASNOSUCHNICK = 406; + public static final int ERR_TOOMANYTARGETS = 407; + public static final int ERR_NOORIGIN = 409; + public static final int ERR_NORECIPIENT = 411; + public static final int ERR_NOTEXTTOSEND = 412; + public static final int ERR_NOTOPLEVEL = 413; + public static final int ERR_WILDTOPLEVEL = 414; + public static final int ERR_UNKNOWNCOMMAND = 421; + public static final int ERR_NOMOTD = 422; + public static final int ERR_NOADMININFO = 423; + public static final int ERR_FILEERROR = 424; + public static final int ERR_NONICKNAMEGIVEN = 431; + public static final int ERR_ERRONEUSNICKNAME = 432; + public static final int ERR_NICKNAMEINUSE = 433; + public static final int ERR_NICKCOLLISION = 436; + public static final int ERR_USERNOTINCHANNEL = 441; + public static final int ERR_NOTONCHANNEL = 442; + public static final int ERR_USERONCHANNEL = 443; + public static final int ERR_NOLOGIN = 444; + public static final int ERR_SUMMONDISABLED = 445; + public static final int ERR_USERSDISABLED = 446; + public static final int ERR_NOTREGISTERED = 451; + public static final int ERR_NEEDMOREPARAMS = 461; + public static final int ERR_ALREADYREGISTRED = 462; + public static final int ERR_NOPERMFORHOST = 463; + public static final int ERR_PASSWDMISMATCH = 464; + public static final int ERR_YOUREBANNEDCREEP = 465; + public static final int ERR_KEYSET = 467; + public static final int ERR_CHANNELISFULL = 471; + public static final int ERR_UNKNOWNMODE = 472; + public static final int ERR_INVITEONLYCHAN = 473; + public static final int ERR_BANNEDFROMCHAN = 474; + public static final int ERR_BADCHANNELKEY = 475; + public static final int ERR_NOPRIVILEGES = 481; + public static final int ERR_CHANOPRIVSNEEDED = 482; + public static final int ERR_CANTKILLSERVER = 483; + public static final int ERR_NOOPERHOST = 491; + public static final int ERR_UMODEUNKNOWNFLAG = 501; + public static final int ERR_USERSDONTMATCH = 502; + + + // Command Responses. + public static final int RPL_TRACELINK = 200; + public static final int RPL_TRACECONNECTING = 201; + public static final int RPL_TRACEHANDSHAKE = 202; + public static final int RPL_TRACEUNKNOWN = 203; + public static final int RPL_TRACEOPERATOR = 204; + public static final int RPL_TRACEUSER = 205; + public static final int RPL_TRACESERVER = 206; + public static final int RPL_TRACENEWTYPE = 208; + public static final int RPL_STATSLINKINFO = 211; + public static final int RPL_STATSCOMMANDS = 212; + public static final int RPL_STATSCLINE = 213; + public static final int RPL_STATSNLINE = 214; + public static final int RPL_STATSILINE = 215; + public static final int RPL_STATSKLINE = 216; + public static final int RPL_STATSYLINE = 218; + public static final int RPL_ENDOFSTATS = 219; + public static final int RPL_UMODEIS = 221; + public static final int RPL_STATSLLINE = 241; + public static final int RPL_STATSUPTIME = 242; + public static final int RPL_STATSOLINE = 243; + public static final int RPL_STATSHLINE = 244; + public static final int RPL_LUSERCLIENT = 251; + public static final int RPL_LUSEROP = 252; + public static final int RPL_LUSERUNKNOWN = 253; + public static final int RPL_LUSERCHANNELS = 254; + public static final int RPL_LUSERME = 255; + public static final int RPL_ADMINME = 256; + public static final int RPL_ADMINLOC1 = 257; + public static final int RPL_ADMINLOC2 = 258; + public static final int RPL_ADMINEMAIL = 259; + public static final int RPL_TRACELOG = 261; + public static final int RPL_NONE = 300; + public static final int RPL_AWAY = 301; + public static final int RPL_USERHOST = 302; + public static final int RPL_ISON = 303; + public static final int RPL_UNAWAY = 305; + public static final int RPL_NOWAWAY = 306; + public static final int RPL_WHOISUSER = 311; + public static final int RPL_WHOISSERVER = 312; + public static final int RPL_WHOISOPERATOR = 313; + public static final int RPL_WHOWASUSER = 314; + public static final int RPL_ENDOFWHO = 315; + public static final int RPL_WHOISIDLE = 317; + public static final int RPL_ENDOFWHOIS = 318; + public static final int RPL_WHOISCHANNELS = 319; + public static final int RPL_LISTSTART = 321; + public static final int RPL_LIST = 322; + public static final int RPL_LISTEND = 323; + public static final int RPL_CHANNELMODEIS = 324; + public static final int RPL_NOTOPIC = 331; + public static final int RPL_TOPIC = 332; + public static final int RPL_TOPICINFO = 333; + public static final int RPL_INVITING = 341; + public static final int RPL_SUMMONING = 342; + public static final int RPL_VERSION = 351; + public static final int RPL_WHOREPLY = 352; + public static final int RPL_NAMREPLY = 353; + public static final int RPL_LINKS = 364; + public static final int RPL_ENDOFLINKS = 365; + public static final int RPL_ENDOFNAMES = 366; + public static final int RPL_BANLIST = 367; + public static final int RPL_ENDOFBANLIST = 368; + public static final int RPL_ENDOFWHOWAS = 369; + public static final int RPL_INFO = 371; + public static final int RPL_MOTD = 372; + public static final int RPL_ENDOFINFO = 374; + public static final int RPL_MOTDSTART = 375; + public static final int RPL_ENDOFMOTD = 376; + public static final int RPL_YOUREOPER = 381; + public static final int RPL_REHASHING = 382; + public static final int RPL_TIME = 391; + public static final int RPL_USERSSTART = 392; + public static final int RPL_USERS = 393; + public static final int RPL_ENDOFUSERS = 394; + public static final int RPL_NOUSERS = 395; + + + // Reserved Numerics. + public static final int RPL_TRACECLASS = 209; + public static final int RPL_STATSQLINE = 217; + public static final int RPL_SERVICEINFO = 231; + public static final int RPL_ENDOFSERVICES = 232; + public static final int RPL_SERVICE = 233; + public static final int RPL_SERVLIST = 234; + public static final int RPL_SERVLISTEND = 235; + public static final int RPL_WHOISCHANOP = 316; + public static final int RPL_KILLDONE = 361; + public static final int RPL_CLOSING = 362; + public static final int RPL_CLOSEEND = 363; + public static final int RPL_INFOSTART = 373; + public static final int RPL_MYPORTIS = 384; + public static final int ERR_YOUWILLBEBANNED = 466; + public static final int ERR_BADCHANMASK = 476; + public static final int ERR_NOSERVICEHOST = 492; + +} diff --git a/EssentialsUpdate/src/org/jibble/pircbot/User.java b/EssentialsUpdate/src/org/jibble/pircbot/User.java new file mode 100755 index 000000000..cb2bfa051 --- /dev/null +++ b/EssentialsUpdate/src/org/jibble/pircbot/User.java @@ -0,0 +1,161 @@ +/* +Copyright Paul James Mutton, 2001-2009, http://www.jibble.org/ + +This file is part of PircBot. + +This software is dual-licensed, allowing you to choose between the GNU +General Public License (GPL) and the www.jibble.org Commercial License. +Since the GPL may be too restrictive for use in a proprietary application, +a commercial license is also provided. Full license information can be +found at http://www.jibble.org/licenses/ + +*/ + +package org.jibble.pircbot; + +/** + * This class is used to represent a user on an IRC server. + * Instances of this class are returned by the getUsers method + * in the PircBot class. + * <p> + * Note that this class no longer implements the Comparable interface + * for Java 1.1 compatibility reasons. + * + * @since 1.0.0 + * @author Paul James Mutton, + * <a href="http://www.jibble.org/">http://www.jibble.org/</a> + * @version 1.5.0 (Build time: Mon Dec 14 20:07:17 2009) + */ +public class User { + + + /** + * Constructs a User object with a known prefix and nick. + * + * @param prefix The status of the user, for example, "@". + * @param nick The nick of the user. + */ + User(String prefix, String nick) { + _prefix = prefix; + _nick = nick; + _lowerNick = nick.toLowerCase(); + } + + + /** + * Returns the prefix of the user. If the User object has been obtained + * from a list of users in a channel, then this will reflect the user's + * status in that channel. + * + * @return The prefix of the user. If there is no prefix, then an empty + * String is returned. + */ + public String getPrefix() { + return _prefix; + } + + + /** + * Returns whether or not the user represented by this object is an + * operator. If the User object has been obtained from a list of users + * in a channel, then this will reflect the user's operator status in + * that channel. + * + * @return true if the user is an operator in the channel. + */ + public boolean isOp() { + return _prefix.indexOf('@') >= 0; + } + + + /** + * Returns whether or not the user represented by this object has + * voice. If the User object has been obtained from a list of users + * in a channel, then this will reflect the user's voice status in + * that channel. + * + * @return true if the user has voice in the channel. + */ + public boolean hasVoice() { + return _prefix.indexOf('+') >= 0; + } + + + /** + * Returns the nick of the user. + * + * @return The user's nick. + */ + public String getNick() { + return _nick; + } + + + /** + * Returns the nick of the user complete with their prefix if they + * have one, e.g. "@Dave". + * + * @return The user's prefix and nick. + */ + public String toString() { + return this.getPrefix() + this.getNick(); + } + + + /** + * Returns true if the nick represented by this User object is the same + * as the argument. A case insensitive comparison is made. + * + * @return true if the nicks are identical (case insensitive). + */ + public boolean equals(String nick) { + return nick.toLowerCase().equals(_lowerNick); + } + + + /** + * Returns true if the nick represented by this User object is the same + * as the nick of the User object given as an argument. + * A case insensitive comparison is made. + * + * @return true if o is a User object with a matching lowercase nick. + */ + public boolean equals(Object o) { + if (o instanceof User) { + User other = (User) o; + return other._lowerNick.equals(_lowerNick); + } + return false; + } + + + /** + * Returns the hash code of this User object. + * + * @return the hash code of the User object. + */ + public int hashCode() { + return _lowerNick.hashCode(); + } + + + /** + * Returns the result of calling the compareTo method on lowercased + * nicks. This is useful for sorting lists of User objects. + * + * @return the result of calling compareTo on lowercased nicks. + */ + public int compareTo(Object o) { + if (o instanceof User) { + User other = (User) o; + return other._lowerNick.compareTo(_lowerNick); + } + return -1; + } + + + private String _prefix; + private String _nick; + private String _lowerNick; + +} diff --git a/EssentialsUpdate/src/plugin.yml b/EssentialsUpdate/src/plugin.yml new file mode 100644 index 000000000..b8dbe8e25 --- /dev/null +++ b/EssentialsUpdate/src/plugin.yml @@ -0,0 +1,21 @@ +# This determines the command prefix when there are conflicts (/name:home, /name:help, etc.) +name: EssentialsUpdate +main: com.earth2me.essentials.update.EssentialsUpdate +# Note to developers: This next line cannot change, or the automatic versioning system will break. +version: TeamCity +description: This plugin allows to install or update all Essentials plugins +authors: [snowleo] +commands: + essentialsupdate: + description: Install or update the Essentials plugins. + usage: /<command> + essentialshelp: + description: Get help from the Essentials support chat. + usage: /<command> +permissions: + essentials.update: + description: Allows you to update Essentials + default: op + essentials.helpchat: + description: Allows you to join Essentials help chat + default: op
\ No newline at end of file diff --git a/EssentialsUpdate/test/com/earth2me/essentials/update/UploadTest.java b/EssentialsUpdate/test/com/earth2me/essentials/update/UploadTest.java new file mode 100644 index 000000000..a51f03bd7 --- /dev/null +++ b/EssentialsUpdate/test/com/earth2me/essentials/update/UploadTest.java @@ -0,0 +1,27 @@ +package com.earth2me.essentials.update; + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import junit.framework.TestCase; +import org.junit.Test; + + +public class UploadTest extends TestCase +{ + @Test + public void testPastieUpload() + { + try + { + final PastieUpload pastie = new PastieUpload(); + assertNotNull(pastie); + //final String url = pastie.send("test"); + //System.out.println(url); + } + catch (IOException ex) + { + Logger.getLogger(UploadTest.class.getName()).log(Level.SEVERE, null, ex); + } + } +} diff --git a/EssentialsUpdate/test/com/earth2me/essentials/update/VersionTest.java b/EssentialsUpdate/test/com/earth2me/essentials/update/VersionTest.java new file mode 100644 index 000000000..901a8f9dc --- /dev/null +++ b/EssentialsUpdate/test/com/earth2me/essentials/update/VersionTest.java @@ -0,0 +1,87 @@ +package com.earth2me.essentials.update; + +import com.earth2me.essentials.update.Version.Type; +import java.util.TreeSet; +import junit.framework.TestCase; +import org.junit.Test; +import static org.junit.Assert.*; + + +public class VersionTest extends TestCase +{ + @Test + public void testStable() + { + final Version instance = new Version("1.2.3"); + assertEquals("Testing Major", 1, instance.getMajor()); + assertEquals("Testing Minor", 2, instance.getMinor()); + assertEquals("Testing Build", 3, instance.getBuild()); + assertEquals("Testing Type", Type.STABLE, instance.getType()); + } + + @Test + public void testDev() + { + final Version instance = new Version("Dev2.3.4"); + assertEquals("Testing Major", 2, instance.getMajor()); + assertEquals("Testing Minor", 3, instance.getMinor()); + assertEquals("Testing Build", 4, instance.getBuild()); + assertEquals("Testing Type", Type.DEVELOPER, instance.getType()); + } + + @Test + public void testTeamCity() + { + final Version instance = new Version("Teamcity"); + assertEquals("Testing Type", Type.DEVELOPER, instance.getType()); + } + + @Test + public void testPre() + { + final Version instance = new Version("Pre5.7.400.2"); + assertEquals("Testing Major", 5, instance.getMajor()); + assertEquals("Testing Minor", 7, instance.getMinor()); + assertEquals("Testing Build", 400, instance.getBuild()); + assertEquals("Testing Type", Type.PREVIEW, instance.getType()); + } + + @Test + public void testCompareTo() + { + Version a = new Version("1.1.1"); + Version b = new Version("Dev1.1.2"); + Version c = new Version("1.1.2"); + Version d = new Version("1.2.0"); + Version e = new Version("2.0.0"); + Version f = new Version("Pre1.1.1.1"); + Version g = new Version("Dev1.2.2"); + assertTrue("Testing dev", a.compareTo(b) < 0); + assertTrue("Testing dev", b.compareTo(a) > 0); + assertTrue("Testing build", a.compareTo(c) < 0); + assertTrue("Testing build", c.compareTo(a) > 0); + assertTrue("Testing minor", a.compareTo(d) < 0); + assertTrue("Testing minor", d.compareTo(a) > 0); + assertTrue("Testing major", a.compareTo(e) < 0); + assertTrue("Testing major", e.compareTo(a) > 0); + assertTrue("Testing pre", f.compareTo(a) < 0); + assertTrue("Testing pre", a.compareTo(f) > 0); + assertTrue("Testing dev vs dev", b.compareTo(g) < 0); + assertTrue("Testing dev vs dev", g.compareTo(b) > 0); + final TreeSet<Version> set = new TreeSet<Version>(); + set.add(a); + set.add(b); + set.add(c); + set.add(d); + set.add(e); + set.add(f); + set.add(g); + assertEquals("Testing sorting", f, set.pollFirst()); + assertEquals("Testing sorting", a, set.pollFirst()); + assertEquals("Testing sorting", c, set.pollFirst()); + assertEquals("Testing sorting", d, set.pollFirst()); + assertEquals("Testing sorting", e, set.pollFirst()); + assertEquals("Testing sorting", b, set.pollFirst()); + assertEquals("Testing sorting", g, set.pollFirst()); + } +} diff --git a/WebPush/apikey.php b/WebPush/apikey.php new file mode 100644 index 000000000..59f2bf3ac --- /dev/null +++ b/WebPush/apikey.php @@ -0,0 +1,5 @@ +<?php + +$params['api-key'] = "c73c331c7e44c156c852f7d08de3f22bb7a6e948"; + +?> diff --git a/WebPush/index.php b/WebPush/index.php new file mode 100644 index 000000000..ab018bf27 --- /dev/null +++ b/WebPush/index.php @@ -0,0 +1,52 @@ +<?php + +//We want to be able to continue if the client aborts. +ignore_user_abort(); +set_time_limit(0); +error_reporting(E_ALL); +ini_set('display_errors', 'Off'); +ini_set('error_log', 'errors.log'); + +//Abort the browser so it doesn't hang while we do the uploading. +ob_end_clean(); +header("Connection: close"); +ob_start(); +header("Content-Length: 0"); +ob_end_flush(); +flush(); + +//Lets get to work! +include('upload.php'); + +$build = $_GET['buildid']; +$branch = $_GET['branch']; +$version = $_GET['version']; + +if ($build == "" || $branch == "" || $version == "") +{ + die(); +} + +//Don't upload dev builds atm. +if ($branch == "bt2") +{ + die(); +} + +sleep(60); + +$changes = getChanges($build, $branch); + +uploadit($build, $branch, 'Essentials.jar', $version, $changes); +sleep(1); +uploadit($build, $branch, 'EssentialsChat.jar', $version, $changes); +sleep(1); +uploadit($build, $branch, 'EssentialsSpawn.jar', $version, $changes); +sleep(1); +uploadit($build, $branch, 'EssentialsProtect.jar', $version, $changes); +sleep(1); +uploadit($build, $branch, 'EssentialsXMPP.jar', $version, $changes); +sleep(1); +uploadit($build, $branch, 'EssentialsGeoIP.jar', $version, $changes); +?> + diff --git a/WebPush/nbproject/private/private.properties b/WebPush/nbproject/private/private.properties new file mode 100644 index 000000000..8c2a80c6f --- /dev/null +++ b/WebPush/nbproject/private/private.properties @@ -0,0 +1,8 @@ +copy.src.files=false +copy.src.target= +index.file=index.php +remote.connection=localhost-d13e79 +remote.directory=/upload +remote.upload=ON_SAVE +run.as=REMOTE +url=http://ess.khhq.net/upload/ diff --git a/WebPush/nbproject/project.properties b/WebPush/nbproject/project.properties new file mode 100644 index 000000000..6ffde2f50 --- /dev/null +++ b/WebPush/nbproject/project.properties @@ -0,0 +1,7 @@ +include.path=${php.global.include.path} +php.version=PHP_5 +source.encoding=UTF-8 +src.dir=. +tags.asp=false +tags.short=true +web.root=. diff --git a/WebPush/nbproject/project.xml b/WebPush/nbproject/project.xml new file mode 100644 index 000000000..da4214310 --- /dev/null +++ b/WebPush/nbproject/project.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://www.netbeans.org/ns/project/1"> + <type>org.netbeans.modules.php.project</type> + <configuration> + <data xmlns="http://www.netbeans.org/ns/php-project/1"> + <name>Push</name> + </data> + </configuration> +</project> diff --git a/WebPush/simple_html_dom.php b/WebPush/simple_html_dom.php new file mode 100644 index 000000000..3f96f8d95 --- /dev/null +++ b/WebPush/simple_html_dom.php @@ -0,0 +1,1727 @@ +<?php + +/** + * Website: http://sourceforge.net/projects/simplehtmldom/ + * Acknowledge: Jose Solorzano (https://sourceforge.net/projects/php-html/) + * Contributions by: + * Yousuke Kumakura (Attribute filters) + * Vadim Voituk (Negative indexes supports of "find" method) + * Antcs (Constructor with automatically load contents either text or file/url) + * + * all affected sections have comments starting with "PaperG" + * + * Paperg - Added case insensitive testing of the value of the selector. + * Paperg - Added tag_start for the starting index of tags - NOTE: This works but not accurately. + * This tag_start gets counted AFTER \r\n have been crushed out, and after the remove_noice calls so it will not reflect the REAL position of the tag in the source, + * it will almost always be smaller by some amount. + * We use this to determine how far into the file the tag in question is. This "percentage will never be accurate as the $dom->size is the "real" number of bytes the dom was created from. + * but for most purposes, it's a really good estimation. + * Paperg - Added the forceTagsClosed to the dom constructor. Forcing tags closed is great for malformed html, but it CAN lead to parsing errors. + * Allow the user to tell us how much they trust the html. + * Paperg add the text and plaintext to the selectors for the find syntax. plaintext implies text in the innertext of a node. text implies that the tag is a text node. + * This allows for us to find tags based on the text they contain. + * Create find_ancestor_tag to see if a tag is - at any level - inside of another specific tag. + * Paperg: added parse_charset so that we know about the character set of the source document. + * NOTE: If the user's system has a routine called get_last_retrieve_url_contents_content_type availalbe, we will assume it's returning the content-type header from the + * last transfer or curl_exec, and we will parse that and use it in preference to any other method of charset detection. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @author S.C. Chen <me578022@gmail.com> + * @author John Schlick + * @author Rus Carroll + * @version 1.11 ($Rev: 184 $) + * @package PlaceLocalInclude + * @subpackage simple_html_dom + */ +/** + * All of the Defines for the classes below. + * @author S.C. Chen <me578022@gmail.com> + */ +define('HDOM_TYPE_ELEMENT', 1); +define('HDOM_TYPE_COMMENT', 2); +define('HDOM_TYPE_TEXT', 3); +define('HDOM_TYPE_ENDTAG', 4); +define('HDOM_TYPE_ROOT', 5); +define('HDOM_TYPE_UNKNOWN', 6); +define('HDOM_QUOTE_DOUBLE', 0); +define('HDOM_QUOTE_SINGLE', 1); +define('HDOM_QUOTE_NO', 3); +define('HDOM_INFO_BEGIN', 0); +define('HDOM_INFO_END', 1); +define('HDOM_INFO_QUOTE', 2); +define('HDOM_INFO_SPACE', 3); +define('HDOM_INFO_TEXT', 4); +define('HDOM_INFO_INNER', 5); +define('HDOM_INFO_OUTER', 6); +define('HDOM_INFO_ENDSPACE', 7); +define('DEFAULT_TARGET_CHARSET', 'UTF-8'); +define('DEFAULT_BR_TEXT', "\r\n"); + +// helper functions +// ----------------------------------------------------------------------------- +// get html dom from file +// $maxlen is defined in the code as PHP_STREAM_COPY_ALL which is defined as -1. +function file_get_html($url, $use_include_path = false, $context=null, $offset = -1, $maxLen=-1, $lowercase = true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT) +{ + // We DO force the tags to be terminated. + $dom = new simple_html_dom(null, $lowercase, $forceTagsClosed, $target_charset, $defaultBRText); + // For sourceforge users: uncomment the next line and comment the retreive_url_contents line 2 lines down if it is not already done. + $contents = file_get_contents($url, $use_include_path, $context, $offset); + // Paperg - use our own mechanism for getting the contents as we want to control the timeout. +// $contents = retrieve_url_contents($url); + if (empty($contents)) + { + return false; + } + // The second parameter can force the selectors to all be lowercase. + $dom->load($contents, $lowercase, $stripRN); + return $dom; +} + +// get html dom from string +function str_get_html($str, $lowercase=true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT) +{ + $dom = new simple_html_dom(null, $lowercase, $forceTagsClosed, $target_charset, $defaultBRText); + if (empty($str)) + { + $dom->clear(); + return false; + } + $dom->load($str, $lowercase, $stripRN); + return $dom; +} + +// dump html dom tree +function dump_html_tree($node, $show_attr=true, $deep=0) +{ + $node->dump($node); +} + +/** + * simple html dom node + * PaperG - added ability for "find" routine to lowercase the value of the selector. + * PaperG - added $tag_start to track the start position of the tag in the total byte index + * + * @package PlaceLocalInclude + */ +class simple_html_dom_node +{ + public $nodetype = HDOM_TYPE_TEXT; + public $tag = 'text'; + public $attr = array(); + public $children = array(); + public $nodes = array(); + public $parent = null; + public $_ = array(); + public $tag_start = 0; + private $dom = null; + + function __construct($dom) + { + $this->dom = $dom; + $dom->nodes[] = $this; + } + + function __destruct() + { + $this->clear(); + } + + function __toString() + { + return $this->outertext(); + } + + // clean up memory due to php5 circular references memory leak... + function clear() + { + $this->dom = null; + $this->nodes = null; + $this->parent = null; + $this->children = null; + } + + // dump node's tree + function dump($show_attr=true, $deep=0) + { + $lead = str_repeat(' ', $deep); + + echo $lead . $this->tag; + if ($show_attr && count($this->attr) > 0) + { + echo '('; + foreach ($this->attr as $k => $v) + echo "[$k]=>\"" . $this->$k . '", '; + echo ')'; + } + echo "\n"; + + foreach ($this->nodes as $c) + $c->dump($show_attr, $deep + 1); + } + + // Debugging function to dump a single dom node with a bunch of information about it. + function dump_node() + { + echo $this->tag; + if (count($this->attr) > 0) + { + echo '('; + foreach ($this->attr as $k => $v) + { + echo "[$k]=>\"" . $this->$k . '", '; + } + echo ')'; + } + if (count($this->attr) > 0) + { + echo ' $_ ('; + foreach ($this->_ as $k => $v) + { + if (is_array($v)) + { + echo "[$k]=>("; + foreach ($v as $k2 => $v2) + { + echo "[$k2]=>\"" . $v2 . '", '; + } + echo ")"; + } + else + { + echo "[$k]=>\"" . $v . '", '; + } + } + echo ")"; + } + + if (isset($this->text)) + { + echo " text: (" . $this->text . ")"; + } + + echo " children: " . count($this->children); + echo " nodes: " . count($this->nodes); + echo " tag_start: " . $this->tag_start; + echo "\n"; + } + + // returns the parent of node + function parent() + { + return $this->parent; + } + + // returns children of node + function children($idx=-1) + { + if ($idx === -1) + return $this->children; + if (isset($this->children[$idx])) + return $this->children[$idx]; + return null; + } + + // returns the first child of node + function first_child() + { + if (count($this->children) > 0) + return $this->children[0]; + return null; + } + + // returns the last child of node + function last_child() + { + if (($count = count($this->children)) > 0) + return $this->children[$count - 1]; + return null; + } + + // returns the next sibling of node + function next_sibling() + { + if ($this->parent === null) + return null; + $idx = 0; + $count = count($this->parent->children); + while ($idx < $count && $this !== $this->parent->children[$idx]) + ++$idx; + if (++$idx >= $count) + return null; + return $this->parent->children[$idx]; + } + + // returns the previous sibling of node + function prev_sibling() + { + if ($this->parent === null) + return null; + $idx = 0; + $count = count($this->parent->children); + while ($idx < $count && $this !== $this->parent->children[$idx]) + ++$idx; + if (--$idx < 0) + return null; + return $this->parent->children[$idx]; + } + + // function to locate a specific ancestor tag in the path to the root. + function find_ancestor_tag($tag) + { + global $debugObject; + if (is_object($debugObject)) + { + $debugObject->debugLogEntry(1); + } + + // Start by including ourselves in the comparison. + $returnDom = $this; + + while (!is_null($returnDom)) + { + if (is_object($debugObject)) + { + $debugObject->debugLog(2, "Current tag is: " . $returnDom->tag); + } + + if ($returnDom->tag == $tag) + { + break; + } + $returnDom = $returnDom->parent; + } + return $returnDom; + } + + // get dom node's inner html + function innertext() + { + if (isset($this->_[HDOM_INFO_INNER])) + return $this->_[HDOM_INFO_INNER]; + if (isset($this->_[HDOM_INFO_TEXT])) + return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); + + $ret = ''; + foreach ($this->nodes as $n) + $ret .= $n->outertext(); + return $ret; + } + + // get dom node's outer text (with tag) + function outertext() + { + global $debugObject; + if (is_object($debugObject)) + { + $text = ''; + if ($this->tag == 'text') + { + if (!empty($this->text)) + { + $text = " with text: " . $this->text; + } + } + $debugObject->debugLog(1, 'Innertext of tag: ' . $this->tag . $text); + } + + if ($this->tag === 'root') + return $this->innertext(); + + // trigger callback + if ($this->dom && $this->dom->callback !== null) + { + call_user_func_array($this->dom->callback, array($this)); + } + + if (isset($this->_[HDOM_INFO_OUTER])) + return $this->_[HDOM_INFO_OUTER]; + if (isset($this->_[HDOM_INFO_TEXT])) + return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); + + // render begin tag + if ($this->dom && $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]) + { + $ret = $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]->makeup(); + } + else + { + $ret = ""; + } + + // render inner text + if (isset($this->_[HDOM_INFO_INNER])) + { + // If it's a br tag... don't return the HDOM_INNER_INFO that we may or may not have added. + if ($this->tag != "br") + { + $ret .= $this->_[HDOM_INFO_INNER]; + } + } + else + { + if ($this->nodes) + { + foreach ($this->nodes as $n) + { + $ret .= $this->convert_text($n->outertext()); + } + } + } + + // render end tag + if (isset($this->_[HDOM_INFO_END]) && $this->_[HDOM_INFO_END] != 0) + $ret .= '</' . $this->tag . '>'; + return $ret; + } + + // get dom node's plain text + function text() + { + if (isset($this->_[HDOM_INFO_INNER])) + return $this->_[HDOM_INFO_INNER]; + switch ($this->nodetype) + { + case HDOM_TYPE_TEXT: return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); + case HDOM_TYPE_COMMENT: return ''; + case HDOM_TYPE_UNKNOWN: return ''; + } + if (strcasecmp($this->tag, 'script') === 0) + return ''; + if (strcasecmp($this->tag, 'style') === 0) + return ''; + + $ret = ''; + // In rare cases, (always node type 1 or HDOM_TYPE_ELEMENT - observed for some span tags, and some p tags) $this->nodes is set to NULL. + // NOTE: This indicates that there is a problem where it's set to NULL without a clear happening. + // WHY is this happening? + if (!is_null($this->nodes)) + { + foreach ($this->nodes as $n) + { + $ret .= $this->convert_text($n->text()); + } + } + return $ret; + } + + function xmltext() + { + $ret = $this->innertext(); + $ret = str_ireplace('<![CDATA[', '', $ret); + $ret = str_replace(']]>', '', $ret); + return $ret; + } + + // build node's text with tag + function makeup() + { + // text, comment, unknown + if (isset($this->_[HDOM_INFO_TEXT])) + return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); + + $ret = '<' . $this->tag; + $i = -1; + + foreach ($this->attr as $key => $val) + { + ++$i; + + // skip removed attribute + if ($val === null || $val === false) + continue; + + $ret .= $this->_[HDOM_INFO_SPACE][$i][0]; + //no value attr: nowrap, checked selected... + if ($val === true) + $ret .= $key; + else + { + switch ($this->_[HDOM_INFO_QUOTE][$i]) + { + case HDOM_QUOTE_DOUBLE: $quote = '"'; + break; + case HDOM_QUOTE_SINGLE: $quote = '\''; + break; + default: $quote = ''; + } + $ret .= $key . $this->_[HDOM_INFO_SPACE][$i][1] . '=' . $this->_[HDOM_INFO_SPACE][$i][2] . $quote . $val . $quote; + } + } + $ret = $this->dom->restore_noise($ret); + return $ret . $this->_[HDOM_INFO_ENDSPACE] . '>'; + } + + // find elements by css selector + //PaperG - added ability for find to lowercase the value of the selector. + function find($selector, $idx=null, $lowercase=false) + { + $selectors = $this->parse_selector($selector); + if (($count = count($selectors)) === 0) + return array(); + $found_keys = array(); + + // find each selector + for ($c = 0; $c < $count; ++$c) + { + // The change on the below line was documented on the sourceforge code tracker id 2788009 + // used to be: if (($levle=count($selectors[0]))===0) return array(); + if (($levle = count($selectors[$c])) === 0) + return array(); + if (!isset($this->_[HDOM_INFO_BEGIN])) + return array(); + + $head = array($this->_[HDOM_INFO_BEGIN] => 1); + + // handle descendant selectors, no recursive! + for ($l = 0; $l < $levle; ++$l) + { + $ret = array(); + foreach ($head as $k => $v) + { + $n = ($k === -1) ? $this->dom->root : $this->dom->nodes[$k]; + //PaperG - Pass this optional parameter on to the seek function. + $n->seek($selectors[$c][$l], $ret, $lowercase); + } + $head = $ret; + } + + foreach ($head as $k => $v) + { + if (!isset($found_keys[$k])) + $found_keys[$k] = 1; + } + } + + // sort keys + ksort($found_keys); + + $found = array(); + foreach ($found_keys as $k => $v) + $found[] = $this->dom->nodes[$k]; + + // return nth-element or array + if (is_null($idx)) + return $found; + else if ($idx < 0) + $idx = count($found) + $idx; + return (isset($found[$idx])) ? $found[$idx] : null; + } + + // seek for given conditions + // PaperG - added parameter to allow for case insensitive testing of the value of a selector. + protected function seek($selector, &$ret, $lowercase=false) + { + global $debugObject; + if (is_object($debugObject)) + { + $debugObject->debugLogEntry(1); + } + + list($tag, $key, $val, $exp, $no_key) = $selector; + + // xpath index + if ($tag && $key && is_numeric($key)) + { + $count = 0; + foreach ($this->children as $c) + { + if ($tag === '*' || $tag === $c->tag) + { + if (++$count == $key) + { + $ret[$c->_[HDOM_INFO_BEGIN]] = 1; + return; + } + } + } + return; + } + + $end = (!empty($this->_[HDOM_INFO_END])) ? $this->_[HDOM_INFO_END] : 0; + if ($end == 0) + { + $parent = $this->parent; + while (!isset($parent->_[HDOM_INFO_END]) && $parent !== null) + { + $end -= 1; + $parent = $parent->parent; + } + $end += $parent->_[HDOM_INFO_END]; + } + + for ($i = $this->_[HDOM_INFO_BEGIN] + 1; $i < $end; ++$i) + { + $node = $this->dom->nodes[$i]; + + $pass = true; + + if ($tag === '*' && !$key) + { + if (in_array($node, $this->children, true)) + $ret[$i] = 1; + continue; + } + + // compare tag + if ($tag && $tag != $node->tag && $tag !== '*') + { + $pass = false; + } + // compare key + if ($pass && $key) + { + if ($no_key) + { + if (isset($node->attr[$key])) + $pass = false; + } else + { + if (($key != "plaintext") && !isset($node->attr[$key])) + $pass = false; + } + } + // compare value + if ($pass && $key && $val && $val !== '*') + { + // If they have told us that this is a "plaintext" search then we want the plaintext of the node - right? + if ($key == "plaintext") + { + // $node->plaintext actually returns $node->text(); + $nodeKeyValue = $node->text(); + } + else + { + // this is a normal search, we want the value of that attribute of the tag. + $nodeKeyValue = $node->attr[$key]; + } + if (is_object($debugObject)) + { + $debugObject->debugLog(2, "testing node: " . $node->tag . " for attribute: " . $key . $exp . $val . " where nodes value is: " . $nodeKeyValue); + } + + //PaperG - If lowercase is set, do a case insensitive test of the value of the selector. + if ($lowercase) + { + $check = $this->match($exp, strtolower($val), strtolower($nodeKeyValue)); + } + else + { + $check = $this->match($exp, $val, $nodeKeyValue); + } + if (is_object($debugObject)) + { + $debugObject->debugLog(2, "after match: " . ($check ? "true" : "false")); + } + + // handle multiple class + if (!$check && strcasecmp($key, 'class') === 0) + { + foreach (explode(' ', $node->attr[$key]) as $k) + { + // Without this, there were cases where leading, trailing, or double spaces lead to our comparing blanks - bad form. + if (!empty($k)) + { + if ($lowercase) + { + $check = $this->match($exp, strtolower($val), strtolower($k)); + } + else + { + $check = $this->match($exp, $val, $k); + } + if ($check) + break; + } + } + } + if (!$check) + $pass = false; + } + if ($pass) + $ret[$i] = 1; + unset($node); + } + // It's passed by reference so this is actually what this function returns. + if (is_object($debugObject)) + { + $debugObject->debugLog(1, "EXIT - ret: ", $ret); + } + } + + protected function match($exp, $pattern, $value) + { + global $debugObject; + if (is_object($debugObject)) + { + $debugObject->debugLogEntry(1); + } + + switch ($exp) + { + case '=': + return ($value === $pattern); + case '!=': + return ($value !== $pattern); + case '^=': + return preg_match("/^" . preg_quote($pattern, '/') . "/", $value); + case '$=': + return preg_match("/" . preg_quote($pattern, '/') . "$/", $value); + case '*=': + if ($pattern[0] == '/') + { + return preg_match($pattern, $value); + } + return preg_match("/" . $pattern . "/i", $value); + } + return false; + } + + protected function parse_selector($selector_string) + { + global $debugObject; + if (is_object($debugObject)) + { + $debugObject->debugLogEntry(1); + } + + // pattern of CSS selectors, modified from mootools + // Paperg: Add the colon to the attrbute, so that it properly finds <tag attr:ibute="something" > like google does. + // Note: if you try to look at this attribute, yo MUST use getAttribute since $dom->x:y will fail the php syntax check. +// Notice the \[ starting the attbute? and the @? following? This implies that an attribute can begin with an @ sign that is not captured. +// This implies that an html attribute specifier may start with an @ sign that is NOT captured by the expression. +// farther study is required to determine of this should be documented or removed. +// $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; + $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; + preg_match_all($pattern, trim($selector_string) . ' ', $matches, PREG_SET_ORDER); + if (is_object($debugObject)) + { + $debugObject->debugLog(2, "Matches Array: ", $matches); + } + + $selectors = array(); + $result = array(); + //print_r($matches); + + foreach ($matches as $m) + { + $m[0] = trim($m[0]); + if ($m[0] === '' || $m[0] === '/' || $m[0] === '//') + continue; + // for browser generated xpath + if ($m[1] === 'tbody') + continue; + + list($tag, $key, $val, $exp, $no_key) = array($m[1], null, null, '=', false); + if (!empty($m[2])) + { + $key = 'id'; + $val = $m[2]; + } + if (!empty($m[3])) + { + $key = 'class'; + $val = $m[3]; + } + if (!empty($m[4])) + { + $key = $m[4]; + } + if (!empty($m[5])) + { + $exp = $m[5]; + } + if (!empty($m[6])) + { + $val = $m[6]; + } + + // convert to lowercase + if ($this->dom->lowercase) + { + $tag = strtolower($tag); + $key = strtolower($key); + } + //elements that do NOT have the specified attribute + if (isset($key[0]) && $key[0] === '!') + { + $key = substr($key, 1); + $no_key = true; + } + + $result[] = array($tag, $key, $val, $exp, $no_key); + if (trim($m[7]) === ',') + { + $selectors[] = $result; + $result = array(); + } + } + if (count($result) > 0) + $selectors[] = $result; + return $selectors; + } + + function __get($name) + { + if (isset($this->attr[$name])) + { + return $this->convert_text($this->attr[$name]); + } + switch ($name) + { + case 'outertext': return $this->outertext(); + case 'innertext': return $this->innertext(); + case 'plaintext': return $this->text(); + case 'xmltext': return $this->xmltext(); + default: return array_key_exists($name, $this->attr); + } + } + + function __set($name, $value) + { + switch ($name) + { + case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value; + case 'innertext': + if (isset($this->_[HDOM_INFO_TEXT])) + return $this->_[HDOM_INFO_TEXT] = $value; + return $this->_[HDOM_INFO_INNER] = $value; + } + if (!isset($this->attr[$name])) + { + $this->_[HDOM_INFO_SPACE][] = array(' ', '', ''); + $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE; + } + $this->attr[$name] = $value; + } + + function __isset($name) + { + switch ($name) + { + case 'outertext': return true; + case 'innertext': return true; + case 'plaintext': return true; + } + //no value attr: nowrap, checked selected... + return (array_key_exists($name, $this->attr)) ? true : isset($this->attr[$name]); + } + + function __unset($name) + { + if (isset($this->attr[$name])) + unset($this->attr[$name]); + } + + // PaperG - Function to convert the text from one character set to another if the two sets are not the same. + function convert_text($text) + { + global $debugObject; + if (is_object($debugObject)) + { + $debugObject->debugLogEntry(1); + } + + $converted_text = $text; + + $sourceCharset = ""; + $targetCharset = ""; + if ($this->dom) + { + $sourceCharset = strtoupper($this->dom->_charset); + $targetCharset = strtoupper($this->dom->_target_charset); + } + if (is_object($debugObject)) + { + $debugObject->debugLog(3, "source charset: " . $sourceCharset . " target charaset: " . $targetCharset); + } + + if (!empty($sourceCharset) && !empty($targetCharset) && (strcasecmp($sourceCharset, $targetCharset) != 0)) + { + // Check if the reported encoding could have been incorrect and the text is actually already UTF-8 + if ((strcasecmp($targetCharset, 'UTF-8') == 0) && ($this->is_utf8($text))) + { + $converted_text = $text; + } + else + { + $converted_text = iconv($sourceCharset, $targetCharset, $text); + } + } + + return $converted_text; + } + + function is_utf8($string) + { + return (utf8_encode(utf8_decode($string)) == $string); + } + + // camel naming conventions + function getAllAttributes() + { + return $this->attr; + } + + function getAttribute($name) + { + return $this->__get($name); + } + + function setAttribute($name, $value) + { + $this->__set($name, $value); + } + + function hasAttribute($name) + { + return $this->__isset($name); + } + + function removeAttribute($name) + { + $this->__set($name, null); + } + + function getElementById($id) + { + return $this->find("#$id", 0); + } + + function getElementsById($id, $idx=null) + { + return $this->find("#$id", $idx); + } + + function getElementByTagName($name) + { + return $this->find($name, 0); + } + + function getElementsByTagName($name, $idx=null) + { + return $this->find($name, $idx); + } + + function parentNode() + { + return $this->parent(); + } + + function childNodes($idx=-1) + { + return $this->children($idx); + } + + function firstChild() + { + return $this->first_child(); + } + + function lastChild() + { + return $this->last_child(); + } + + function nextSibling() + { + return $this->next_sibling(); + } + + function previousSibling() + { + return $this->prev_sibling(); + } +} + +/** + * simple html dom parser + * Paperg - in the find routine: allow us to specify that we want case insensitive testing of the value of the selector. + * Paperg - change $size from protected to public so we can easily access it + * Paperg - added ForceTagsClosed in the constructor which tells us whether we trust the html or not. Default is to NOT trust it. + * + * @package PlaceLocalInclude + */ +class simple_html_dom +{ + public $root = null; + public $nodes = array(); + public $callback = null; + public $lowercase = false; + public $size; + protected $pos; + protected $doc; + protected $char; + protected $cursor; + protected $parent; + protected $noise = array(); + protected $token_blank = " \t\r\n"; + protected $token_equal = ' =/>'; + protected $token_slash = " />\r\n\t"; + protected $token_attr = ' >'; + protected $_charset = ''; + protected $_target_charset = ''; + protected $default_br_text = ""; + // use isset instead of in_array, performance boost about 30%... + protected $self_closing_tags = array('img' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'link' => 1, 'hr' => 1, 'base' => 1, 'embed' => 1, 'spacer' => 1); + protected $block_tags = array('root' => 1, 'body' => 1, 'form' => 1, 'div' => 1, 'span' => 1, 'table' => 1); + // Known sourceforge issue #2977341 + // B tags that are not closed cause us to return everything to the end of the document. + protected $optional_closing_tags = array( + 'tr' => array('tr' => 1, 'td' => 1, 'th' => 1), + 'th' => array('th' => 1), + 'td' => array('td' => 1), + 'li' => array('li' => 1), + 'dt' => array('dt' => 1, 'dd' => 1), + 'dd' => array('dd' => 1, 'dt' => 1), + 'dl' => array('dd' => 1, 'dt' => 1), + 'p' => array('p' => 1), + 'nobr' => array('nobr' => 1), + 'b' => array('b' => 1), + ); + + function __construct($str=null, $lowercase=true, $forceTagsClosed=true, $target_charset=DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT) + { + if ($str) + { + if (preg_match("/^http:\/\//i", $str) || is_file($str)) + $this->load_file($str); + else + $this->load($str, $lowercase, $stripRN, $defaultBRText); + } + // Forcing tags to be closed implies that we don't trust the html, but it can lead to parsing errors if we SHOULD trust the html. + if (!$forceTagsClosed) + { + $this->optional_closing_array = array(); + } + $this->_target_charset = $target_charset; + } + + function __destruct() + { + $this->clear(); + } + + // load html from string + function load($str, $lowercase=true, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT) + { + global $debugObject; + + // prepare + $this->prepare($str, $lowercase, $stripRN, $defaultBRText); + // strip out comments + $this->remove_noise("'<!--(.*?)-->'is"); + // strip out cdata + $this->remove_noise("'<!\[CDATA\[(.*?)\]\]>'is", true); + // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037 + // Script tags removal now preceeds style tag removal. + // strip out <script> tags + $this->remove_noise("'<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>'is"); + $this->remove_noise("'<\s*script\s*>(.*?)<\s*/\s*script\s*>'is"); + // strip out <style> tags + $this->remove_noise("'<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>'is"); + $this->remove_noise("'<\s*style\s*>(.*?)<\s*/\s*style\s*>'is"); + // strip out preformatted tags + $this->remove_noise("'<\s*(?:code)[^>]*>(.*?)<\s*/\s*(?:code)\s*>'is"); + // strip out server side scripts + $this->remove_noise("'(<\?)(.*?)(\?>)'s", true); + // strip smarty scripts + $this->remove_noise("'(\{\w)(.*?)(\})'s", true); + + // parsing + while ($this->parse()); + // end + $this->root->_[HDOM_INFO_END] = $this->cursor; + $this->parse_charset(); + } + + // load html from file + function load_file() + { + $args = func_get_args(); + $this->load(call_user_func_array('file_get_contents', $args), true); + // Per the simple_html_dom repositiry this is a planned upgrade to the codebase. + // Throw an error if we can't properly load the dom. + if (($error = error_get_last()) !== null) + { + $this->clear(); + return false; + } + } + + // set callback function + function set_callback($function_name) + { + $this->callback = $function_name; + } + + // remove callback function + function remove_callback() + { + $this->callback = null; + } + + // save dom as string + function save($filepath='') + { + $ret = $this->root->innertext(); + if ($filepath !== '') + file_put_contents($filepath, $ret, LOCK_EX); + return $ret; + } + + // find dom node by css selector + // Paperg - allow us to specify that we want case insensitive testing of the value of the selector. + function find($selector, $idx=null, $lowercase=false) + { + return $this->root->find($selector, $idx, $lowercase); + } + + // clean up memory due to php5 circular references memory leak... + function clear() + { + foreach ($this->nodes as $n) + { + $n->clear(); + $n = null; + } + // This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear. + if (isset($this->children)) + foreach ($this->children as $n) + { + $n->clear(); + $n = null; + } + if (isset($this->parent)) + { + $this->parent->clear(); + unset($this->parent); + } + if (isset($this->root)) + { + $this->root->clear(); + unset($this->root); + } + unset($this->doc); + unset($this->noise); + } + + function dump($show_attr=true) + { + $this->root->dump($show_attr); + } + + // prepare HTML data and init everything + protected function prepare($str, $lowercase=true, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT) + { + $this->clear(); + + // set the length of content before we do anything to it. + $this->size = strlen($str); + + //before we save the string as the doc... strip out the \r \n's if we are told to. + if ($stripRN) + { + $str = str_replace("\r", " ", $str); + $str = str_replace("\n", " ", $str); + } + + $this->doc = $str; + $this->pos = 0; + $this->cursor = 1; + $this->noise = array(); + $this->nodes = array(); + $this->lowercase = $lowercase; + $this->default_br_text = $defaultBRText; + $this->root = new simple_html_dom_node($this); + $this->root->tag = 'root'; + $this->root->_[HDOM_INFO_BEGIN] = -1; + $this->root->nodetype = HDOM_TYPE_ROOT; + $this->parent = $this->root; + if ($this->size > 0) + $this->char = $this->doc[0]; + } + + // parse html content + protected function parse() + { + if (($s = $this->copy_until_char('<')) === '') + return $this->read_tag(); + + // text + $node = new simple_html_dom_node($this); + ++$this->cursor; + $node->_[HDOM_INFO_TEXT] = $s; + $this->link_nodes($node, false); + return true; + } + + // PAPERG - dkchou - added this to try to identify the character set of the page we have just parsed so we know better how to spit it out later. + // NOTE: IF you provide a routine called get_last_retrieve_url_contents_content_type which returns the CURLINFO_CONTENT_TYPE fromt he last curl_exec + // (or the content_type header fromt eh last transfer), we will parse THAT, and if a charset is specified, we will use it over any other mechanism. + protected function parse_charset() + { + global $debugObject; + + $charset = null; + + if (function_exists('get_last_retrieve_url_contents_content_type')) + { + $contentTypeHeader = get_last_retrieve_url_contents_content_type(); + $success = preg_match('/charset=(.+)/', $contentTypeHeader, $matches); + if ($success) + { + $charset = $matches[1]; + if (is_object($debugObject)) + { + $debugObject->debugLog(2, 'header content-type found charset of: ' . $charset); + } + } + } + + if (empty($charset)) + { + $el = $this->root->find('meta[http-equiv=Content-Type]', 0); + if (!empty($el)) + { + $fullvalue = $el->content; + if (is_object($debugObject)) + { + $debugObject->debugLog(2, 'meta content-type tag found' . $fullValue); + } + + if (!empty($fullvalue)) + { + $success = preg_match('/charset=(.+)/', $fullvalue, $matches); + if ($success) + { + $charset = $matches[1]; + } + else + { + // If there is a meta tag, and they don't specify the character set, research says that it's typically ISO-8859-1 + if (is_object($debugObject)) + { + $debugObject->debugLog(2, 'meta content-type tag couldn\'t be parsed. using iso-8859 default.'); + } + $charset = 'ISO-8859-1'; + } + } + } + } + + // If we couldn't find a charset above, then lets try to detect one based on the text we got... + if (empty($charset)) + { + // Have php try to detect the encoding from the text given to us. + $charset = mb_detect_encoding($this->root->plaintext . "ascii", $encoding_list = array("UTF-8", "CP1252")); + if (is_object($debugObject)) + { + $debugObject->debugLog(2, 'mb_detect found: ' . $charset); + } + + // and if this doesn't work... then we need to just wrongheadedly assume it's UTF-8 so that we can move on - cause this will usually give us most of what we need... + if ($charset === false) + { + if (is_object($debugObject)) + { + $debugObject->debugLog(2, 'since mb_detect failed - using default of utf-8'); + } + $charset = 'UTF-8'; + } + } + + // Since CP1252 is a superset, if we get one of it's subsets, we want it instead. + if ((strtolower($charset) == strtolower('ISO-8859-1')) || (strtolower($charset) == strtolower('Latin1')) || (strtolower($charset) == strtolower('Latin-1'))) + { + if (is_object($debugObject)) + { + $debugObject->debugLog(2, 'replacing ' . $charset . ' with CP1252 as its a superset'); + } + $charset = 'CP1252'; + } + + if (is_object($debugObject)) + { + $debugObject->debugLog(1, 'EXIT - ' . $charset); + } + + return $this->_charset = $charset; + } + + // read tag info + protected function read_tag() + { + if ($this->char !== '<') + { + $this->root->_[HDOM_INFO_END] = $this->cursor; + return false; + } + $begin_tag_pos = $this->pos; + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + // end tag + if ($this->char === '/') + { + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + // This represetns the change in the simple_html_dom trunk from revision 180 to 181. + // $this->skip($this->token_blank_t); + $this->skip($this->token_blank); + $tag = $this->copy_until_char('>'); + + // skip attributes in end tag + if (($pos = strpos($tag, ' ')) !== false) + $tag = substr($tag, 0, $pos); + + $parent_lower = strtolower($this->parent->tag); + $tag_lower = strtolower($tag); + + if ($parent_lower !== $tag_lower) + { + if (isset($this->optional_closing_tags[$parent_lower]) && isset($this->block_tags[$tag_lower])) + { + $this->parent->_[HDOM_INFO_END] = 0; + $org_parent = $this->parent; + + while (($this->parent->parent) && strtolower($this->parent->tag) !== $tag_lower) + $this->parent = $this->parent->parent; + + if (strtolower($this->parent->tag) !== $tag_lower) + { + $this->parent = $org_parent; // restore origonal parent + if ($this->parent->parent) + $this->parent = $this->parent->parent; + $this->parent->_[HDOM_INFO_END] = $this->cursor; + return $this->as_text_node($tag); + } + } + else if (($this->parent->parent) && isset($this->block_tags[$tag_lower])) + { + $this->parent->_[HDOM_INFO_END] = 0; + $org_parent = $this->parent; + + while (($this->parent->parent) && strtolower($this->parent->tag) !== $tag_lower) + $this->parent = $this->parent->parent; + + if (strtolower($this->parent->tag) !== $tag_lower) + { + $this->parent = $org_parent; // restore origonal parent + $this->parent->_[HDOM_INFO_END] = $this->cursor; + return $this->as_text_node($tag); + } + } + else if (($this->parent->parent) && strtolower($this->parent->parent->tag) === $tag_lower) + { + $this->parent->_[HDOM_INFO_END] = 0; + $this->parent = $this->parent->parent; + } + else + return $this->as_text_node($tag); + } + + $this->parent->_[HDOM_INFO_END] = $this->cursor; + if ($this->parent->parent) + $this->parent = $this->parent->parent; + + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + return true; + } + + $node = new simple_html_dom_node($this); + $node->_[HDOM_INFO_BEGIN] = $this->cursor; + ++$this->cursor; + $tag = $this->copy_until($this->token_slash); + $node->tag_start = $begin_tag_pos; + + // doctype, cdata & comments... + if (isset($tag[0]) && $tag[0] === '!') + { + $node->_[HDOM_INFO_TEXT] = '<' . $tag . $this->copy_until_char('>'); + + if (isset($tag[2]) && $tag[1] === '-' && $tag[2] === '-') + { + $node->nodetype = HDOM_TYPE_COMMENT; + $node->tag = 'comment'; + } + else + { + $node->nodetype = HDOM_TYPE_UNKNOWN; + $node->tag = 'unknown'; + } + if ($this->char === '>') + $node->_[HDOM_INFO_TEXT].='>'; + $this->link_nodes($node, true); + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + return true; + } + + // text + if ($pos = strpos($tag, '<') !== false) + { + $tag = '<' . substr($tag, 0, -1); + $node->_[HDOM_INFO_TEXT] = $tag; + $this->link_nodes($node, false); + $this->char = $this->doc[--$this->pos]; // prev + return true; + } + + if (!preg_match("/^[\w-:]+$/", $tag)) + { + $node->_[HDOM_INFO_TEXT] = '<' . $tag . $this->copy_until('<>'); + if ($this->char === '<') + { + $this->link_nodes($node, false); + return true; + } + + if ($this->char === '>') + $node->_[HDOM_INFO_TEXT].='>'; + $this->link_nodes($node, false); + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + return true; + } + + // begin tag + $node->nodetype = HDOM_TYPE_ELEMENT; + $tag_lower = strtolower($tag); + $node->tag = ($this->lowercase) ? $tag_lower : $tag; + + // handle optional closing tags + if (isset($this->optional_closing_tags[$tag_lower])) + { + while (isset($this->optional_closing_tags[$tag_lower][strtolower($this->parent->tag)])) + { + $this->parent->_[HDOM_INFO_END] = 0; + $this->parent = $this->parent->parent; + } + $node->parent = $this->parent; + } + + $guard = 0; // prevent infinity loop + $space = array($this->copy_skip($this->token_blank), '', ''); + + // attributes + do + { + if ($this->char !== null && $space[0] === '') + break; + $name = $this->copy_until($this->token_equal); + if ($guard === $this->pos) + { + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + continue; + } + $guard = $this->pos; + + // handle endless '<' + if ($this->pos >= $this->size - 1 && $this->char !== '>') + { + $node->nodetype = HDOM_TYPE_TEXT; + $node->_[HDOM_INFO_END] = 0; + $node->_[HDOM_INFO_TEXT] = '<' . $tag . $space[0] . $name; + $node->tag = 'text'; + $this->link_nodes($node, false); + return true; + } + + // handle mismatch '<' + if ($this->doc[$this->pos - 1] == '<') + { + $node->nodetype = HDOM_TYPE_TEXT; + $node->tag = 'text'; + $node->attr = array(); + $node->_[HDOM_INFO_END] = 0; + $node->_[HDOM_INFO_TEXT] = substr($this->doc, $begin_tag_pos, $this->pos - $begin_tag_pos - 1); + $this->pos -= 2; + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + $this->link_nodes($node, false); + return true; + } + + if ($name !== '/' && $name !== '') + { + $space[1] = $this->copy_skip($this->token_blank); + $name = $this->restore_noise($name); + if ($this->lowercase) + $name = strtolower($name); + if ($this->char === '=') + { + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + $this->parse_attr($node, $name, $space); + } + else + { + //no value attr: nowrap, checked selected... + $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO; + $node->attr[$name] = true; + if ($this->char != '>') + $this->char = $this->doc[--$this->pos]; // prev + } + $node->_[HDOM_INFO_SPACE][] = $space; + $space = array($this->copy_skip($this->token_blank), '', ''); + } + else + break; + } while ($this->char !== '>' && $this->char !== '/'); + + $this->link_nodes($node, true); + $node->_[HDOM_INFO_ENDSPACE] = $space[0]; + + // check self closing + if ($this->copy_until_char_escape('>') === '/') + { + $node->_[HDOM_INFO_ENDSPACE] .= '/'; + $node->_[HDOM_INFO_END] = 0; + } + else + { + // reset parent + if (!isset($this->self_closing_tags[strtolower($node->tag)])) + $this->parent = $node; + } + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + // If it's a BR tag, we need to set it's text to the default text. + // This way when we see it in plaintext, we can generate formatting that the user wants. + if ($node->tag == "br") + { + $node->_[HDOM_INFO_INNER] = $this->default_br_text; + } + + return true; + } + + // parse attributes + protected function parse_attr($node, $name, &$space) + { + // Per sourceforge: http://sourceforge.net/tracker/?func=detail&aid=3061408&group_id=218559&atid=1044037 + // If the attribute is already defined inside a tag, only pay atetntion to the first one as opposed to the last one. + if (isset($node->attr[$name])) + { + return; + } + + $space[2] = $this->copy_skip($this->token_blank); + switch ($this->char) + { + case '"': + $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE; + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + $node->attr[$name] = $this->restore_noise($this->copy_until_char_escape('"')); + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + break; + case '\'': + $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_SINGLE; + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + $node->attr[$name] = $this->restore_noise($this->copy_until_char_escape('\'')); + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + break; + default: + $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO; + $node->attr[$name] = $this->restore_noise($this->copy_until($this->token_attr)); + } + // PaperG: Attributes should not have \r or \n in them, that counts as html whitespace. + $node->attr[$name] = str_replace("\r", "", $node->attr[$name]); + $node->attr[$name] = str_replace("\n", "", $node->attr[$name]); + // PaperG: If this is a "class" selector, lets get rid of the preceeding and trailing space since some people leave it in the multi class case. + if ($name == "class") + { + $node->attr[$name] = trim($node->attr[$name]); + } + } + + // link node's parent + protected function link_nodes(&$node, $is_child) + { + $node->parent = $this->parent; + $this->parent->nodes[] = $node; + if ($is_child) + $this->parent->children[] = $node; + } + + // as a text node + protected function as_text_node($tag) + { + $node = new simple_html_dom_node($this); + ++$this->cursor; + $node->_[HDOM_INFO_TEXT] = '</' . $tag . '>'; + $this->link_nodes($node, false); + $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + return true; + } + + protected function skip($chars) + { + $this->pos += strspn($this->doc, $chars, $this->pos); + $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + } + + protected function copy_skip($chars) + { + $pos = $this->pos; + $len = strspn($this->doc, $chars, $pos); + $this->pos += $len; + $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + if ($len === 0) + return ''; + return substr($this->doc, $pos, $len); + } + + protected function copy_until($chars) + { + $pos = $this->pos; + $len = strcspn($this->doc, $chars, $pos); + $this->pos += $len; + $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next + return substr($this->doc, $pos, $len); + } + + protected function copy_until_char($char) + { + if ($this->char === null) + return ''; + + if (($pos = strpos($this->doc, $char, $this->pos)) === false) + { + $ret = substr($this->doc, $this->pos, $this->size - $this->pos); + $this->char = null; + $this->pos = $this->size; + return $ret; + } + + if ($pos === $this->pos) + return ''; + $pos_old = $this->pos; + $this->char = $this->doc[$pos]; + $this->pos = $pos; + return substr($this->doc, $pos_old, $pos - $pos_old); + } + + protected function copy_until_char_escape($char) + { + if ($this->char === null) + return ''; + + $start = $this->pos; + while (1) + { + if (($pos = strpos($this->doc, $char, $start)) === false) + { + $ret = substr($this->doc, $this->pos, $this->size - $this->pos); + $this->char = null; + $this->pos = $this->size; + return $ret; + } + + if ($pos === $this->pos) + return ''; + + if ($this->doc[$pos - 1] === '\\') + { + $start = $pos + 1; + continue; + } + + $pos_old = $this->pos; + $this->char = $this->doc[$pos]; + $this->pos = $pos; + return substr($this->doc, $pos_old, $pos - $pos_old); + } + } + + // remove noise from html content + protected function remove_noise($pattern, $remove_tag=false) + { + $count = preg_match_all($pattern, $this->doc, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); + + for ($i = $count - 1; $i > -1; --$i) + { + $key = '___noise___' . sprintf('% 3d', count($this->noise) + 100); + $idx = ($remove_tag) ? 0 : 1; + $this->noise[$key] = $matches[$i][$idx][0]; + $this->doc = substr_replace($this->doc, $key, $matches[$i][$idx][1], strlen($matches[$i][$idx][0])); + } + + // reset the length of content + $this->size = strlen($this->doc); + if ($this->size > 0) + $this->char = $this->doc[0]; + } + + // restore noise to html content + function restore_noise($text) + { + while (($pos = strpos($text, '___noise___')) !== false) + { + $key = '___noise___' . $text[$pos + 11] . $text[$pos + 12] . $text[$pos + 13]; + if (isset($this->noise[$key])) + $text = substr($text, 0, $pos) . $this->noise[$key] . substr($text, $pos + 14); + } + return $text; + } + + function __toString() + { + return $this->root->innertext(); + } + + function __get($name) + { + switch ($name) + { + case 'outertext': + return $this->root->innertext(); + case 'innertext': + return $this->root->innertext(); + case 'plaintext': + return $this->root->text(); + case 'charset': + return $this->_charset; + case 'target_charset': + return $this->_target_charset; + } + } + + // camel naming conventions + function childNodes($idx=-1) + { + return $this->root->childNodes($idx); + } + + function firstChild() + { + return $this->root->first_child(); + } + + function lastChild() + { + return $this->root->last_child(); + } + + function getElementById($id) + { + return $this->find("#$id", 0); + } + + function getElementsById($id, $idx=null) + { + return $this->find("#$id", $idx); + } + + function getElementByTagName($name) + { + return $this->find($name, 0); + } + + function getElementsByTagName($name, $idx=-1) + { + return $this->find($name, $idx); + } + + function loadFile() + { + $args = func_get_args(); + $this->load_file($args); + } +} +?>
\ No newline at end of file diff --git a/WebPush/upload.php b/WebPush/upload.php new file mode 100644 index 000000000..cf5c2112e --- /dev/null +++ b/WebPush/upload.php @@ -0,0 +1,100 @@ +<?php +include_once('simple_html_dom.php'); + +function uploadit($build, $branch, $file, $version, $changes) +{ + file_put_contents('status.log', "\nUploading file $file to devbukkit! ", FILE_APPEND); + $slug = "essentials"; + $plugin = "Essentials"; + $url = "http://ci.earth2me.net/guestAuth/repository/download/$branch/$build:id/$file"; + $filename = explode('.', $file); + $request_url = "http://dev.bukkit.org/server-mods/$slug/upload-file.json"; + + include ('apikey.php'); + + $params['name'] = $filename[0] . '-' . $version; + $params['game_versions'] = 176; + $params['change_log'] = $changes; + $params['change_markup_type'] = "html"; + $params['fileurl'] = $url; + + if (stripos($version, 'Dev') !== false) + { + $params['file_type'] = "a"; + } + elseif (stripos($version, 'Pre') !== false) + { + $params['file_type'] = "b"; + } + else + { + $params['file_type'] = "r"; + } + + $content = file_get_contents($url); + file_put_contents($file, $content); + + $params['file'] = '@' . $file; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $request_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $params); + $result = curl_exec($ch); + + if ($result === false) + { + $result = curl_error($ch); + } + elseif ($result == "") + { + $result = "Success uploading $file - $version"; + } + curl_close($ch); + + file_put_contents('status.log', $result, FILE_APPEND); + return true; +} + +function getChanges($job, $project) +{ + $commitblacklist = array( + 'Merge branch', + 'Merge pull', + 'Revert', + 'Cleanup', + ); + + $url = "http://ci.earth2me.net/viewLog.html?buildId=$job&tab=buildChangesDiv&buildTypeId=$project&guest=1"; + + $html = new simple_html_dom(); + $html->load_file($url); + + $output = "Change Log:<ul>"; + foreach ($html->find('.changelist') as $list) + { + foreach ($list->find('.comment') as $comment) + { + $text = $comment->innertext; + foreach ($commitblacklist as $matchtext) + { + if (stripos($text, $matchtext) !== FALSE) + { + $text = ""; + } + } + if ($text != "") + { + $output .= "<li>$text</li>\n"; + } + } + } + $output .= "</ul>"; + + file_put_contents('status.log', "Collected changes! ", FILE_APPEND); + + return $output; +} +?> + diff --git a/lib/lombok-0.10.1.jar b/lib/lombok-0.10.1.jar Binary files differnew file mode 100644 index 000000000..993ebc3c2 --- /dev/null +++ b/lib/lombok-0.10.1.jar |