diff --git a/README.md b/README.md index 6adb974..25a6120 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,44 @@ -SimpleAuthHelper -================ +# SimpleAuthHelper -* Summary: Simplifies the SimpleAuth login process -* Dependency Plugins: n/a -* PocketMine-MP version: 1.4 - API 1.10.0 +* Summary: Simplifies the way people authenticate to servers +* Dependency Plugins: SimpleAuth +* PocketMine-MP version: 1.5 (API:1.12.0) * DependencyPlugins: SimpleAuth * OptionalPlugins: - * Categories: General * Plugin Access: Commands * WebSite: [github](https://github.com/alejandroliu/pocketmine-plugins/tree/master/SimpleAuthHelper) -Overview --------- +## Overview -Very simple plugin that simplifies the login process... Instead of + + + +**DO NOT POST QUESTION/BUG-REPORTS/REQUESTS IN THE REVIEWS** + +It is difficult to carry a conversation in the reviews. If you +have a question/bug-report/request please use the +[Thread](http://forums.pocketmine.net/threads/simpleauthhelper.8074/) for +that. You are more likely to get a response and help that way. + +_NOTE:_ + +This documentation was last updated for version **2.0.1**. + + +You can also download this plugin from this [page](https://github.com/alejandroliu/pocketmine-plugins/releases/tag/SimpleAuthHelper-2.0.1). + + + +A plugin that simplifies the login process... Instead of asking for commands, users simply chat away... -### Register process +I also provides for a number of tweaks that can improve the usability of +[SimpleAuth](https://forums.pocketmine.net/plugins/simpleauth.4/). + +#### Register process Player connects for the first time. They are prompted to enter a *NEW* password. They enter their password directly, without having to @@ -27,64 +47,133 @@ enter */register*. They are asked for the password again to confirm. They re-enter their password (again without */register*). -### Login process +#### Login process Player connects again. They are prompted to enter their login password. They type their login password directly (without */login*). And they are in. -Documentation -------------- - -As a bonus, it can start a player with initial inventory upon -registration. This is configured through the `nest-egg` setting. +## Documentation ### Commands -* *chpwd* __ - Used by players to change their passwords. -* *resetpwd* __ - Used by ops to reset a players password. This actually unregisters - the password. +* *chpwd* _<old-pwd>_ + * Used by players to change their passwords. +* *resetpwd* _<player>_ + * Used by ops to reset a players password. This actually unregisters + the password. +* *preregister* _<player>_ _<passwd>_ + * Used by ops to pre-register players. +* *logout* + * De-authenticates a player. + +### Permission Nodes + +* simpleauthhelper.command.chpwd : Allow users to change passwords +* simpleauthhelper.command.logout : Allow users to logout +* simpleauthhelper.command.resetpwd : Allow ops to reset other's passwords + (Defaults to Op) +* simpleauthhelper.command.prereg : Allow ops to pre-register users + (Defaults to Op) + ### Configuration - --- - messages: - re-enter pwd: 'Please re-enter password to confirm:' - passwords dont match: |- - Passwords do not match. - Please try again! - Enter a new password: - register ok: You have been registered! - no spaces: |- - Password should not contain spaces - or tabs - not name: Password should not be your name - too many login: You have attempted to login too many times. - login timeout: Login timer expired! - nest-egg: - - "272:0:1" - - "17:0:16" - - "364:0:5" - - "266:0:10" - max-attempts: 5 - login-timeout: 60 - auto-ban: false - ... - -* The section `messages` can be used to configure displayed texts. -* `nest-egg` section contains list of items that will be given to the - player upon registration. -* `max-attempts` counts the number of tries to login. -* `login-timeout` will kick the player out if not authenticated in - that number of seconds. -* `auto-ban`: If set to true it will automatically ban the IP of a - player that does too many login attempts. - -Changes -------- +Configuration is through the `config.yml` file. +The following sections are defined: + +#### main + + +Configure the different features used by this plugin. + + feature: true|false + +If `true` the feature is enabled. if `false` the feature is disabled. +* max-attemps: kick player after this many login attempts. NOTE: This conflicts with SimpleAuth's blockAfterFail setting +* login-timeout: must authenticate within this number of seconds +* leet-mode: lets players use also /login and /register +* chat-protect: prevent player to display their password in chat +* hide-unauth: EXPERIMENTAL, hide unauthenticated players +* event-fixer: EXPERIMENTAL, cancels additional events for unauthenticated players +* hack-login-perms: EXPERIMENTAL, overrides login permisions to make sure players can login +* hack-register-perms: EXPERIMENTAL, overrides register permisions to make sure players can register +* db-monitor: EXPERIMENTAL, enable database server monitoring +* monitor-settings: Configure database monitor settings +#### monitor-settings + +* canary-account: account to query this account is tested to check database proper operations +* check-interval: how to often to check database (seconds) + + +## Translations + +This plugin will honour the server language configuration. The +languages currently available are: + +* English +* Spanish + +You can provide your own message file by creating a file called +`messages.ini` in the plugin config directory. Check +[github](https://github.com/alejandroliu/pocketmine-plugins/tree/master/SimpleAuthHelper/resources/messages) +for sample files. + +## Player pre-registration + +It is possible to implement a web based pre-registration system with this +plugin. + +1. *rcon* must be enabled on the PocketMine server. +2. web server must be able to send *rcon* commands to PocketMine. +3. Enable the *whitelist* functionality in PocketMine. +4. Install *SimpleAuth* and *SimpleAuthHelper*. +5. **Optionally** install *PurePerms* and disable + `simpleauthhelper.command.chpwd` permission. You probably want + users to change passwords from the web site. +6. Whenever a user registers in web site, the web site script uses *rcon* + to send the follwoing: + - whitelist add _player_ + - preregister _player_ _passwd_ +7. Whenever a user changes password in web site, we use *rcon* with: + - resetpwd _player_ + - preregister _player_ _passwd_ + +## Database Monitor + +This module is responsible for monitoring the SimpleAuth data provider +to make sure that it is up and running and disable logins if it is not +available. + +It kicks off a background task that will poll the SimpleAuth data provider +by trying to retrieve the data from the "canary-account". It is important +that you have configured and have working SimpleAuth provider the first +time you enable the database monitor. This is because the "canary-account" +needs to be created (if it doesn't exist already). + +On a regular interval, the SimpleAuth +data provider is checked. If it is not running, all unauthenticated players +are kicked and any new joins are not allowed. + +## Issues + +* Event Fixer: Crafting canceling doesn't work + +# Changes + +* 2.0.1: language defaults + - make sure that languages default to English (reported by @minebuilder0110) +* 2.0.0: Major upgrade + - uses now a common translation library + - Removed little used feature: nest-egg + - leet-mode also works for /register. + - Removed auto-ban. It is now done in SimpleAuth. + - Added support for hiding unauthenticated players (Suggested by @CaptainKenji17) + - Added pre-register and logout command + - forces permissions to be set + - Added a task to monitor database server status + - Thanks @rvachvg for helping debug this. * 1.2.3: Security improvements - prevent user from chatting away their password - add option so that players can also use "/login" to login. @@ -93,7 +182,7 @@ Changes * 1.2.1: CallbackTask deprecation * Removed CallbackTask deprecation warnings * 1.2.0: max-logins - * Suggestion from MCPEPIG + * Suggestion from @MCPEPIG - kick user out after `max-attempts`. - Added a chpwd command. * Kick user out if not authenticated after `timeout` seconds. @@ -103,8 +192,7 @@ Changes * Messages can be configured. * 1.0.0: First release -Copyright ---------- +# Copyright SimpleAuthHelper Copyright (C) 2015 Alejandro Liu @@ -122,3 +210,4 @@ Copyright You should have received a copy of the GNU General Public License along with this program. If not, see . + diff --git a/plugin.yml b/plugin.yml index a01ad84..5953e5b 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,11 +1,11 @@ main: aliuly\helper\Main -api: 1.10.0 +api: 1.12.0 load: POSTWORLD depend: [SimpleAuth] name: SimpleAuthHelper description: Simplifies the way people authenticate to servers -version: 1.2.3 +version: 2.0.1 author: aliuly commands: @@ -13,15 +13,32 @@ commands: description: "Change password" usage: "/chpwd " permission: simpleauthhelper.command.chpwd + aliases: [passwd] resetpwd: description: "Reset password" usage: "/resetpwd " permission: simpleauthhelper.command.resetpwd + aliases: [resetpw] + preregister: + description: "pre-register player" + usage: "/preregister " + permission: simpleauthhelper.command.prereg + aliases: [prereg] + logout: + description: "logout player" + usage: "/logout" + permission: simpleauthhelper.command.logout permissions: simpleauthhelper.command.chpwd: default: true description: "Allow users to change passwords" + simpleauthhelper.command.logout: + default: true + description: "Allow users to logout" simpleauthhelper.command.resetpwd: default: op description: "Allow ops to reset other's passwords" + simpleauthhelper.command.prereg: + default: op + description: "Allow ops to pre-register users" diff --git a/resources/messages/eng.ini b/resources/messages/eng.ini new file mode 100644 index 0000000..8997756 --- /dev/null +++ b/resources/messages/eng.ini @@ -0,0 +1,51 @@ +; eng.ini +"%1%-mode"="" +"%1% already registered"="" +"%1% has just joined"="" +"%1% unauthenticated players were kicked"="" +"%1% unregistered"="" +"**CENSORED**"="" +""="" +"Adventure"="" +"Creative"="" +"DBM Error: %1%"="" +"Database connectivity restored!"="" +"Database is experiencing technical difficulties"="" +"Detected loss of database connectivity!"="" +"Disabling SimpleAuth"="" +"Enabling SimpleAuth"="" +"Fixing %1% for %2%"="" +"LOST DATABASE CONNECTION!"="" +"Restored database connection"="" +"SimpleAuthHelper has been disabled"="" +"SimpleAuth not found!"="" +"Spectator"="" +"Survival"="" +"This command only works in-game."="" +"Unable to find SimpleAuth"="" +"Unable to unregister %1%"="" +"You are no longer registered!"="" +"You can only do this in-game"="" +"You do not have permission to do that."="" +"auth error"="Authentication error. Try again later!" +"chat protected"="Do not send your password on the chat window" +"chpwd error"="Old password does not match" +"chpwd msg"="Enter your new password:" +"chpwd ok"="Password changed succesfully" +"error registering %1%"="" +"login first"="" +"login timeout"="Login timer expired!" +"logout completed"="" +"no spaces"="Password should not contain spaces or tabs" +"not name"="Password should not be your name" +"one unauthenticated player was kicked"="" +"passwords dont match"="Passwords do not match.\nPlease try again!\nEnter a new password:" +"re-enter pwd"="Please re-enter password to confirm:" +"register.error.password %1%"="Your password should be at least %1% characters" +"registered %1%"="" +"register first"="You must first be registered" +"register ok"="You have been registered!" +"registration error"="Registration error. Try again later!" +"snob login"="Actually, you don't really need to type /login" +"snob register"="Actually, you don't really need to type /register" +"too many logins"="You have attempted to login too many times." diff --git a/resources/messages/messages.ini b/resources/messages/messages.ini new file mode 100644 index 0000000..5604421 --- /dev/null +++ b/resources/messages/messages.ini @@ -0,0 +1,51 @@ +; messages.ini +"%1%-mode"="" +"%1% already registered"="" +"%1% has just joined"="" +"%1% unauthenticated players were kicked"="" +"%1% unregistered"="" +"**CENSORED**"="" +""="" +"Adventure"="" +"Creative"="" +"DBM Error: %1%"="" +"Database connectivity restored!"="" +"Database is experiencing technical difficulties"="" +"Detected loss of database connectivity!"="" +"Disabling SimpleAuth"="" +"Enabling SimpleAuth"="" +"Fixing %1% for %2%"="" +"LOST DATABASE CONNECTION!"="" +"Restored database connection"="" +"SimpleAuthHelper has been disabled"="" +"SimpleAuth not found!"="" +"Spectator"="" +"Survival"="" +"This command only works in-game."="" +"Unable to find SimpleAuth"="" +"Unable to unregister %1%"="" +"You are no longer registered!"="" +"You can only do this in-game"="" +"You do not have permission to do that."="" +"auth error"="" +"chat protected"="" +"chpwd error"="" +"chpwd msg"="" +"chpwd ok"="" +"error registering %1%"="" +"login first"="" +"login timeout"="" +"logout completed"="" +"no spaces"="" +"not name"="" +"one unauthenticated player was kicked"="" +"passwords dont match"="" +"re-enter pwd"="" +"register.error.password %1%"="" +"registered %1%"="" +"register first"="" +"register ok"="" +"registration error"="" +"snob login"="" +"snob register"="" +"too many logins"="" diff --git a/resources/messages/spa.ini b/resources/messages/spa.ini new file mode 100644 index 0000000..d779ec6 --- /dev/null +++ b/resources/messages/spa.ini @@ -0,0 +1,51 @@ +; spa.ini +"%1%-mode"="modo-%1%" +"%1% already registered"="%1% ya esta registrado" +"%1% has just joined"="%1% se ha conectado al juego" +"%1% unauthenticated players were kicked"="%1% jugadores deconocidos retirados" +"%1% unregistered"="%1% de-registrado" +"**CENSORED**"="**CENSURADO**" +""="no" +"Adventure"="Aventura" +"Creative"="Creativo" +"DBM Error: %1%"="Error DBM: %1%" +"Database connectivity restored!"="Acceso a base de datos restaurado" +"Database is experiencing technical difficulties"="Base datos en problemas" +"Detected loss of database connectivity!"="Acceso a base de datos perdido" +"Disabling SimpleAuth"="Deshabilitando SimpleAuth" +"Enabling SimpleAuth"="Rehabilitando SimpleAuth" +"Fixing %1% for %2%"="Arreglando %1% para %2%" +"LOST DATABASE CONNECTION!"="SE PERDIO LA BASE DE DATOS" +"Restored database connection"="Base de datos retaurada" +"SimpleAuthHelper has been disabled"="SimpleAuthHelper ha sido des-habilitado" +"SimpleAuth not found!"="No se encotró SimpleAuth!" +"Spectator"="Espectador" +"Survival"="Supervivencia" +"This command only works in-game."="Esto solo funciona dentro del juego" +"Unable to find SimpleAuth"="No encuentro SimleAuth" +"Unable to unregister %1%"="No se puede de-registrar a %1%" +"You are no longer registered!"="Usted ya no está registrado!" +"You can only do this in-game"="Solo puede hacer esto en el juego" +"You do not have permission to do that."="No tiene permiso para hacer eso" +"auth error"="No se pudo identificar. Pruebe más tarde!" +"chat protected"="No ingrese su contraseña en la pantalla" +"chpwd error"="No concuerda con su contraseña anterior" +"chpwd msg"="Ingrese una nueva contraseña" +"chpwd ok"="Su contraseña ha sido canbiada" +"error registering %1%"="Error registrando a %1%" +"login first"="Identifiquese primero" +"login timeout"="Se demoró mucho en indentificarse!" +"logout completed"="Se des-identificado" +"no spaces"="contraseña no puede tener espacios o tabs" +"not name"="no use su nombre como contraseña" +"one unauthenticated player was kicked"="un jugador desconocido retirado" +"passwords dont match"="contraseñas no concuerdan.\nPor favor intente de nuevo.\nIngrese nueva contraseña:" +"re-enter pwd"="Vuelva a ingresar su contraseña para confirmar:" +"register.error.password %1%"="su contraseña es muy corta (necesita %1% letras)" +"registered %1%"="Registrando %1%" +"register first"="Tiene que registrarse primero" +"register ok"="Usted ha sido registrado." +"registration error"="No se pudo registrar. Intente más tarde!" +"snob login"="En realidad, no necesita ingresar /login" +"snob register"="En realidad, no necesita ingresar /register" +"too many logins"="Ha intentado indentificarse demasiadas veces" diff --git a/src/aliuly/helper/DbMonitorTask.php b/src/aliuly/helper/DbMonitorTask.php new file mode 100644 index 0000000..83203b0 --- /dev/null +++ b/src/aliuly/helper/DbMonitorTask.php @@ -0,0 +1,154 @@ + "account to query",//this account is tested to check database proper operations + "canary-account" => "test user", + "# check-interval" => "how to often to check database (seconds)", + "check-interval" => 600, + ]; + } + public function __construct(HelperPlugin $owner,$cfg){ + parent::__construct($owner); + $this->canary = $cfg["canary-account"]; + if ($owner->auth->isEnabled()) { + $this->dbm = $owner->auth->getDataProvider(); + $this->ok = true; // Assume things are OK... + if (!$this->pollDB()) { + // If this fails then canary account doesn't exist yet... create it + $player = $this->getOwner()->getServer()->getOfflinePlayer($this->canary); + if ($player === null) { + throw new \RuntimeException("canary account definition error!"); + return; + } + $err = $this->dbm->registerPlayer($player,"N/A"); + if ($err === null) { + throw new \RuntimeException("Unable to register canary account!"); + } + } + } else { + $this->ok = false; + } + + $owner->getServer()->getScheduler()->scheduleRepeatingTask($this,$cfg["check-interval"]*20); + $owner->getServer()->getPluginManager()->registerEvents($this, $owner); + } + private function setStatus($mode) { + if ($this->ok === $mode) return; + $this->ok = $mode; + if ($mode) { + $this->getOwner()->getLogger()->info(mc::_("Restored database connection")); + $this->getOwner()->getServer()->broadcastMessage(TextFormat::GREEN.mc::_("Database connectivity restored!")); + return; + } + $this->getOwner()->getLogger()->error(mc::_("LOST DATABASE CONNECTION!")); + $this->getOwner()->getServer()->broadcastMessage(TextFormat::RED.mc::_("Detected loss of database connectivity!")); + // Kick all unregistered players... + $auth = $this->getOwner()->getServer()->getPluginManager()->getPlugin("SimpleAuth"); + if ($auth !== null) { + $cnt = 0; + foreach ($this->getOwner()->getServer()->getOnlinePlayers() as $ll) { + if (!$auth->isPlayerAuthenticated($ll)) { + $this->delayedKick($ll,mc::_("Database is experiencing technical difficulties")); + ++$cnt; + } + } + if ($cnt) + $this->getOwner()->getServer()->broadcastMessage( + TextFormat::BLUE. + mc::n( + mc::_("one unauthenticated player was kicked"), + mc::_("%1% unauthenticated players were kicked", $cnt), + $cnt + ) + ); + return; + } + } + private function enableAuth($mgr,$auth) { + if ($auth === null) return false; // OK, this is weird! + if ($auth->isEnabled()) return true; + $this->getOwner()->getLogger()->info(mc::_("Enabling SimpleAuth")); + $mgr->enablePlugin($auth); + if (!$auth->isEnabled()) return false; + $this->dbm = $auth->getDataProvider(); + return true; + } + private function pollDB() { + $player = $this->getOwner()->getServer()->getOfflinePlayer($this->canary); + if ($player == null) return true;//Automatically assume things are OK :) + try { + return $this->dbm->isPlayerRegistered($player); + } catch (\Exception $e) { + $this->getOwner()->getLogger()->error(mc::_("DBM Error: %1%",$e->getMessage())); + } + return false; + } + + public function onRun($currentTicks){ + $mgr = $this->getOwner()->getServer()->getPluginManager(); + $auth = $mgr->getPlugin("SimpleAuth"); + if ($auth === null) return; // OK, this is weird! + + if (!$auth->isEnabled()) { + if (!$this->enableAuth($mgr,$auth)) return; // Ouch... + } + if ($this->pollDB()) { + $this->setStatus(true); + return; + } + /* + * Lost connection to database... + */ + $this->setStatus(false); + /* + * let's try to reconnect by resetting SimpleAuth + */ + if ($auth->isEnabled()) { + $this->getOwner()->getLogger()->info(mc::_("Disabling SimpleAuth")); + $mgr->disablePlugin($auth); + } + if (!$auth->isEnabled()) { + $this->getOwner()->getLogger()->info(mc::_("Enabling SimpleAuth")); + if (!$this->enableAuth($mgr,$auth)) return; // Ouch... + } + if ($this->pollDB()) $this->setStatus(true); + } + public function doKick($n,$msg) { + $pl = $this->getOwner()->getServer()->getPlayer($n); + if ($pl === null) return; + $pl->kick($msg); + } + private function delayedKick($pl,$msg) { + $this->getOwner()->getServer()->getScheduler()->scheduleDelayedTask( + new PluginCallbackTask($this->getOwner(),[$this,"doKick"],[$pl->getName(),$msg]), + 10 + ); + } + public function onConnect(PlayerLoginEvent $ev) { + $this->onRun(0); + } + public function onJoin(PlayerJoinEvent $ev) { + if ($this->ok) return; + $this->delayedKick($ev->getPlayer(),mc::_("Database is experiencing technical difficulties")); + } +} diff --git a/src/aliuly/helper/EventListener.php b/src/aliuly/helper/EventListener.php new file mode 100644 index 0000000..1cd80b8 --- /dev/null +++ b/src/aliuly/helper/EventListener.php @@ -0,0 +1,177 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ +namespace aliuly\helper; + +use pocketmine\event\block\BlockBreakEvent; +use pocketmine\event\block\BlockPlaceEvent; +use pocketmine\event\inventory\InventoryOpenEvent; +use pocketmine\event\inventory\InventoryPickupItemEvent; +use pocketmine\event\Listener; +use pocketmine\event\player\PlayerCommandPreprocessEvent; +use pocketmine\event\player\PlayerDropItemEvent; +use pocketmine\event\player\PlayerInteractEvent; +use pocketmine\event\player\PlayerItemConsumeEvent; +use pocketmine\event\player\PlayerJoinEvent; +use pocketmine\event\player\PlayerMoveEvent; +use pocketmine\event\player\PlayerPreLoginEvent; +use pocketmine\event\player\PlayerQuitEvent; +use pocketmine\event\player\PlayerRespawnEvent; +use pocketmine\event\entity\EntityDamageEvent; +use pocketmine\event\entity\EntityDamageByEntityEvent; +use pocketmine\event\inventory\CraftItemEvent; +use pocketmine\Player; +use pocketmine\inventory\PlayerInventory; +use aliuly\helper\Main as HelperPlugin; + + +class EventListener implements Listener{ + /** @var SimpleAuth */ + private $auth; + private $owner; + + public function __construct(HelperPlugin $owner){ + $this->auth = $owner->auth; + $owner->getServer()->getPluginManager()->registerEvents($this, $owner); + } + + /** + * @priority LOWEST + */ + public function onCrafting(CraftItemEvent $event){ + foreach ($event->getTransaction()->getInventories() as $inv) { + if (($inv instanceof PlayerInventory)) continue; + $player = $inv->getHolder(); + if (!$this->auth->isPlayerAuthenticated($inv->getHolder())) { + $event->setCancelled(true); + return; + } + } + } + + /** + * @param PlayerMoveEvent $event + * + * @priority LOWEST + */ + public function onPlayerMove(PlayerMoveEvent $event){ + if(!$this->auth->isPlayerAuthenticated($event->getPlayer())){ + if(!$event->getPlayer()->hasPermission("simpleauth.move")){ + $event->setCancelled(true); + $event->getPlayer()->onGround = true; + } + } + } + + /** + * @param PlayerInteractEvent $event + * + * @priority HIGHEST + */ + public function onPlayerInteract(PlayerInteractEvent $event){ + + if(!$this->auth->isPlayerAuthenticated($event->getPlayer())){ + $event->setCancelled(true); + } + } + + /** + * @param PlayerDropItemEvent $event + * + * @priority HIGHEST + */ + public function onPlayerDropItem(PlayerDropItemEvent $event){ + if(!$this->auth->isPlayerAuthenticated($event->getPlayer())){ + $event->setCancelled(true); + } + } + + /** + * @param PlayerItemConsumeEvent $event + * + * @priority LOWEST + */ + public function onPlayerItemConsume(PlayerItemConsumeEvent $event){ + if(!$this->auth->isPlayerAuthenticated($event->getPlayer())){ + $event->setCancelled(true); + } + } + + /** + * @param EntityDamageEvent $event + * + * @priority LOWEST + */ + public function onEntityDamage(EntityDamageEvent $event){ + if($event->getEntity() instanceof Player and !$this->auth->isPlayerAuthenticated($event->getEntity())){ + $event->setCancelled(true); + return; + } + // Also check if we are inflicting damage to others + if(!($event instanceof EntityDamageByEntityEvent)) return; + $giver = $event->getDamager(); + if (!($giver instanceof Player)) return; + if (!$this->auth->isPlayerAuthenticated($giver)) { + $event->setCancelled(true); + return; + } + } + + /** + * @param BlockBreakEvent $event + * + * @priority LOWEST + */ + public function onBlockBreak(BlockBreakEvent $event){ + if($event->getPlayer() instanceof Player and !$this->auth->isPlayerAuthenticated($event->getPlayer())){ + $event->setCancelled(true); + } + } + + /** + * @param BlockPlaceEvent $event + * + * @priority LOWEST + */ + public function onBlockPlace(BlockPlaceEvent $event){ + if($event->getPlayer() instanceof Player and !$this->auth->isPlayerAuthenticated($event->getPlayer())){ + $event->setCancelled(true); + } + } + + /** + * @param InventoryOpenEvent $event + * + * @priority LOWEST + */ + public function onInventoryOpen(InventoryOpenEvent $event){ + if(!$this->auth->isPlayerAuthenticated($event->getPlayer())){ + $event->setCancelled(true); + } + } + + /** + * @param InventoryPickupItemEvent $event + * + * @priority LOWEST + */ + public function onPickupItem(InventoryPickupItemEvent $event){ + $player = $event->getInventory()->getHolder(); + if($player instanceof Player and !$this->auth->isPlayerAuthenticated($player)){ + $event->setCancelled(true); + } + } +} diff --git a/src/aliuly/helper/Main.php b/src/aliuly/helper/Main.php index 5e9938c..0ec4b57 100644 --- a/src/aliuly/helper/Main.php +++ b/src/aliuly/helper/Main.php @@ -1,4 +1,16 @@ getDataFolder())) mkdir($this->getDataFolder()); + if (mc::plugin_init($this,$this->getFile()) === false) { + file_put_contents($this->getDataFolder()."messages.ini",MPMU::getResourceContents($this,"messages/eng.ini")."\n\"\"=\"yes\"\n"); + mc::plugin_init($this,$this->getFile()); + $this->getLogger()->error(TextFormat::RED."Your selected language \"".$this->getServer()->getProperty("settings.language")."\" is not supported"); + $this->getLogger()->error(TextFormat::YELLOW."Creating a custom \"messages.ini\" with English strings"); + $this->getLogger()->error(TextFormat::AQUA."Please consider translating and submitting a translation"); + $this->getLogger()->error(TextFormat::AQUA."to the developer"); + } else { + if (mc::_("") === "yes") { + $this->getLogger()->error(TextFormat::RED."Your selected language \"".$this->getServer()->getProperty("settings.language")."\" is not supported"); + $this->getLogger()->error(TextFormat::AQUA."Please consider translating \"messages.ini\""); + $this->getLogger()->error(TextFormat::AQUA."and submitting a translation to the developer"); + } + } $this->auth = $this->getServer()->getPluginManager()->getPlugin("SimpleAuth"); if (!$this->auth) { - $this->getLogger()->info(TextFormat::RED."Unable to find SimpleAuth"); + $this->getLogger()->error(TextFormat::RED.mc::_("Unable to find SimpleAuth")); + throw new \RuntimeException("Missing Dependancy"); return; } - if (!is_dir($this->getDataFolder())) mkdir($this->getDataFolder()); + $defaults = [ "version" => $this->getDescription()->getVersion(), - "messages" => [ - "re-enter pwd" => "Please re-enter password to confirm:", - "passwords dont match" => "Passwords do not match.\nPlease try again!\nEnter a new password:", - "register ok" => "You have been registered!", - "no spaces" => "Password should not contain spaces or tabs", - "not name" => "Password should not be your name", - "too many logins" => "You have attempted to login too many times.", - "login timeout" => "Login timer expired!", - "register first" => "You must first be registered", - "chpwd msg" => "Enter your new password:", - "chpwd error" => "Old password does not match", - "chpwd ok" => "Password changed succesfully", - "registration error" => "Registration error. Try again later!", - "auth error" => "Authentication error. Try again later!", - "chat protected" => "Do not send your password on the chat window", - "snob login" => "Actually, you don't really need to type /login", - ], - "nest-egg" => [ - "STONE_SWORD:0:1", - "WOOD:0:16", - "COOKED_BEEF:0:5", - "GOLD_INGOT:0:10", - ], + "# max-attemps" => "kick player after this many login attempts. ",// NOTE: This conflicts with SimpleAuth's blockAfterFail setting "max-attempts" => 5, + "# login-timeout" => "must authenticate within this number of seconds", "login-timeout" => 60, - "auto-ban" => false, - "lamer-mode" => false, + "# leet-mode" => "lets players use also /login and /register", + "leet-mode" => true, + "# chat-protect" => "prevent player to display their password in chat", "chat-protect" => false, + "# hide-unauth" => "EXPERIMENTAL, hide unauthenticated players", + "hide-unauth" => false, + "# event-fixer" => "EXPERIMENTAL, cancels additional events",// for unauthenticated players + "event-fixer" => false, + "# hack-login-perms" => "EXPERIMENTAL, overrides login permisions",//to make sure players can login + "hack-login-perms" => false, + "# hack-register-perms" => "EXPERIMENTAL, overrides register permisions",//to make sure players can register + "hack-register-perms" => false, + "# db-monitor" => "EXPERIMENTAL, enable database server monitoring", + "db-monitor" => false, + "# monitor-settings" => "Configure database monitor settings", + "monitor-settings" => DbMonitorTask::defaults(), ]; - if (file_exists($this->getDataFolder()."config.yml")) { - unset($defaults["nest-egg"]); - } $this->cfg=(new Config($this->getDataFolder()."config.yml", Config::YAML,$defaults))->getAll(); - $this->getServer()->getPluginManager()->registerEvents($this, $this); + $this->getServer()->getPluginManager()->registerEvents($this,$this); + if ($this->cfg["event-fixer"]) { + $this->listener =new EventListener($this); + } + if ($this->cfg["hack-login-perms"] || $this->cfg["hack-register-perms"]) { + $this->permshacker = new PermsHacker($this,$this->cfg["hack-login-perms"],$this->cfg["hack-register-perms"]); + } + if ($this->cfg["db-monitor"]) { + $this->monitor = new DbMonitorTask($this,$this->cfg["monitor-settings"]); + } $this->pwds = []; } ////////////////////////////////////////////////////////////////////// @@ -79,9 +122,30 @@ public function onPlayerQuit(PlayerQuitEvent $ev) { if (isset($this->chpwd[$n])) unset($this->chpwd[$n]); } public function onPlayerJoin(PlayerJoinEvent $ev) { - if ($this->cfg["login-timeout"] == 0) return; - $n = $ev->getPlayer()->getName(); - $this->getServer()->getScheduler()->scheduleDelayedTask(new PluginCallbackTask($this,[$this,"checkTimeout"],[$n]),$this->cfg["login-timeout"]*20); + if ($this->cfg["login-timeout"] !== 0) { + $n = $ev->getPlayer()->getName(); + $this->getServer()->getScheduler()->scheduleDelayedTask(new PluginCallbackTask($this,[$this,"checkTimeout"],[$n]),$this->cfg["login-timeout"]*20); + } + if ($this->cfg["hide-unauth"]) { + $p = $ev->getPlayer(); + foreach($this->getServer()->getOnlinePlayers() as $online){ + $online->hidePlayer($p); + $p->hidePlayer($online); + } + $ev->setJoinMessage(""); + // + } + } + public function onAuthenticate(PlayerAuthenticateEvent $ev) { + if (!$this->cfg["hide-unauth"]) return; + $pl = $ev->getPlayer(); + $this->getServer()->broadcastMessage(TextFormat::YELLOW.mc::_("%1% has just joined", $pl->getDisplayName())); + foreach($this->getServer()->getOnlinePlayers() as $online){ + $online->showPlayer($pl); + if ($this->auth->isPlayerAuthenticated($online)) { + $pl->showPlayer($online); + } + } } /** * @priority LOW @@ -93,8 +157,8 @@ public function onPlayerCmd(PlayerCommandPreprocessEvent $ev) { if ($this->auth->isPlayerAuthenticated($pl) && !isset($this->chpwd[$n])) { if ($this->cfg["chat-protect"]) { if ($this->authenticate($pl,$ev->getMessage())) { - $pl->sendMessage($this->cfg["messages"]["chat protected"]); - $ev->setMessage("**CENSORED**"); + $pl->sendMessage(TextFormat::RED.mc::_("chat protected")); + $ev->setMessage(mc::_("**CENSORED**")); $ev->setCancelled(); } } @@ -103,13 +167,17 @@ public function onPlayerCmd(PlayerCommandPreprocessEvent $ev) { if (!$this->auth->isPlayerRegistered($pl) || isset($this->chpwd[$n])) { if (!isset($this->pwds[$n])) { + if ($this->cfg["leet-mode"] && preg_match(self::RE_REGISTER,$ev->getMessage())) { + $pl->sendMessage(TextFormat::YELLOW.mc::_("snob register")); + $ev->setMessage(preg_replace(self::RE_REGISTER,'',$ev->getMessage())); + } if (!$this->checkPwd($pl,$ev->getMessage())) { $ev->setCancelled(); $ev->setMessage("~"); return; } $this->pwds[$n] = $ev->getMessage(); - $pl->sendMessage($this->cfg["messages"]["re-enter pwd"]); + $pl->sendMessage(TextFormat::AQUA.mc::_("re-enter pwd")); $ev->setCancelled(); $ev->setMessage("~"); return; @@ -118,7 +186,7 @@ public function onPlayerCmd(PlayerCommandPreprocessEvent $ev) { unset($this->pwds[$n]); $ev->setCancelled(); $ev->setMessage("~"); - $pl->sendMessage($this->cfg["messages"]["passwords dont match"]); + $pl->sendMessage(TextFormat::RED.mc::_("passwords dont match")); return; } if (isset($this->chpwd[$n])) { @@ -130,45 +198,35 @@ public function onPlayerCmd(PlayerCommandPreprocessEvent $ev) { unset($this->pwds[$n]); if (!$this->auth->unregisterPlayer($pl)) { - $pl->sendMessage($this->cfg["messages"]["registration error"]); + $pl->sendMessage(TextFormat::RED.mc::_("registration error")); return; } if (!$this->auth->registerPlayer($pl,$pw)) { - $pl->kick($this->cfg["messages"]["registration error"]); + $pl->kick(mc::_("registration error")); return; } - $pl->sendMessage($this->cfg["messages"]["chpwd ok"]); + $pl->sendMessage(TextFormat::GREEN.mc::_("chpwd ok")); return; } // New user registration... if (!$this->auth->registerPlayer($pl,$this->pwds[$n])) { - $pl->kick($this->cfg["messages"]["registration error"]); + $pl->kick(mc::_("registration error")); return; } if (!$this->auth->authenticatePlayer($pl)) { - $pl->kick($this->cfg["messages"]["auth error"]); + $pl->kick(mc::_("auth error")); return; } unset($this->pwds[$n]); $ev->setMessage("~"); $ev->setCancelled(); - $pl->sendMessage($this->cfg["messages"]["register ok"]); - if (isset($this->cfg["nest-egg"]) && !$pl->isCreative()) { - // Award a nest egg to player... - foreach ($this->cfg["nest-egg"] as $i) { - $r = explode(":",$i); - if (count($r) != 3) continue; - $item = Item::fromString($r[0].":".$r[1]); - $item->setCount(intval($r[2])); - $pl->getInventory()->addItem($item); - } - } + $pl->sendMessage(TextFormat::GREEN.mc::_("register ok")); return; } - if ($this->cfg["lamer-mode"]) { + if ($this->cfg["leet-mode"]) { $msg = $ev->getMessage(); - if (preg_match('/^\s*\/login\s+/',$msg)) { - $pl->sendMessage($this->cfg["messages"]["snob login"]); + if (preg_match(self::RE_LOGIN,$msg)) { + $pl->sendMessage(TextFormat::YELLOW.mc::_("snob login")); } else { $ev->setMessage("/login $msg"); } @@ -188,7 +246,7 @@ public function onPlayerCmd(PlayerCommandPreprocessEvent $ev) { public function checkTimeout($n) { $pl = $this->getServer()->getPlayer($n); if ($pl && !$this->auth->isPlayerAuthenticated($pl)) { - $pl->kick($this->cfg["messages"]["login timeout"]); + $pl->kick(mc::_("login timeout")); } } public function checkLoginCount($n) { @@ -196,15 +254,7 @@ public function checkLoginCount($n) { $pl = $this->getServer()->getPlayer($n); if ($pl && !$this->auth->isPlayerAuthenticated($pl)) { if ($this->pwds[$n] >= $this->cfg["max-attempts"]) { - if ($this->cfg["auto-ban"]) { - // OK banning use for trying to hack... - $ip = $pl->getAddress(); - $this->getServer()->getIPBans()->addBan($ip,"Too many login attempts",null,"SimpleAuthHelper"); - $this->getServer()->blockAddress($ip,-1); - $this->getServer()->broadcastMessage("[Helper] Banned IP Address $ip"); - } - - $pl->kick($this->cfg["messages"]["too many logins"]); + $pl->kick(mc::_("too many logins")); unset($this->pwds[$n]); } return; @@ -212,18 +262,19 @@ public function checkLoginCount($n) { unset($this->pwds[$n]); return; } - public function checkPwd($pl,$pwd) { + public function checkPwd($pl,$pwd, $name = null) { if (preg_match('/\s/',$pwd)) { - $pl->sendMessage($this->cfg["messages"]["no spaces"]); + $pl->sendMessage(TextFormat::RED.mc::_("no spaces")); return false; } if (strlen($pwd) < $this->auth->getConfig()->get("minPasswordLength")){ - $pl->sendMessage($this->auth->getMessage("register.error.password")); + $pl->sendMessage(TextFormat::RED.mc::_("register.error.password %1%", + $this->auth->getConfig()->get("minPasswordLength"))); return false; } - if (strtolower($pl->getName()) == strtolower($pwd)) { - $pl->sendMessage($this->cfg["messages"]["not name"]); - return false; + if (strtolower($name === null ? $pl->getName() : $name) == strtolower($pwd)) { + $pl->sendMessage(TextFormat::RED.mc::_("not name")); + return false; } return true; } @@ -240,47 +291,85 @@ protected function authenticate($pl,$password) { // Commands // ////////////////////////////////////////////////////////////////////// + private function chpwd(CommandSender $sender, $oldpwd) { + if (!($sender instanceof Player)) { + $sender->sendMessage(TextFormat::RED. + mc::_("This command only works in-game.")); + return true; + } + if(!$this->auth->isPlayerRegistered($sender)) { + $sender->sendMessage(TextFormat::YELLOW.mc::_("register first")); + return true; + } + if ($this->authenticate($sender,$oldpwd)) { + $this->chpwd[$sender->getName()] = $sender->getName(); + $sender->sendMessage(TextFormat::AQUA.mc::_("chpwd msg")); + return true; + } + $sender->sendMessage(TextFormat::RED.mc::_("chpwd error")); + return false; + } + private function resetpwd($sender, $name) { + $player = $this->getServer()->getOfflinePlayer($name); + if($this->auth->unregisterPlayer($player)){ + $sender->sendMessage(TextFormat::GREEN . mc::_("%1% unregistered",$name)); + if($player instanceof Player){ + $player->sendMessage(TextFormat::YELLOW.mc::_("You are no longer registered!")); + $this->auth->deauthenticatePlayer($player); + } + }else{ + $sender->sendMessage(TextFormat::RED . mc::_("Unable to unregister %1%",$name)); + } + return true; + } + private function logout($sender) { + if (!($sender instanceof Player)) { + $sender->sendMessage(TextFormat::RED. + mc::_("This command only works in-game.")); + return true; + } + if(!$this->auth->isPlayerAuthenticated($sender)) { + $sender->sendMessage(TextFormat::YELLOW.mc::_("login first")); + return true; + } + $sender->sendMessage(TextFormat::GREEN.mc::_("logout completed")); + $this->auth->deauthenticatePlayer($sender); + return true; + } + private function prereg($sender,$name,$newpwd) { + $player = $this->getServer()->getOfflinePlayer($name); + if ($this->auth->isPlayerRegistered($player)) { + $sender->sendMessage(TextFormat::RED.mc::_("%1% already registered", $name)); + return true; + } + if (!$this->checkPwd($sender,$newpwd,$name)) return true; + if ($this->auth->registerPlayer($player,$newpwd)) { + $sender->sendMessage(TextFormat::GREEN.mc::_("registered %1%", $name)); + $sender->sendMessage("OK"); + } else { + $sender->sendMessage(TextFormat::RED.mc::_("error registering %1%", $name)); + } + return true; + } public function onCommand(CommandSender $sender, Command $cmd, $label, array $args) { if (!$this->auth) { - $sender->sendMessage(TextFormat::RED."SimpleAuthHelper has been disabled"); - $sender->sendMessage(TextFormat::RED."SimpleAuth not found!"); + $sender->sendMessage(TextFormat::RED.mc::_("SimpleAuthHelper has been disabled")); + $sender->sendMessage(TextFormat::RED.mc::_("SimpleAuth not found!")); return true; } switch($cmd->getName()){ case "chpwd": - if (!($sender instanceof Player)) { - $sender->sendMessage(TextFormat::RED. - "This command only works in-game."); - return true; - } if (count($args) == 0) return false; - if(!$this->auth->isPlayerRegistered($sender)) { - $sender->sendMessage($this->cfg["messages"]["register first"]); - return true; - } - if ($this->authenticate($sender,implode(" ", $args))) { - $this->chpwd[$sender->getName()] = $sender->getName(); - $sender->sendMessage($this->cfg["messages"]["chpwd msg"]); - return true; - } - $sender->sendMessage($this->cfg["messages"]["chpwd error"]); - return false; - break; + return $this->chpwd($sender, implode(" ", $args)); case "resetpwd": - foreach($args as $name){ - $player = $this->getServer()->getOfflinePlayer($name); - if($this->auth->unregisterPlayer($player)){ - $sender->sendMessage(TextFormat::GREEN . "$name unregistered"); - if($player instanceof Player){ - $player->sendMessage(TextFormat::YELLOW."You are no longer registered!"); - $this->auth->deauthenticatePlayer($player); - } - }else{ - $sender->sendMessage(TextFormat::RED . "Unable to unregister $name"); - } - return true; - } - break; + if (count($args) != 1) return false; + return $this->resetpwd($sender, $args[0]); + case "logout": + if (count($args) != 0) return false; + return $this->logout($sender); + case "preregister": + if (count($args) != 2) return false; + return $this->prereg($sender,$args[0],$args[1]); } return false; } diff --git a/src/aliuly/helper/PermsHacker.php b/src/aliuly/helper/PermsHacker.php new file mode 100644 index 0000000..ec157e3 --- /dev/null +++ b/src/aliuly/helper/PermsHacker.php @@ -0,0 +1,61 @@ +helper = $plugin; + $plugin->getServer()->getPluginManager()->registerEvents($this, $plugin); + $this->opts = [ + "login" => $login, + "register" => $register, + ]; + } + private function checkPerm(Player $pl, $perm) { + if ($pl->hasPermission($perm)) return; + $n = strtolower($pl->getName()); + $this->helper->getLogger()->warning(mc::_("Fixing %1% for %2%", $perm, $n)); + if (!isset($this->perms[$n])) $this->perms[$n] = $pl->addAttachment($this->helper); + $this->perms[$n]->setPermission($perm,true); + $pl->recalculatePermissions(); + } + public function forcePerms(Player $player) { + if ($this->helper->auth->isPlayerAuthenticated($player)) { + $this->resetPerms($player); + return; + } + if ($this->opts["register"] && !$this->helper->auth->isPlayerRegistered($player)) { + $this->checkPerm($player,"simpleauth.command.register"); + return; + } + if ($this->opts["login"]) $this->checkPerm($player,"simpleauth.command.login"); + } + public function resetPerms(Player $pl) { + $n = strtolower($pl->getName()); + if (isset($this->perms[$n])) { + $attach = $this->perms[$n]; + unset($this->perms[$n]); + $pl->removeAttachment($attach); + $pl->recalculatePermissions(); + } + } + public function onQuit(PlayerQuitEvent $ev) { + $this->resetPerms($ev->getPlayer()); + } + public function onCmd(PlayerCommandPreprocessEvent $ev) { + $this->forcePerms($ev->getPlayer()); + } +} diff --git a/src/aliuly/helper/common/MPMU.php b/src/aliuly/helper/common/MPMU.php new file mode 100644 index 0000000..a624e69 --- /dev/null +++ b/src/aliuly/helper/common/MPMU.php @@ -0,0 +1,246 @@ +=, <=, <> or !=, =, !|~, <, > + * + * @param str api Installed API version + * @param str version API version to compare against + * + * @return bool + */ + static public function apiCheck($api,$version) { + switch (substr($version,0,2)) { + case ">=": + return version_compare($api,trim(substr($version,2))) >= 0; + case "<=": + return version_compare($api,trim(substr($version,2))) <= 0; + case "<>": + case "!=": + return version_compare($api,trim(substr($version,2))) != 0; + } + switch (substr($version,0,1)) { + case "=": + return version_compare($api,trim(substr($version,1))) == 0; + case "!": + case "~": + return version_compare($api,trim(substr($version,1))) != 0; + case "<": + return version_compare($api,trim(substr($version,1))) < 0; + case ">": + return version_compare($api,trim(substr($version,1))) > 0; + } + if (intval($api) != intval($version)) return 0; + return version_compare($api,$version) >= 0; + } + /** + * Returns a localized string for the gamemode + * + * @param int mode + * @return str + */ + static public function gamemodeStr($mode) { + if (class_exists(__NAMESPACE__."\\mc",false)) { + switch ($mode) { + case 0: return mc::_("Survival"); + case 1: return mc::_("Creative"); + case 2: return mc::_("Adventure"); + case 3: return mc::_("Spectator"); + } + return mc::_("%1%-mode",$mode); + } + switch ($mode) { + case 0: return "Survival"; + case 1: return "Creative"; + case 2: return "Adventure"; + case 3: return "Spectator"; + } + return "$mode-mode"; + } + /** + * Check's player or sender's permissions and shows a message if appropriate + * + * @param CommandSender $sender + * @param str $permission + * @param bool $msg If false, no message is shown + * @return bool + */ + static public function access(CommandSender $sender, $permission,$msg=true) { + if($sender->hasPermission($permission)) return true; + if ($msg) + $sender->sendMessage(mc::_("You do not have permission to do that.")); + return false; + } + /** + * Check's if $sender is a player in game + * + * @param CommandSender $sender + * @param bool $msg If false, no message is shown + * @return bool + */ + static public function inGame(CommandSender $sender,$msg = true) { + if (!($sender instanceof Player)) { + if ($msg) $sender->sendMessage(mc::_("You can only do this in-game")); + return false; + } + return true; + } + /** + * Takes a player and creates a string suitable for indexing + * + * @param Player|str $player - Player to index + * @return str + */ + static public function iName($player) { + if ($player instanceof Player) { + $player = strtolower($player->getName()); + } + return $player; + } + /** + * Lile file_get_contents but for a Plugin resource + * + * @param Plugin $plugin + * @param str $filename + * @return str|null + */ + static public function getResourceContents($plugin,$filename) { + $fp = $plugin->getResource($filename); + if($fp === null){ + return null; + } + $contents = stream_get_contents($fp); + fclose($fp); + return $contents; + } + /** + * Call a plugin's function + * + * @param Server $server - pocketmine server instance + * @param str $plug - plugin to call + * @param str $method - method to call + * @param mixed $default - If the plugin does not exist or it is not enable, this value uis returned + * @return mixed + */ + static public function callPlugin($server,$plug,$method,$args,$default = null) { + if (($plugin = $server->getPluginManager()->getPlugin($plug)) !== null + && $plugin->isEnabled()) { + $fn = [ $plugin, $method ]; + return $fn(...$args); + } + return $default; + } + /** + * Register a command + * + * @param Plugin $plugin - plugin that "owns" the command + * @param CommandExecutor $executor - object that will be called onCommand + * @param str $cmd - Command name + * @param array $yaml - Additional settings for this command. + */ + static public function addCommand($plugin, $executor, $cmd, $yaml) { + $newCmd = new PluginCommand($cmd,$plugin); + if (isset($yaml["description"])) + $newCmd->setDescription($yaml["description"]); + if (isset($yaml["usage"])) + $newCmd->setUsage($yaml["usage"]); + if(isset($yaml["aliases"]) and is_array($yaml["aliases"])) { + $aliasList = []; + foreach($yaml["aliases"] as $alias) { + if(strpos($alias,":")!== false) { + $this->owner->getLogger()->info("Unable to load alias $alias"); + continue; + } + $aliasList[] = $alias; + } + $newCmd->setAliases($aliasList); + } + if(isset($yaml["permission"])) + $newCmd->setPermission($yaml["permission"]); + if(isset($yaml["permission-message"])) + $newCmd->setPermissionMessage($yaml["permission-message"]); + $newCmd->setExecutor($executor); + $cmdMap = $plugin->getServer()->getCommandMap(); + $cmdMap->register($plugin->getDescription()->getName(),$newCmd); + } + /** + * Unregisters a command + * @param Server|Plugin $obj - Access path to server instance + * @param str $cmd - Command name to remove + */ + static public function rmCommand($srv, $cmd) { + $cmdMap = $srv->getCommandMap(); + $oldCmd = $cmdMap->getCommand($cmd); + if ($oldCmd === null) return false; + $oldCmd->setLabel($cmd."_disabled"); + $oldCmd->unregister($cmdMap); + return true; + } + /** + * Send a PopUp, but takes care of checking if there are some + * plugins that might cause issues. + * + * Currently only supports SimpleAuth and BasicHUD. + * + * @param Player $player + * @param str $msg + */ + static public function sendPopup($player,$msg) { + $pm = $player->getServer()->getPluginManager(); + if (($sa = $pm->getPlugin("SimpleAuth")) !== null) { + // SimpleAuth also has a HUD when not logged in... + if ($sa->isEnabled() && !$sa->isPlayerAuthenticated($player)) return; + } + if (($hud = $pm->getPlugin("BasicHUD")) !== null) { + // Send pop-ups through BasicHUD + $hud->sendPopup($player,$msg); + return; + } + $player->sendPopup($msg); + } + + +} diff --git a/src/aliuly/helper/PluginCallbackTask.php b/src/aliuly/helper/common/PluginCallbackTask.php similarity index 51% rename from src/aliuly/helper/PluginCallbackTask.php rename to src/aliuly/helper/common/PluginCallbackTask.php index b4b4100..672cf25 100644 --- a/src/aliuly/helper/PluginCallbackTask.php +++ b/src/aliuly/helper/common/PluginCallbackTask.php @@ -1,32 +1,20 @@ args = $args; $this->args[] = $this; } - /** * @return callable */ diff --git a/src/aliuly/helper/common/mc.php b/src/aliuly/helper/common/mc.php new file mode 100644 index 0000000..4e66199 --- /dev/null +++ b/src/aliuly/helper/common/mc.php @@ -0,0 +1,106 @@ +getFile()); + * * mc::_("string to translate\n") + * * mc::_("string to translate %1% %2%\n",$arg1,$arg2) + * * mc::n(mc::\_("singular form"),mc::\_("Plural form"),$count) + */ +abstract class mc { + /** @var str[] $txt Message translations */ + public static $txt = []; + /** Main translation function + * + * This translates strings. The naming of "_" is to make it compatible + * with gettext utilities. The string can contain "%1%", "%2%, etc... + * These are inserted from the following arguments. Use "%%" to insert + * a single "%". + * + * @param str[] $args - messages + * @return str translated string + */ + public static function _(...$args) { + $fmt = array_shift($args); + if (isset(self::$txt[$fmt])) $fmt = self::$txt[$fmt]; + if (count($args)) { + $vars = [ "%%" => "%" ]; + $i = 1; + foreach ($args as $j) { + $vars["%$i%"] = $j; + ++$i; + } + $fmt = strtr($fmt,$vars); + } + return $fmt; + } + /** + * Plural and singular forms. + * + * @param str $a - Singular form + * @param str $b - Plural form + * @param int $c - the number to test to select between $a or $b + * @return str - Either plural or singular forms depending on the value of $c + */ + public static function n($a,$b,$c) { + return $c == 1 ? $a : $b; + } + /** + * Load a message file for a PocketMine plugin. Only uses .ini files. + * + * @param Plugin $plugin - owning plugin + * @param str $path - output of $plugin->getFile() + * @return int|false - false on error or the number of messages loaded + */ + public static function plugin_init($plugin,$path) { + if (file_exists($plugin->getDataFolder()."messages.ini")) { + return self::load($plugin->getDataFolder()."messages.ini"); + } + $msgs = $path."resources/messages/". + $plugin->getServer()->getProperty("settings.language"). + ".ini"; + if (!file_exists($msgs)) return false; + return mc::load($msgs); + } + /** + * Load the specified message catalogue. + * Can read .ini or .po files. + * @param str $f - Filename to load + * @return int|false - returns the number of strings loaded or false on error + */ + public static function load($f) { + $potxt = "\n".file_get_contents($f)."\n"; + if (preg_match('/\nmsgid\s/',$potxt)) { + $potxt = preg_replace('/\\\\n"\n"/',"\\n", + preg_replace('/\s+""\s*\n\s*"/'," \"", + $potxt)); + } + foreach (['/\nmsgid "(.+)"\nmsgstr "(.+)"\n/', + '/^\s*"(.+)"\s*=\s*"(.+)"\s*$/m'] as $re) { + $c = preg_match_all($re,$potxt,$mm); + if ($c) { + for ($i=0;$i<$c;++$i) { + if ($mm[2][$i] == "") continue; + eval('$a = "'.$mm[1][$i].'";'); + eval('$b = "'.$mm[2][$i].'";'); + mc::$txt[$a] = $b; + } + return $c; + } + } + return false; + } +}