Merge branch 'master' of https://github.com/AuthMe-Team/AuthMeReloaded into command-perms-refactor

This commit is contained in:
ljacqu 2015-11-30 21:12:47 +01:00
commit 485b6934e6
10 changed files with 150 additions and 74 deletions

65
pom.xml
View File

@ -53,6 +53,7 @@
<pluginName>AuthMe</pluginName> <pluginName>AuthMe</pluginName>
<mainClass>fr.xephi.authme.AuthMe</mainClass> <mainClass>fr.xephi.authme.AuthMe</mainClass>
<pluginAuthors>Xephi, sgdc3, DNx5, timvisee, games647, ljacqu</pluginAuthors> <pluginAuthors>Xephi, sgdc3, DNx5, timvisee, games647, ljacqu</pluginAuthors>
<buildNumber>Unknown</buildNumber>
<!-- Change Compiler Version (JDK) HERE! --> <!-- Change Compiler Version (JDK) HERE! -->
<javaVersion>1.7</javaVersion> <javaVersion>1.7</javaVersion>
@ -61,6 +62,20 @@
<bukkitVersion>1.8.8-R0.1-SNAPSHOT</bukkitVersion> <bukkitVersion>1.8.8-R0.1-SNAPSHOT</bukkitVersion>
</properties> </properties>
<profiles>
<profile>
<id>jenkins</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<buildNumber>${env.BUILD_NUMBER}</buildNumber>
</properties>
</profile>
</profiles>
<build> <build>
<finalName>AuthMe-${project.version}</finalName> <finalName>AuthMe-${project.version}</finalName>
<sourceDirectory>src/main/java</sourceDirectory> <sourceDirectory>src/main/java</sourceDirectory>
@ -101,6 +116,39 @@
</testResource> </testResource>
</testResources> </testResources>
<!-- Just to keep Eclipse compatibility... -->
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<versionRange>[1.0,)</versionRange>
<goals>
<goal>create-timestamp</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnConfiguration>true</runOnConfiguration>
<runOnIncremental>true</runOnIncremental>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -111,6 +159,23 @@
<target>${javaVersion}</target> <target>${javaVersion}</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<timestampFormat>dd-MM-yy_HH-mm</timestampFormat>
<timestampPropertyName>build.time</timestampPropertyName>
</configuration>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>create-timestamp</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- TODO: we need also to relocate the other libs --> <!-- TODO: we need also to relocate the other libs -->
<plugin> <plugin>

View File

@ -61,15 +61,9 @@ public class AuthMe extends JavaPlugin {
* Defines the name of the plugin. * Defines the name of the plugin.
*/ */
private static final String PLUGIN_NAME = "AuthMeReloaded"; private static final String PLUGIN_NAME = "AuthMeReloaded";
/**
* Defines the current AuthMeReloaded version name. private static String pluginVersion = "N/D";
*/ private static String pluginBuildNumber = "Unknown";
private static final String PLUGIN_VERSION_NAME = "5.1-SNAPSHOT";
/**
* Defines the current AuthMeReloaded version code.
*/
// TODO: increment this value manually
private static final int PLUGIN_VERSION_CODE = 120;
private static AuthMe plugin; private static AuthMe plugin;
private static Server server; private static Server server;
@ -130,8 +124,8 @@ public class AuthMe extends JavaPlugin {
* *
* @return The version name of the currently installed AuthMeReloaded instance. * @return The version name of the currently installed AuthMeReloaded instance.
*/ */
public static String getVersionName() { public static String getPluginVersion() {
return PLUGIN_VERSION_NAME; return pluginVersion;
} }
/** /**
@ -139,8 +133,8 @@ public class AuthMe extends JavaPlugin {
* *
* @return The version code of the currently installed AuthMeReloaded instance. * @return The version code of the currently installed AuthMeReloaded instance.
*/ */
public static int getVersionCode() { public static String getPluginBuildNumber() {
return PLUGIN_VERSION_CODE; return pluginBuildNumber;
} }
/** /**
@ -189,6 +183,20 @@ public class AuthMe extends JavaPlugin {
this.canConnect = canConnect; this.canConnect = canConnect;
} }
// Get version and build number of the plugin
// TODO: enhance this
private void setupConstants() {
String versionRaw = this.getDescription().getVersion();
int index = versionRaw.lastIndexOf("-");
if (index != -1) {
pluginVersion = versionRaw.substring(0, index);
pluginBuildNumber = versionRaw.substring(index + 1);
if (pluginBuildNumber.startsWith("b")) {
pluginBuildNumber = pluginBuildNumber.substring(1);
}
}
}
/** /**
* Method called when the server enables the plugin. * Method called when the server enables the plugin.
* *
@ -199,6 +207,7 @@ public class AuthMe extends JavaPlugin {
// Set various instances // Set various instances
server = getServer(); server = getServer();
plugin = this; plugin = this;
setupConstants();
// Set up the permissions manager // Set up the permissions manager
setupPermissionsManager(); setupPermissionsManager();

View File

@ -13,7 +13,7 @@ public class AuthMeCommand extends ExecutableCommand {
@Override @Override
public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) {
// Show some version info // Show some version info
sender.sendMessage(ChatColor.GREEN + "This server is running " + AuthMe.getPluginName() + " v" + AuthMe.getVersionName() + "! " + ChatColor.RED + "<3"); sender.sendMessage(ChatColor.GREEN + "This server is running " + AuthMe.getPluginName() + " v" + AuthMe.getPluginVersion() + " b" + AuthMe.getPluginBuildNumber()+ "! " + ChatColor.RED + "<3");
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + commandReference.get(0) + " help" + ChatColor.YELLOW + " to view help."); sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + commandReference.get(0) + " help" + ChatColor.YELLOW + " to view help.");
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + commandReference.get(0) + " about" + ChatColor.YELLOW + " to view about."); sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + commandReference.get(0) + " about" + ChatColor.YELLOW + " to view about.");
return true; return true;

View File

@ -3,6 +3,8 @@ package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.CommandParts;
import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.settings.Settings;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -24,8 +26,8 @@ public class VersionCommand extends ExecutableCommand {
@Override @Override
public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { public boolean executeCommand(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) {
// Show some version info // Show some version info
sender.sendMessage(ChatColor.GOLD + "==========[ " + AuthMe.getPluginName().toUpperCase() + " ABOUT ]=========="); sender.sendMessage(ChatColor.GOLD + "==========[ " + Settings.helpHeader.toUpperCase() + " ABOUT ]==========");
sender.sendMessage(ChatColor.GOLD + "Version: " + ChatColor.WHITE + AuthMe.getPluginName() + " v" + AuthMe.getVersionName() + ChatColor.GRAY + " (code: " + AuthMe.getVersionCode() + ")"); sender.sendMessage(ChatColor.GOLD + "Version: " + ChatColor.WHITE + AuthMe.getPluginName() + " v" + AuthMe.getPluginVersion() + ChatColor.GRAY + " (build: " + AuthMe.getPluginBuildNumber() + ")");
sender.sendMessage(ChatColor.GOLD + "Developers:"); sender.sendMessage(ChatColor.GOLD + "Developers:");
printDeveloper(sender, "Xephi", "xephi59", "Lead Developer"); printDeveloper(sender, "Xephi", "xephi59", "Lead Developer");
printDeveloper(sender, "DNx5", "DNx5", "Developer"); printDeveloper(sender, "DNx5", "DNx5", "Developer");

View File

@ -4,6 +4,8 @@ import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandDescription;
import fr.xephi.authme.command.CommandParts; import fr.xephi.authme.command.CommandParts;
import fr.xephi.authme.command.FoundCommandResult; import fr.xephi.authme.command.FoundCommandResult;
import fr.xephi.authme.settings.Settings;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -99,7 +101,7 @@ public class HelpProvider {
} }
// Print the help header // Print the help header
sender.sendMessage(ChatColor.GOLD + "==========[ " + AuthMe.getPluginName().toUpperCase() + " HELP ]=========="); sender.sendMessage(ChatColor.GOLD + "==========[ " + Settings.helpHeader.toUpperCase() + " HELP ]==========");
// Print the command help information // Print the command help information
if (showCommand) if (showCommand)

View File

@ -42,16 +42,16 @@ public class vAuthFileReader {
String name = line.split(": ")[0]; String name = line.split(": ")[0];
String password = line.split(": ")[1]; String password = line.split(": ")[1];
PlayerAuth auth; PlayerAuth auth;
if (isUUIDinstance(password)) { if (isUuidInstance(password)) {
String playerName; String pname;
try { try {
playerName = Bukkit.getOfflinePlayer(UUID.fromString(name)).getName(); pname = Bukkit.getOfflinePlayer(UUID.fromString(name)).getName();
} catch (Exception | NoSuchMethodError e) { } catch (Exception | NoSuchMethodError e) {
playerName = getName(UUID.fromString(name)); pname = getName(UUID.fromString(name));
} }
if (playerName == null) if (pname == null)
continue; continue;
auth = new PlayerAuth(playerName.toLowerCase(), password, "127.0.0.1", System.currentTimeMillis(), "your@email.com", playerName); auth = new PlayerAuth(pname.toLowerCase(), password, "127.0.0.1", System.currentTimeMillis(), "your@email.com", pname);
} else { } else {
auth = new PlayerAuth(name.toLowerCase(), password, "127.0.0.1", System.currentTimeMillis(), "your@email.com", name); auth = new PlayerAuth(name.toLowerCase(), password, "127.0.0.1", System.currentTimeMillis(), "your@email.com", name);
} }
@ -64,17 +64,8 @@ public class vAuthFileReader {
} }
/** private static boolean isUuidInstance(String s) {
* Method isUUIDinstance. return s.length() > 8 && s.charAt(8) == '-';
*
* @param s String
*
* @return boolean
*/
private boolean isUUIDinstance(String s) {
if (String.valueOf(s.charAt(8)).equalsIgnoreCase("-"))
return true;
return true;
} }
/** /**

View File

@ -69,7 +69,7 @@ public final class Settings extends YamlConfiguration {
broadcastWelcomeMessage, forceRegKick, forceRegLogin, broadcastWelcomeMessage, forceRegKick, forceRegLogin,
checkVeryGames, delayJoinLeaveMessages, noTeleport, applyBlindEffect, checkVeryGames, delayJoinLeaveMessages, noTeleport, applyBlindEffect,
customAttributes, generateImage, isRemoveSpeedEnabled, isMySQLWebsite; customAttributes, generateImage, isRemoveSpeedEnabled, isMySQLWebsite;
public static String getNickRegex, getUnloggedinGroup, getMySQLHost, public static String helpHeader, getNickRegex, getUnloggedinGroup, getMySQLHost,
getMySQLPort, getMySQLUsername, getMySQLPassword, getMySQLDatabase, getMySQLPort, getMySQLUsername, getMySQLPassword, getMySQLDatabase,
getMySQLTablename, getMySQLColumnName, getMySQLColumnPassword, getMySQLTablename, getMySQLColumnName, getMySQLColumnPassword,
getMySQLColumnIp, getMySQLColumnLastLogin, getMySQLColumnSalt, getMySQLColumnIp, getMySQLColumnLastLogin, getMySQLColumnSalt,
@ -129,6 +129,7 @@ public final class Settings extends YamlConfiguration {
public static void loadVariables() { public static void loadVariables() {
helpHeader = configFile.getString("settings.helpHeader", "AuthMeReloaded");
messagesLanguage = checkLang(configFile.getString("settings.messagesLanguage", "en").toLowerCase()); messagesLanguage = checkLang(configFile.getString("settings.messagesLanguage", "en").toLowerCase());
isPermissionCheckEnabled = configFile.getBoolean("permission.EnablePermissionCheck", false); isPermissionCheckEnabled = configFile.getBoolean("permission.EnablePermissionCheck", false);
isForcedRegistrationEnabled = configFile.getBoolean("settings.registration.force", true); isForcedRegistrationEnabled = configFile.getBoolean("settings.registration.force", true);
@ -551,6 +552,10 @@ public final class Settings extends YamlConfiguration {
set("Protection.countriesBlacklist", countriesBlacklist); set("Protection.countriesBlacklist", countriesBlacklist);
changes = true; changes = true;
} }
if (!contains("settings.helpHeader")) {
set("settings.helpHeader", "AuthMeReloaded");
changes = true;
}
if (!contains("settings.broadcastWelcomeMessage")) { if (!contains("settings.broadcastWelcomeMessage")) {
set("settings.broadcastWelcomeMessage", false); set("settings.broadcastWelcomeMessage", false);
changes = true; changes = true;

View File

@ -45,6 +45,8 @@ DataSource:
# Enable this when you allow registration through a website # Enable this when you allow registration through a website
mySQLWebsite: false mySQLWebsite: false
settings: settings:
# The name shown in the help messages.
helpHeader: AuthMeReloaded
sessions: sessions:
# Do you want to enable the session feature? # Do you want to enable the session feature?
# If enabled, when a player authenticates successfully, # If enabled, when a player authenticates successfully,

View File

@ -1,50 +1,50 @@
unknown_user: '&fПользователь не найден в Базе Данных' unknown_user: '&fПользователь не найден в Базе Данных'
unsafe_spawn: '&eВаше расположение перед выходом было опасным - вы перенесены на спавн' unsafe_spawn: '&eВаше расположение перед выходом было опасным - вы перенесены на спавн'
not_logged_in: '&cВы еще не вошли!' not_logged_in: '&c&lВы еще не вошли!'
reg_voluntarily: '&aЧтобы зарегистрироваться введите: &5/reg ПАРОЛЬ ПОВТОРАРОЛЯ' reg_voluntarily: '&aЧтобы зарегистрироваться введите: &e&l/reg ПАРОЛЬ ПОВТОРАРОЛЯ'
usage_log: '&eСинтаксис: &d/l ПАРОЛЬ &eили &d/login ПАРОЛЬ' usage_log: '&eСинтаксис: &d/l ПАРОЛЬ &eили &d/login ПАРОЛЬ'
wrong_pwd: '&4Неправильный пароль!' wrong_pwd: '&c&lНеправильный пароль!'
unregistered: '&6Вы успешно удалили свой аккаунт!' unregistered: '&6Вы успешно удалили свой аккаунт!'
reg_disabled: '&4Регистрация отключена' reg_disabled: '&c&lРегистрация отключена'
valid_session: '&aСессия открыта' valid_session: '&aСессия открыта'
login: '&2Вы успешно вошли!' login: '&a&lВы успешно вошли!'
vb_nonActiv: '&6Ваш аккаунт еще не активирован! Проверьте вашу почту!' vb_nonActiv: '&6Ваш аккаунт еще не активирован! Проверьте вашу почту!'
user_regged: '&4Такой игрок уже зарегистрирован' user_regged: '&c&lТакой игрок уже зарегистрирован'
usage_reg: '&спользование: &5/reg ПАРОЛЬ ПОВТОРАРОЛЯ' usage_reg: '&c&lИспользование: &e&l/reg ПАРОЛЬ ПОВТОРАРОЛЯ'
max_reg: '&4Вы превысили макс количество регистраций на ваш IP' max_reg: '&c&lВы превысили макс количество регистраций на ваш IP'
no_perm: '&4Недостаточно прав' no_perm: '&c&lНедостаточно прав'
error: '&4Произошла ошибка. Свяжитесь с администратором' error: '&c&lПроизошла ошибка. Свяжитесь с администратором'
login_msg: '&4Авторизация: &5/l ПАРОЛЬ' login_msg: '&a&lАвторизация: &e&l/l ПАРОЛЬ'
reg_msg: '&4Регистрация: &5/reg ПАРОЛЬ ПОВТОРАРОЛЯ' reg_msg: '&a&lРегистрация: &e&l/reg ПАРОЛЬ ПОВТОРАРОЛЯ'
password_error_nick: '&fВы не можете использовать ваш ник в роли пароля' password_error_nick: '&c&lВы не можете использовать ваш ник в роли пароля'
password_error_unsafe: '&fВы не можете использовать небезопасный пароль' password_error_unsafe: '&c&lВы не можете использовать небезопасный пароль'
reg_email_msg: '&4Регистрация: &5/reg EMAIL ПОВТОР_EMAIL' reg_email_msg: '&c&lРегистрация: &e&l/reg EMAIL ПОВТОР_EMAIL'
usage_unreg: '&спользование: &5/unregister ПАРОЛЬ' usage_unreg: '&c&lИспользование: &e&l/unregister ПАРОЛЬ'
pwd_changed: '&2Пароль изменен!' pwd_changed: '&2Пароль изменен!'
user_unknown: '&4Такой игрок не зарегистрирован' user_unknown: '&c&lТакой игрок не зарегистрирован'
password_error: '&4Пароль не совпадает' password_error: '&c&lПароль не совпадает'
invalid_session: '&4Сессия некорректна. Дождитесь, пока она закончится' invalid_session: '&c&lСессия некорректна. Дождитесь, пока она закончится'
reg_only: '&4Только для зарегистрированных! Посетите http://сайт_сервера.com/register/ для регистрации' reg_only: '&c&lТолько для зарегистрированных! Посетите http://сайт_сервера.com/register/ для регистрации'
logged_in: '&4Вы уже авторизированы!' logged_in: '&c&lВы уже авторизированы!'
logout: '&2Вы успешно вышли' logout: '&2Вы успешно вышли'
same_nick: '&4Такой игрок уже играет на сервере' same_nick: '&c&lТакой игрок уже играет на сервере'
registered: '&2Успешная регистрация!' registered: '&a&lУспешная регистрация!'
pass_len: '&4Твой пароль либо слишком длинный, либо слишком короткий' pass_len: '&c&lТвой пароль либо слишком длинный, либо слишком короткий'
reload: '&6Конфигурация и база данных перезагружены' reload: '&6Конфигурация и база данных перезагружены'
timeout: '&4Время для авторизации истекло' timeout: '&c&lВремя для авторизации истекло'
usage_changepassword: '&спользование: &5/changepassword СТАРЫЙ_ПАРОЛЬ НОВЫЙ_ПАРОЛЬ' usage_changepassword: '&c&lИспользование: &e&l/changepassword СТАРЫЙ_ПАРОЛЬ НОВЫЙ_ПАРОЛЬ'
name_len: '&4Ваш логин слишком длинный или слишком короткий' name_len: '&c&lВаш ник слишком длинный или слишком короткий'
regex: '&4Ваш логин содержит запрещенные символы. Разрешенные символы: REG_EX' regex: '&c&lВаш логин содержит запрещенные символы. Разрешенные символы: REG_EX'
add_email: '&обавьте свой email: &5/email add ВАШ_EMAIL ВАШ_EMAIL' add_email: '&c&lДобавьте свой email: &e&l/email add ВАШ_EMAIL ВАШ_EMAIL'
recovery_email: '&4Забыли пароль? Используйте &5/email recovery ВАШ_EMAIL' recovery_email: '&c&lЗабыли пароль? Используйте &e&l/email recovery ВАШ_EMAIL'
usage_captcha: '&4Вы должны ввести код, используйте: &5/captcha <theCaptcha>' usage_captcha: '&c&lВы должны ввести код, используйте: &e&l/captcha <theCaptcha>'
wrong_captcha: '&4Неверный код, используйте: &5/captcha THE_CAPTCHA' wrong_captcha: '&c&lНеверный код, используйте: &e&l/captcha THE_CAPTCHA'
valid_captcha: '&2Вы успешно ввели код!' valid_captcha: '&2Вы успешно ввели код!'
kick_forvip: '&6VIP игрок зашел на переполненный сервер!' kick_forvip: '&6VIP игрок зашел на переполненный сервер!'
kick_fullserver: '&4Сервер переполнен!' kick_fullserver: '&c&lСервер переполнен!'
usage_email_add: '&спользование: &5/email add ВАШ_EMAIL ПОВТОР_EMAIL' usage_email_add: '&c&lИспользование: &e&l/email add ВАШ_EMAIL ПОВТОР_EMAIL'
usage_email_change: '&спользование: &5/email change СТАРЫЙ_EMAIL НОВЫЙ_EMAIL' usage_email_change: '&c&lИспользование: &e&l/email change СТАРЫЙ_EMAIL НОВЫЙ_EMAIL'
usage_email_recovery: '&4Использование: /email recovery EMAIL' usage_email_recovery: '&c&lИспользование: /email recovery EMAIL'
new_email_invalid: '[AuthMe] Недействительный новый email!' new_email_invalid: '[AuthMe] Недействительный новый email!'
old_email_invalid: '[AuthMe] Недействительный старый email!' old_email_invalid: '[AuthMe] Недействительный старый email!'
email_invalid: '[AuthMe] Недействительный email' email_invalid: '[AuthMe] Недействительный email'
@ -53,5 +53,5 @@ email_confirm: '[AuthMe] Подтвердите ваш Email!'
email_changed: '[AuthMe] Email изменен!' email_changed: '[AuthMe] Email изменен!'
email_send: '[AuthMe] Письмо с инструкциями для восстановления было отправлено на ваш Email!' email_send: '[AuthMe] Письмо с инструкциями для восстановления было отправлено на ваш Email!'
country_banned: 'Вход с IP-адресов вашей страны воспрещен на этом сервере' country_banned: 'Вход с IP-адресов вашей страны воспрещен на этом сервере'
antibot_auto_enabled: '[AuthMe] AntiBot-режим автоматически включен из-за большого количества входов!' antibot_auto_enabled: '&a[AuthMe] AntiBot-режим автоматически включен из-за большого количества входов!'
antibot_auto_disabled: '[AuthMe] AntiBot-режим автоматичски отключен после %m мин. Надеюсь атака закончилась' antibot_auto_disabled: '&a[AuthMe] AntiBot-режим автоматичски отключен после %m мин. Надеюсь атака закончилась'

View File

@ -3,7 +3,7 @@ authors: [${pluginAuthors}]
website: ${project.url} website: ${project.url}
description: ${project.description} description: ${project.description}
main: ${mainClass} main: ${mainClass}
version: ${project.version}-b${env.BUILD_NUMBER} version: ${project.version}-b${buildNumber}
softdepend: softdepend:
- Vault - Vault
- PermissionsBukkit - PermissionsBukkit