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/OfflinePlayer.java b/Essentials/src/com/earth2me/essentials/OfflinePlayer.java index 3ca0c17f4..8e50aa1bd 100644 --- a/Essentials/src/com/earth2me/essentials/OfflinePlayer.java +++ b/Essentials/src/com/earth2me/essentials/OfflinePlayer.java @@ -3,9 +3,9 @@ package com.earth2me.essentials; import java.net.InetSocketAddress; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.UUID; +import lombok.Delegate; import org.bukkit.Achievement; import org.bukkit.Effect; import org.bukkit.GameMode; @@ -36,40 +36,32 @@ import org.bukkit.util.Vector; public class OfflinePlayer implements Player { - private final String name; - final transient IEssentials ess; + private final transient IEssentials ess; private Location location = new Location(null, 0, 0, 0, 0, 0); private World world; private UUID uniqueId = UUID.randomUUID(); - private org.bukkit.OfflinePlayer base; + @Delegate(types=org.bukkit.OfflinePlayer.class) + private final org.bukkit.OfflinePlayer base; - public OfflinePlayer(String name, IEssentials ess) + public OfflinePlayer(final String name, final IEssentials ess) { - this.name = name; this.ess = ess; this.world = ess.getServer().getWorlds().get(0); this.base = ess.getServer().getOfflinePlayer(name); } - public boolean isOnline() - { - return base.isOnline(); - } - - public boolean isOp() - { - return base.isOp(); - } - - public void sendMessage(String string) + @Override + public void sendMessage(final String string) { } + @Override public String getDisplayName() { - return name; + return base.getName(); } + @Override public void setDisplayName(String string) { } @@ -87,11 +79,6 @@ public class OfflinePlayer implements Player { } - public String getName() - { - return name; - } - public PlayerInventory getInventory() { return null; @@ -577,42 +564,13 @@ public class OfflinePlayer implements Player { throw new UnsupportedOperationException("Not supported yet."); } - - public void setOp(boolean bln) - { - base.setOp(bln); - } - + @Override public void sendMap(MapView mv) { throw new UnsupportedOperationException("Not supported yet."); } - @Override - public boolean isBanned() - { - return base.isBanned(); - } - - @Override - public void setBanned(boolean bln) - { - base.setBanned(bln); - } - - @Override - public boolean isWhitelisted() - { - return base.isWhitelisted(); - } - - @Override - public void setWhitelisted(boolean bln) - { - base.setWhitelisted(bln); - } - @Override public GameMode getGameMode() { @@ -738,10 +696,4 @@ public class OfflinePlayer implements Player { throw new UnsupportedOperationException("Not supported yet."); } - - @Override - public Map serialize() - { - throw new UnsupportedOperationException("Not supported yet."); - } } diff --git a/Essentials/src/com/earth2me/essentials/PlayerExtension.java b/Essentials/src/com/earth2me/essentials/PlayerExtension.java index 9555bb1a7..11c17761c 100644 --- a/Essentials/src/com/earth2me/essentials/PlayerExtension.java +++ b/Essentials/src/com/earth2me/essentials/PlayerExtension.java @@ -1,28 +1,53 @@ package com.earth2me.essentials; +import lombok.Delegate; import org.bukkit.craftbukkit.entity.CraftPlayer; import net.minecraft.server.EntityPlayer; import net.minecraft.server.IInventory; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.craftbukkit.inventory.CraftInventoryPlayer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.ServerOperator; -public class PlayerExtension extends PlayerWrapper +public class PlayerExtension implements Player { - protected final IEssentials ess; - - public PlayerExtension(Player base, IEssentials ess) + protected final transient IEssentials ess; + @Delegate(types = { - super(base); + Player.class, Entity.class, CommandSender.class, ServerOperator.class, + HumanEntity.class, ConfigurationSerializable.class, LivingEntity.class, + Permissible.class + }) + protected Player base; + + public PlayerExtension(final Player base, final IEssentials ess) + { + this.base = base; this.ess = ess; } - public void showInventory(IInventory inventory) + public final Player getBase() + { + return base; + } + + public final Player setBase(final Player base) + { + return this.base = base; + } + + public void showInventory(final IInventory inventory) { getHandle().a(inventory); } - public void showInventory(CraftInventoryPlayer inventory) + public void showInventory(final CraftInventoryPlayer inventory) { showInventory((IInventory)inventory.getInventory()); } @@ -37,7 +62,7 @@ public class PlayerExtension extends PlayerWrapper return ess.getPermissionsHandler().getGroup(base); } - public boolean inGroup(String group) + public boolean inGroup(final String group) { return ess.getPermissionsHandler().inGroup(base, group); } diff --git a/Essentials/src/com/earth2me/essentials/PlayerWrapper.java b/Essentials/src/com/earth2me/essentials/PlayerWrapper.java deleted file mode 100644 index ccb61d891..000000000 --- a/Essentials/src/com/earth2me/essentials/PlayerWrapper.java +++ /dev/null @@ -1,777 +0,0 @@ -package com.earth2me.essentials; - -import java.net.InetSocketAddress; -import java.util.*; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.entity.*; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.*; -import org.bukkit.map.MapView; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionAttachment; -import org.bukkit.permissions.PermissionAttachmentInfo; -import org.bukkit.plugin.Plugin; -import org.bukkit.util.Vector; - -public class PlayerWrapper implements Player -{ - protected Player base; - - public PlayerWrapper(Player base) - { - this.base = base; - } - - public final Player getBase() - { - return base; - } - - public final Player setBase(Player base) - { - return this.base = base; - } - - @Override - public void setDisplayName(String string) - { - base.setDisplayName(string); - } - - @Override - public void setCompassTarget(Location lctn) - { - base.setCompassTarget(lctn); - } - - @Override - public InetSocketAddress getAddress() - { - return base.getAddress(); - } - - @Override - public void kickPlayer(String string) - { - base.kickPlayer(string); - } - - @Override - public String getName() - { - return base.getName(); - } - - @Override - public PlayerInventory getInventory() - { - return base.getInventory(); - } - - @Override - public ItemStack getItemInHand() - { - return base.getItemInHand(); - } - - @Override - public void setItemInHand(ItemStack is) - { - base.setItemInHand(is); - } - - @Override - public int getHealth() - { - return base.getHealth(); - } - - @Override - public void setHealth(int i) - { - base.setHealth(i); - } - - @Override - public Egg throwEgg() - { - return base.throwEgg(); - } - - @Override - public Snowball throwSnowball() - { - return base.throwSnowball(); - } - - @Override - public Arrow shootArrow() - { - return base.shootArrow(); - } - - @Override - public boolean isInsideVehicle() - { - return base.isInsideVehicle(); - } - - @Override - public boolean leaveVehicle() - { - return base.leaveVehicle(); - } - - @Override - public Vehicle getVehicle() - { - return base.getVehicle(); - } - - @Override - public Location getLocation() - { - return base.getLocation(); - } - - @Override - public World getWorld() - { - return base.getWorld(); - } - - @Override - public Server getServer() - { - return base.getServer(); - } - - @Override - public boolean isOnline() - { - return base.isOnline(); - } - - @Override - public boolean isOp() - { - return base.isOp(); - } - - @Override - public boolean teleport(Location lctn) - { - return base.teleport(lctn); - } - - @Override - public boolean teleport(Entity entity) - { - return base.teleport(entity); - } - - @Override - public void sendMessage(String string) - { - base.sendMessage(string); - } - - @Override - public void setVelocity(Vector vector) - { - base.setVelocity(vector); - } - - @Override - public Vector getVelocity() - { - return base.getVelocity(); - } - - @Override - public double getEyeHeight() - { - return base.getEyeHeight(); - } - - @Override - public double getEyeHeight(boolean bln) - { - return base.getEyeHeight(bln); - } - - @Override - public List getLineOfSight(HashSet hs, int i) - { - return base.getLineOfSight(hs, i); - } - - @Override - public Block getTargetBlock(HashSet hs, int i) - { - return base.getTargetBlock(hs, i); - } - - @Override - public List getLastTwoTargetBlocks(HashSet hs, int i) - { - return base.getLastTwoTargetBlocks(hs, i); - } - - @Override - public int getFireTicks() - { - return base.getFireTicks(); - } - - @Override - public int getMaxFireTicks() - { - return base.getMaxFireTicks(); - } - - @Override - public void setFireTicks(int i) - { - base.setFireTicks(i); - } - - @Override - public void remove() - { - base.remove(); - } - - /** - * This is not deprecated because the underlying method isn't really deprecated; rather, it's just "imperfect". By - * We will continue to use this method even after the underlying CraftBukkit method is changed, so do not deprecate - * it. Chances are Bukkit will also choose to un-deprecate this method at some point. - */ - @Override - public void updateInventory() - { - base.updateInventory(); - } - - @Override - public void chat(String string) - { - base.chat(string); - } - - @Override - public boolean isSneaking() - { - return base.isSneaking(); - } - - @Override - public void setSneaking(boolean bln) - { - base.setSneaking(bln); - } - - @Override - public int getEntityId() - { - return base.getEntityId(); - } - - @Override - public boolean performCommand(String string) - { - return base.performCommand(string); - } - - @Override - public int getRemainingAir() - { - return base.getRemainingAir(); - } - - @Override - public void setRemainingAir(int i) - { - base.setRemainingAir(i); - } - - @Override - public int getMaximumAir() - { - return base.getMaximumAir(); - } - - @Override - public void setMaximumAir(int i) - { - base.setMaximumAir(i); - } - - @Override - public String getDisplayName() - { - if (base.getDisplayName() != null) - return base.getDisplayName(); - else - return base.getName(); - } - - @Override - public void damage(int i) - { - base.damage(i); - } - - @Override - public void damage(int i, Entity entity) - { - base.damage(i, entity); - } - - @Override - public Location getEyeLocation() - { - return base.getEyeLocation(); - } - - @Override - public void sendRawMessage(String string) { - base.sendRawMessage(string); - } - - @Override - public Location getCompassTarget() - { - return base.getCompassTarget(); - } - - @Override - public int getMaximumNoDamageTicks() - { - return base.getMaximumNoDamageTicks(); - } - - @Override - public void setMaximumNoDamageTicks(int i) - { - base.setMaximumNoDamageTicks(i); - } - - @Override - public int getLastDamage() - { - return base.getLastDamage(); - } - - @Override - public void setLastDamage(int i) - { - base.setLastDamage(i); - } - - @Override - public int getNoDamageTicks() - { - return base.getNoDamageTicks(); - } - - @Override - public void setNoDamageTicks(int i) - { - base.setNoDamageTicks(i); - } - - @Override - public Entity getPassenger() - { - return base.getPassenger(); - } - - @Override - public boolean setPassenger(Entity entity) - { - return base.setPassenger(entity); - } - - @Override - public boolean isEmpty() - { - return base.isEmpty(); - } - - @Override - public boolean eject() - { - return base.eject(); - } - - public void saveData() - { - base.saveData(); - } - - public void loadData() - { - base.loadData(); - } - - public boolean isSleeping() - { - return base.isSleeping(); - } - - public int getSleepTicks() - { - return base.getSleepTicks(); - } - - public List getNearbyEntities(double d, double d1, double d2) - { - return base.getNearbyEntities(d, d1, d2); - } - - public boolean isDead() - { - return base.isDead(); - } - - public float getFallDistance() - { - return base.getFallDistance(); - } - - public void setFallDistance(float f) - { - base.setFallDistance(f); - } - - public void setSleepingIgnored(boolean bln) - { - base.setSleepingIgnored(bln); - } - - public boolean isSleepingIgnored() - { - return base.isSleepingIgnored(); - } - - public void awardAchievement(Achievement a) - { - base.awardAchievement(a); - } - - public void incrementStatistic(Statistic ststc) - { - base.incrementStatistic(ststc); - } - - public void incrementStatistic(Statistic ststc, int i) - { - base.incrementStatistic(ststc, i); - } - - public void incrementStatistic(Statistic ststc, Material mtrl) - { - base.incrementStatistic(ststc, mtrl); - } - - public void incrementStatistic(Statistic ststc, Material mtrl, int i) - { - base.incrementStatistic(ststc, mtrl, i); - } - - public void playNote(Location lctn, byte b, byte b1) - { - base.playNote(lctn, b, b1); - } - - public void sendBlockChange(Location lctn, Material mtrl, byte b) - { - base.sendBlockChange(lctn, mtrl, b); - } - - public void sendBlockChange(Location lctn, int i, byte b) - { - base.sendBlockChange(lctn, i, b); - } - - public void setLastDamageCause(EntityDamageEvent ede) - { - base.setLastDamageCause(ede); - - } - - public EntityDamageEvent getLastDamageCause() - { - return base.getLastDamageCause(); - } - - public void playEffect(Location lctn, Effect effect, int i) - { - base.playEffect(lctn, effect, i); - } - - public boolean sendChunkChange(Location lctn, int i, int i1, int i2, byte[] bytes) - { - return base.sendChunkChange(lctn, i, i1, i2, bytes); - } - - public UUID getUniqueId() - { - return base.getUniqueId(); - } - - public void playNote(Location lctn, Instrument i, Note note) - { - base.playNote(lctn, i, note); - } - - public void setPlayerTime(long l, boolean bln) - { - base.setPlayerTime(l, bln); - } - - public long getPlayerTime() - { - return base.getPlayerTime(); - } - - public long getPlayerTimeOffset() - { - return base.getPlayerTimeOffset(); - } - - public boolean isPlayerTimeRelative() - { - return base.isPlayerTimeRelative(); - } - - public void resetPlayerTime() - { - base.resetPlayerTime(); - } - - public boolean isPermissionSet(String string) - { - return base.isPermissionSet(string); - } - - public boolean isPermissionSet(Permission prmsn) - { - return base.isPermissionSet(prmsn); - } - - public boolean hasPermission(String string) - { - return base.hasPermission(string); - } - - public boolean hasPermission(Permission prmsn) - { - return base.hasPermission(prmsn); - } - - public PermissionAttachment addAttachment(Plugin plugin, String string, boolean bln) - { - return base.addAttachment(plugin, string, bln); - } - - public PermissionAttachment addAttachment(Plugin plugin) - { - return base.addAttachment(plugin); - } - - public PermissionAttachment addAttachment(Plugin plugin, String string, boolean bln, int i) - { - return base.addAttachment(plugin, string, bln, i); - } - - public PermissionAttachment addAttachment(Plugin plugin, int i) - { - return base.addAttachment(plugin, i); - } - - public void removeAttachment(PermissionAttachment pa) - { - throw new UnsupportedOperationException("Not supported yet."); - } - - public void recalculatePermissions() - { - base.recalculatePermissions(); - } - - public Set getEffectivePermissions() - { - return base.getEffectivePermissions(); - } - - public void setOp(boolean bln) - { - base.setOp(bln); - } - - @Override - public void sendMap(MapView mv) - { - base.sendMap(mv); - } - - @Override - public boolean isBanned() - { - return base.isBanned(); - } - - @Override - public void setBanned(boolean bln) - { - base.setBanned(bln); - } - - @Override - public boolean isWhitelisted() - { - return base.isWhitelisted(); - } - - @Override - public void setWhitelisted(boolean bln) - { - base.setWhitelisted(bln); - } - - @Override - public GameMode getGameMode() - { - return base.getGameMode(); - } - - @Override - public void setGameMode(GameMode gm) - { - base.setGameMode(gm); - } - - @Override - public int getExperience() - { - return base.getExperience(); - } - - @Override - public void setExperience(int i) - { - base.setExperience(i); - } - - @Override - public int getLevel() - { - return base.getLevel(); - } - - @Override - public void setLevel(int i) - { - base.setLevel(i); - } - - @Override - public int getTotalExperience() - { - return base.getTotalExperience(); - } - - @Override - public void setTotalExperience(int i) - { - base.setTotalExperience(i); - } - - @Override - public float getExhaustion() - { - return base.getExhaustion(); - } - - @Override - public void setExhaustion(float f) - { - base.setExhaustion(f); - } - - @Override - public float getSaturation() - { - return base.getSaturation(); - } - - @Override - public void setSaturation(float f) - { - base.setSaturation(f); - } - - @Override - public int getFoodLevel() - { - return base.getFoodLevel(); - } - - @Override - public void setFoodLevel(int i) - { - base.setFoodLevel(i); - } - - @Override - public Location getBedSpawnLocation() - { - return base.getBedSpawnLocation(); - } - - @Override - public boolean isSprinting() - { - return base.isSprinting(); - } - - @Override - public void setSprinting(boolean bln) - { - base.setSprinting(bln); - } - - @Override - public void setPlayerListName(String name) - { - base.setPlayerListName(name); - } - - @Override - public String getPlayerListName() - { - return base.getPlayerListName(); - } - - @Override - public int getTicksLived() - { - return base.getTicksLived(); - } - - @Override - public void setTicksLived(int i) - { - base.setTicksLived(i); - } - - @Override - public Map serialize() - { - return base.serialize(); - } - -} 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 homeList = getMultipleHomes(); + final List 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/User.java b/Essentials/src/com/earth2me/essentials/User.java index 3e335a1df..bcc871eab 100644 --- a/Essentials/src/com/earth2me/essentials/User.java +++ b/Essentials/src/com/earth2me/essentials/User.java @@ -282,16 +282,23 @@ public class User extends UserData implements Comparable, IReplyTo, IUser return nickname.toString(); } - + public void setDisplayNick(String name) { setDisplayName(name); //TODO: Maybe we need to limit nick length, or try use a string trim. - if (name.length() <= 16) { + if (name.length() <= 16) + { setPlayerListName(name); } } + @Override + public String getDisplayName() + { + return super.getDisplayName() == null ? super.getName() : super.getDisplayName(); + } + public Teleport getTeleport() { return teleport; @@ -355,7 +362,8 @@ public class User extends UserData implements Comparable, IReplyTo, IUser public void setAfk(final boolean set) { this.setSleepingIgnored(this.isAuthorized("essentials.sleepingignored") ? true : set); - if (set && !isAfk()) { + if (set && !isAfk()) + { afkPosition = getLocation(); } super.setAfk(set); @@ -461,7 +469,8 @@ public class User extends UserData implements Comparable, IReplyTo, IUser if (!isAfk() && autoafk > 0 && lastActivity + autoafk * 1000 < System.currentTimeMillis() && isAuthorized("essentials.afk")) { setAfk(true); - if (!isHidden()) { + if (!isHidden()) + { ess.broadcastMessage(this, Util.format("userIsAway", getDisplayName())); } } @@ -475,7 +484,8 @@ public class User extends UserData implements Comparable, IReplyTo, IUser @Override public boolean toggleGodModeEnabled() { - if (!isGodModeEnabled()) { + if (!isGodModeEnabled()) + { setFoodLevel(20); } return super.toggleGodModeEnabled(); 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 + " [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 overwritten = new ArrayList(); + + @ListType + @Comment("Disabled commands will be completelly unavailable on the server.") + private List disabled = new ArrayList(); +} 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 commandCosts = new HashMap(); + @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 groups = new LinkedHashMap(); +} 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., 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 kits = new HashMap(); +} 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 items = new ArrayList(); + 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 clazz; + + protected StorageObject() + { + } + private static Map constructors = new HashMap(); + + public static T load(Class 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 classes = new HashSet(); + + prepareConstructor(constructor, classes, clazz); + return constructor; + } + + private static void prepareConstructor(final Constructor constructor, final Set 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 entry : ((Map)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)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 8ae568f21..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. @@ -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/FakeServer.java b/Essentials/test/com/earth2me/essentials/FakeServer.java index 2344c4513..7f3fb9bf2 100644 --- a/Essentials/test/com/earth2me/essentials/FakeServer.java +++ b/Essentials/test/com/earth2me/essentials/FakeServer.java @@ -467,9 +467,70 @@ public class FakeServer implements Server } @Override - public org.bukkit.OfflinePlayer getOfflinePlayer(String string) + public org.bukkit.OfflinePlayer getOfflinePlayer(final String string) { - return null; + return new org.bukkit.OfflinePlayer() { + + @Override + public boolean isOnline() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public String getName() + { + return string; + } + + @Override + public boolean isBanned() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setBanned(boolean bln) + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isWhitelisted() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setWhitelisted(boolean bln) + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Player getPlayer() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isOp() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setOp(boolean bln) + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Map serialize() + { + throw new UnsupportedOperationException("Not supported yet."); + } + }; } @Override 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/EssentialsGroupManager/src/Changelog.txt b/EssentialsGroupManager/src/Changelog.txt index fe33a9f15..6729907b5 100644 --- a/EssentialsGroupManager/src/Changelog.txt +++ b/EssentialsGroupManager/src/Changelog.txt @@ -39,4 +39,13 @@ v 1.3: - Fix for Bukkit passing a null To location on a player Portaling - Fixed manudelsub not correctly selecting the group to remove. - Added two new permission nodes - groupmanager.notify.self & groupmanager.notify.other - These allow players/admins to be notified when players are moved between groups. \ No newline at end of file + These allow players/admins to be notified when players are moved between groups. +v 1.4: + - Updated for Bukkits new YamlConfiguration. + - Cleared remaining Cast errors cause by object cloning. + - Removed extra notification messages for the player issuing the group move command. + - Added a config setting - bukkit_perms_override: false + Enable to allow default Bukkit based permissions to remain enabled, unless directly negated within GroupManager. + - Fixed reading world mirrors from the config. + - Simplified config.yml while retaining backwards compatibility. + - Added data.save.hours setting to config. This allow control over how long backups are retained. \ No newline at end of file diff --git a/EssentialsGroupManager/src/config.yml b/EssentialsGroupManager/src/config.yml index 1c5e3e32d..25fb5dcd0 100644 --- a/EssentialsGroupManager/src/config.yml +++ b/EssentialsGroupManager/src/config.yml @@ -2,17 +2,24 @@ settings: config: # With this enabled anyone set as op has full permissions when managing GroupManager opOverrides: true + + # If enabled any bukkit permissiosn which default to true will be left enabled. + # If the player is op any permissions set to Op will follow suit. + bukkit_perms_override: false + data: save: # How often GroupManager will save it's data back to groups and users.yml minutes: 10 + # Number of hours to retain backups (plugins/GroupManager/backup) + hours: 24 + logging: # level of detail GroupManager will use when logging. # Acceptable entries are - ALL,CONFIG,FINE,FINER,FINEST,INFO,OFF,SEVERE,WARNING level: INFO - permission: - world: - mirror: + + mirrors: # Worlds listed here have their permissions mirrored in their children. world: - world_nether diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/GMConfiguration.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/GMConfiguration.java index aa838d9e8..ad2945076 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/GMConfiguration.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/GMConfiguration.java @@ -10,7 +10,7 @@ import java.util.Map; import java.util.logging.Level; import org.anjocaido.groupmanager.utils.Tasks; -import org.bukkit.util.config.Configuration; +import org.bukkit.configuration.file.YamlConfiguration; /** * @@ -20,7 +20,7 @@ public class GMConfiguration { private GroupManager plugin; private File configFile; - private Configuration GMconfig; + private YamlConfiguration GMconfig; public GMConfiguration(GroupManager plugin) { this.plugin = plugin; @@ -41,10 +41,10 @@ public class GMConfiguration { } } - GMconfig = new Configuration(configFile); + GMconfig = new YamlConfiguration(); try { - GMconfig.load(); + GMconfig.load(configFile); } catch (Exception ex) { throw new IllegalArgumentException("The following file couldn't pass on Parser.\n" + configFile.getPath(), ex); } @@ -52,17 +52,28 @@ public class GMConfiguration { } public boolean isOpOverride() { + return GMconfig.getBoolean("settings.config.bukkit_perms_override", true); + } + public boolean isBukkitPermsOverride() { return GMconfig.getBoolean("settings.config.opOverrides", true); } - @SuppressWarnings("unchecked") - public Map getMirrorsMap() { - return (Map) GMconfig.getProperty("settings.permission.world.mirror"); + public Map getMirrorsMap() { + // Try to fetch the old mirror path first + if (GMconfig.isConfigurationSection("settings.permission.world.mirror")) + return (Map) GMconfig.getConfigurationSection("settings.permission.world.mirror").getValues(false); + else + return (Map) GMconfig.getConfigurationSection("settings.mirrors").getValues(false); + } public Integer getSaveInterval() { return GMconfig.getInt("settings.data.save.minutes", 10); } + + public Integer getBackupDuration() { + return GMconfig.getInt("settings.data.save.hours", 24); + } public void adjustLoggerLevel() { diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/GroupManager.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/GroupManager.java index 43a57c419..5ef4fc537 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/GroupManager.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/GroupManager.java @@ -57,7 +57,7 @@ public class GroupManager extends JavaPlugin { private boolean validateOnlinePlayer = true; private boolean isReady = false; private static boolean isLoaded = false; - private GMConfiguration config; + protected GMConfiguration config; private GMLoggerHandler ch; public static BukkitPermissions BukkitPermissions; private static WorldListener WorldEvents; @@ -163,6 +163,7 @@ public class GroupManager extends JavaPlugin { int minutes = getGMConfig().getSaveInterval(); scheduler.scheduleAtFixedRate(commiter, minutes, minutes, TimeUnit.MINUTES); GroupManager.logger.info("Scheduled Data Saving is set for every " + minutes + " minutes!"); + GroupManager.logger.info("Backups will be retained for " + getGMConfig().getBackupDuration() + " hours!"); } } @@ -369,7 +370,8 @@ public class GroupManager extends JavaPlugin { //PARECE OK auxUser.setGroup(auxGroup); - sender.sendMessage(ChatColor.YELLOW + "You changed player '" + auxUser.getName() + "' group to '" + auxGroup.getName() + "'."); + if (!sender.hasPermission("groupmanager.notify.other")) + sender.sendMessage(ChatColor.YELLOW + "You changed player '" + auxUser.getName() + "' group to '" + auxGroup.getName() + "'."); targetPlayer = this.getServer().getPlayer(auxUser.getName()); if (targetPlayer != null) BukkitPermissions.updatePermissions(targetPlayer); @@ -1590,7 +1592,8 @@ public class GroupManager extends JavaPlugin { } //PARECE OK auxUser.setGroup(auxGroup); - sender.sendMessage(ChatColor.YELLOW + "You changed " + auxUser.getName() + " group to " + auxGroup.getName() + "."); + if (!sender.hasPermission("groupmanager.notify.other")) + sender.sendMessage(ChatColor.YELLOW + "You changed " + auxUser.getName() + " group to " + auxGroup.getName() + "."); targetPlayer = this.getServer().getPlayer(auxUser.getName()); if (targetPlayer != null) BukkitPermissions.updatePermissions(targetPlayer); @@ -1648,7 +1651,8 @@ public class GroupManager extends JavaPlugin { } //PARECE OK auxUser.setGroup(auxGroup); - sender.sendMessage(ChatColor.YELLOW + "You changed " + auxUser.getName() + " group to " + auxGroup.getName() + "."); + if (!sender.hasPermission("groupmanager.notify.other")) + sender.sendMessage(ChatColor.YELLOW + "You changed " + auxUser.getName() + " group to " + auxGroup.getName() + "."); targetPlayer = this.getServer().getPlayer(auxUser.getName()); if (targetPlayer != null) BukkitPermissions.updatePermissions(targetPlayer); @@ -1770,10 +1774,10 @@ public class GroupManager extends JavaPlugin { for(Player test: Bukkit.getServer().getOnlinePlayers()) { if (!test.equals(player)){ if (test.hasPermission("groupmanager.notify.other")) - test.sendMessage(ChatColor.YELLOW + name +" was " + msg); + test.sendMessage(ChatColor.YELLOW + name +" was" + msg); } else if ((player != null) && ((player.hasPermission("groupmanager.notify.self")) || (player.hasPermission("groupmanager.notify.other")))) - player.sendMessage(ChatColor.YELLOW + "You were " + msg); + player.sendMessage(ChatColor.YELLOW + "You were" + msg); } } diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/DataUnit.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/DataUnit.java index f17d8b21e..7b027a94d 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/DataUnit.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/DataUnit.java @@ -102,7 +102,7 @@ public abstract class DataUnit { * @return a copy of the permission list */ public ArrayList getPermissionList() { - return (ArrayList) permissions.clone(); + return new ArrayList(permissions); } public void sortPermissions() { diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/Group.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/Group.java index c881fc95d..84d4fceed 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/Group.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/Group.java @@ -41,7 +41,7 @@ public class Group extends DataUnit implements Cloneable { @Override public Group clone() { Group clone = new Group(getDataSource(), this.getName()); - clone.inherits = ((ArrayList) this.getInherits().clone()); + clone.inherits = new ArrayList(this.getInherits()); for (String perm : this.getPermissionList()) { clone.addPermission(perm); } @@ -60,7 +60,7 @@ public class Group extends DataUnit implements Cloneable { return null; } Group clone = getDataSource().createGroup(this.getName()); - clone.inherits = ((ArrayList) this.getInherits().clone()); + clone.inherits = new ArrayList(this.getInherits()); for (String perm : this.getPermissionList()) { clone.addPermission(perm); } @@ -76,7 +76,7 @@ public class Group extends DataUnit implements Cloneable { * @return the inherits */ public ArrayList getInherits() { - return (ArrayList) inherits.clone(); + return new ArrayList(inherits); } /** diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/User.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/User.java index a95018fa9..8a0e6cdbb 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/User.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/data/User.java @@ -183,7 +183,7 @@ public class User extends DataUnit implements Cloneable { } public ArrayList subGroupListStringCopy() { - return (ArrayList) subGroups.clone(); + return new ArrayList(subGroups); } /** diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/worlds/WorldsHolder.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/worlds/WorldsHolder.java index 0bcf9a827..7cbe4f65c 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/worlds/WorldsHolder.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/worlds/WorldsHolder.java @@ -99,11 +99,11 @@ public class WorldsHolder { * don't load any worlds which are already loaded * or mirrored worlds that don't need data. */ - if (worldsData.containsKey(folder.getName().toLowerCase()) - || mirrors.containsKey(folder.getName().toLowerCase())) { - continue; + if (!worldsData.containsKey(folder.getName().toLowerCase()) + || !mirrors.containsKey(folder.getName().toLowerCase())) { + loadWorld(folder.getName()); } - loadWorld(folder.getName()); + } } } @@ -168,7 +168,7 @@ public class WorldsHolder { if (alreadyDone.contains(w)) { continue; } - Tasks.removeOldFiles(plugin.getBackupFolder()); + Tasks.removeOldFiles(plugin, plugin.getBackupFolder()); if (w == null) { GroupManager.logger.severe("WHAT HAPPENED?"); continue; diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/permissions/BukkitPermissions.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/permissions/BukkitPermissions.java index 19d90854b..6a5968c22 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/permissions/BukkitPermissions.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/permissions/BukkitPermissions.java @@ -43,6 +43,7 @@ import org.bukkit.event.server.ServerListener; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; @@ -124,15 +125,31 @@ public class BukkitPermissions { User user = worldData.getUser(player.getName()); - // clear permissions - for (String permission : attachment.getPermissions().keySet()) { - attachment.unsetPermission(permission); - } + // clear permissions + for (String permission : attachment.getPermissions().keySet()) + attachment.unsetPermission(permission); - // find matching permissions + /* + * find matching permissions + * + * and base bukkit perms if we are set to allow bukkit permissions to override. + */ Boolean value; for (Permission permission : registeredPermissions) { - value = worldData.getPermissionsHandler().checkUserPermission(user, permission.getName()); + + value = worldData.getPermissionsHandler().checkUserPermission(user, permission.getName()); + + // Only check bukkit override IF we don't have the permission directly. + if (value = false) { + PermissionDefault permDefault = permission.getDefault(); + + if ((plugin.getGMConfig().isBukkitPermsOverride()) + && ((permDefault == PermissionDefault.TRUE) + || ((permDefault == PermissionDefault.NOT_OP) && !player.isOp()) + || ((permDefault == PermissionDefault.OP) && player.isOp()))) + value = true; + } + if (value == true) attachment.setPermission(permission, value); } diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/utils/Tasks.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/utils/Tasks.java index 8e4d58dde..f3defd94a 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/utils/Tasks.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/utils/Tasks.java @@ -13,6 +13,8 @@ import java.io.OutputStream; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.List; + +import org.anjocaido.groupmanager.GroupManager; import org.anjocaido.groupmanager.data.Group; /** @@ -43,9 +45,9 @@ public abstract class Tasks { copy(in, dst); } - public static void removeOldFiles(File folder) { + public static void removeOldFiles(GroupManager gm, File folder) { if (folder.isDirectory()) { - long oldTime = System.currentTimeMillis() - 86400000L; + long oldTime = System.currentTimeMillis() - (((long)gm.getGMConfig().getBackupDuration()*60*60)*1000); for (File olds : folder.listFiles()) { if (olds.isFile()) { if (olds.lastModified() < oldTime) { diff --git a/EssentialsGroupManager/src/plugin.yml b/EssentialsGroupManager/src/plugin.yml index a247c7ad0..596b4a8d8 100644 --- a/EssentialsGroupManager/src/plugin.yml +++ b/EssentialsGroupManager/src/plugin.yml @@ -1,5 +1,5 @@ name: GroupManager -version: "1.3 (Phoenix)" +version: "1.4 (Phoenix)" main: org.anjocaido.groupmanager.GroupManager website: http://www.anjocaido.info/ description: Provides on-the-fly system for permissions system created by Nijikokun. But all in memory, and with flat-file saving schedule. 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 @@ + + + + + + + + + + + Builds, tests, and runs the project EssentialsUpdate. + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + + + + + + java -cp "${run.classpath.with.dist.jar}" ${main.class} + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + + + + + + + Must select one file in the IDE or set profile.class + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + org.netbeans.modules.java.j2seproject + + + EssentialsUpdate + + + + + + + + + ../lib/nblibraries.properties + + + 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 map = new HashMap(); + 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 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 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 versions = updateFile.getVersions(); + final int bukkitVersion = getBukkitVersion(); + Version higher = null; + Version found = null; + Version lower = null; + int bukkitHigher = 0; + int bukkitLower = 0; + for (Entry 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 versions = new TreeMap(); + + 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 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 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 +{ + 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 changelog; + private final transient int minBukkit; + private final transient int maxBukkit; + private final transient List modules; + + public VersionInfo(final Configuration updateConfig, final String path) + { + changelog = updateConfig.getList(path + ".changelog", Collections.emptyList()); + minBukkit = updateConfig.getInt(path + ".min-bukkit", 0); + maxBukkit = updateConfig.getInt(path + ".max-bukkit", 0); + modules = new ArrayList(); + final String modulesPath = path + ".modules"; + for (String module : updateConfig.getKeys(false)) + { + modules.add(new ModuleInfo(updateConfig, modulesPath + module)); + } + } + + public List getChangelog() + { + return Collections.unmodifiableList(changelog); + } + + public int getMinBukkit() + { + return minBukkit; + } + + public int getMaxBukkit() + { + return maxBukkit; + } + + public List 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. + *

+ * 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. + *

+ * The class contains static methods to remove colours and formatting + * from lines of IRC text. + *

+ * Here are some examples of how to use the contants from within a + * class that extends PircBot and imports org.jibble.pircbot.*; + * + *

 sendMessage("#cs", Colors.BOLD + "A bold hello!");
+ *     A bold hello!
+ * sendMessage("#cs", Colors.RED + "Red" + Colors.NORMAL + " text");
+ *     Red text
+ * sendMessage("#cs", Colors.BOLD + Colors.RED + "Bold and red");
+ *     Bold and red
+ * + * 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. + *

+ * 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, + * http://www.jibble.org/ + * @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, + * http://www.jibble.org/ + * @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, + * http://www.jibble.org/ + * @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, + * http://www.jibble.org/ + * @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, + * http://www.jibble.org/ + * @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. + *

+ * 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. + *

+ * 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. + *

+ * 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 onXYZ methods in the PircBot class are automatically called + * when the event XYZ happens, so you would override these if you wish + * to do something when it does happen. + *

+ * 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. + *

+ * Please visit the PircBot homepage at + * http://www.jibble.org/pircbot.php + * 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, + * http://www.jibble.org/ + * @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. + *

+ * Some examples: - + *

    // Send the message "Hello!" to the channel #cs.
+     *    sendMessage("#cs", "Hello!");
+     *    
+     *    // Send a private message to Paul that says "Hi".
+     *    sendMessage("Paul", "Hi");
+ * + * You may optionally apply colours, boldness, underlining, etc to + * the message by using the Colors 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 ", "FINGER", "VERSION", etc. + * For example, if you wish to request the version of a user called "Dave", + * then you would call sendCTCPCommand("Dave", "VERSION");. + * 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 register and + * identify 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 identify 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 identify with + * the supplied password. It usually makes sense to identify with NickServ + * immediately after connecting to a server. + *

+ * 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: + *

sendMessage("NickServ", "identify PASSWORD");
+ * + * @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. + *

+ * 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 "###". + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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"). + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 "PircBot #test :I Am King of Test" + * with a code of 332 to signify that this is a topic. + * (This is just an example - note that overriding the + * onTopic 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. + *

+ * PircBot implements the interface ReplyConstants, which contains + * contstants that you may find useful here. + *

+ * 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. + *

+ * 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. + *

+ * To obtain the nick of each user in the channel, call the getNick() + * method on each User object in the array. + *

+ * At a later time, you may call the getUsers method to obtain an + * up to date list of the users in the channel. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * Note that certain channels, such as those marked as hidden, + * may not appear in channel listings. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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 :-) + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * This is a type of mode change and is also passed to the onMode + * method in the PircBot class. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 (not + * recommended), 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, any 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, any 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 + * + * Version{PircBot x.y.z Java IRC Bot - www.jibble.org} + * Connected{true} + * Server{irc.dal.net} + * Port{6667} + * Password{} + * + * + * @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. + *

+ * There are some important things to note about this method:- + *

    + *
  • 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. + *
  • + *
  • 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. + *
  • + *
  • This method will return immediately, as it does not require any + * interaction with the IRC server. + *
  • + *
  • The bot must be in a channel to be able to know which users are + * in it. + *
  • + *
+ * + * @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. + *

+ * 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. + *

+ * 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, + * http://www.jibble.org/ + * @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 next() is + * called, then the call to next() 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. + *

+ * 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, + * http://www.jibble.org/ + * @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. + *

+ * Note that this class no longer implements the Comparable interface + * for Java 1.1 compatibility reasons. + * + * @since 1.0.0 + * @author Paul James Mutton, + * http://www.jibble.org/ + * @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: / + essentialshelp: + description: Get help from the Essentials support chat. + usage: / +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 set = new TreeSet(); + 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 @@ + 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 @@ + + 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 @@ + + + org.netbeans.modules.php.project + + + Push + + + 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 @@ +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 + * @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 + */ +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 .= '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('', '', $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 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("''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