Skip to content

Improve PSR4 command classes #1209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Exclamation symbols (:exclamation:) note something of importance e.g. breaking c
- [:ledger: View file changes][Unreleased] ∙ [:page_with_curl: DB migration script][unreleased-sql-migration]
### Added
- Bot API 5.1 (ChatMember Update types, Improved Invite Links, Voice Chat). (@massadm, @noplanman)
- Method to allow adding command classes directly. (@alligator77, @noplanman)
### Changed
### Deprecated
- `Telegram::handleGetUpdates` method should be passed a `$data` array for parameters.
Expand Down
34 changes: 13 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,27 +525,7 @@ With [admin commands](#admin-commands) you can manage your channels directly wit

The bot is able to recognise commands in a chat with multiple bots (/command@mybot).

It can execute commands that get triggered by chat events.

Here's the list:

- *StartCommand.php* (A new user starts to use the bot.)
- *NewChatMembersCommand.php* (A new member(s) was added to the group, information about them.)
- *LeftChatMemberCommand.php* (A member was removed from the group, information about them.)
- *NewChatTitleCommand.php* (A chat title was changed to this value.)
- *NewChatPhotoCommand.php* (A chat photo was changed to this value.)
- *DeleteChatPhotoCommand.php* (Service message: the chat photo was deleted.)
- *GroupChatCreatedCommand.php* (Service message: the group has been created.)
- *SupergroupChatCreatedCommand.php* (Service message: the supergroup has been created.)
- *ChannelChatCreatedCommand.php* (Service message: the channel has been created.)
- *MigrateToChatIdCommand.php* (The group has been migrated to a supergroup with the specified identifier.)
- *MigrateFromChatIdCommand.php* (The supergroup has been migrated from a group with the specified identifier.)
- *PinnedMessageCommand.php* (Specified message was pinned.)

- *GenericmessageCommand.php* (Handle any type of message.)
- *GenericCommand.php* (Handle commands that don't exist or to use commands as a variable.)
- Favourite colour? */black, /red*
- Favourite number? */1, /134*
It can also execute commands that get triggered by events, so-called Service Messages.

### Custom Commands

Expand All @@ -554,6 +534,18 @@ There is a guide to help you [create your own commands][wiki-create-your-own-com

Also, be sure to have a look at the [example commands][ExampleCommands-folder] to learn more about custom commands and how they work.

You can add your custom commands in different ways:

```php
// Add a folder that contains command files
$telegram->addCommandsPath('/path/to/command/files');
//$telegram->addCommandsPaths(['/path/to/command/files', '/another/path']);

// Add a command directly using the class name
$telegram->addCommandClass(MyCommand::class);
//$telegram->addCommandClasses([MyCommand::class, MyOtherCommand::class]);
```

### Commands Configuration

With this method you can set some command specific parameters, for example:
Expand Down
15 changes: 15 additions & 0 deletions src/Commands/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@
*/
abstract class Command
{
/**
* Auth level for user commands
*/
public const AUTH_USER = 'User';

/**
* Auth level for system commands
*/
public const AUTH_SYSTEM = 'System';

/**
* Auth level for admin commands
*/
public const AUTH_ADMIN = 'Admin';

/**
* Telegram object
*
Expand Down
185 changes: 87 additions & 98 deletions src/Telegram.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,6 @@

class Telegram
{
/**
* Auth name for user commands
*/
const AUTH_USER = 'User';
/**
* Auth name tof system commands
*/
const AUTH_SYSTEM = 'System';
/**
* Auth name for admin commands
*/
const AUTH_ADMIN = 'Admin';

/**
* Version
*
Expand Down Expand Up @@ -86,22 +73,23 @@ class Telegram
protected $commands_paths = [];

/**
* Custom commands class names
* Custom command class names
* ```
* [
* 'User' => [
* //commandName => className
* 'start' => 'name\space\to\StartCommand',
* ],
* 'Admin' => [], //etc
* 'User' => [
* // command_name => command_class
* 'start' => 'name\space\to\StartCommand',
* ],
* 'Admin' => [], //etc
* ]
* ```
*
* @var array
*/
protected $commandsClasses = [
self::AUTH_USER => [],
self::AUTH_ADMIN => [],
self::AUTH_SYSTEM => [],
protected $command_classes = [
Command::AUTH_USER => [],
Command::AUTH_ADMIN => [],
Command::AUTH_SYSTEM => [],
];

/**
Expand Down Expand Up @@ -318,22 +306,37 @@ public function getCommandsList(): array

/**
* Get classname of predefined commands
* @see commandsClasses
* @param string $auth Auth of command
* @param string $command Command name
*
* @see command_classes
*
* @param string $auth Auth of command
* @param string $command Command name
* @param string $filepath Path to the command file
*
* @return string|null
*/
public function getCommandClassName(string $auth, string $command): ?string
public function getCommandClassName(string $auth, string $command, string $filepath = ''): ?string
{
$command = mb_strtolower($command);
$auth = $this->ucFirstUnicode($auth);
$auth = $this->ucFirstUnicode($auth);

if (!empty($this->commandsClasses[$auth][$command])) {
$className = $this->commandsClasses[$auth][$command];
if (class_exists($className)){
return $className;
}
// First, check for directly assigned command class.
if ($command_class = $this->command_classes[$auth][$command] ?? null) {
return $command_class;
}

// Start with default namespace.
$command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands';

// Check if we can get the namespace from the file (if passed).
if ($filepath && !($command_namespace = $this->getFileNamespace($filepath))) {
return null;
}

$command_class = $command_namespace . '\\' . $this->ucFirstUnicode($command) . 'Command';

if (class_exists($command_class)) {
return $command_class;
}

return null;
Expand All @@ -353,43 +356,24 @@ public function getCommandObject(string $command, string $filepath = ''): ?Comma
return $this->commands_objects[$command];
}

$which = [self::AUTH_SYSTEM];
$this->isAdmin() && $which[] = self::AUTH_ADMIN;
$which[] = self::AUTH_USER;

foreach ($which as $auth)
{
if (!($command_class = $this->getCommandClassName($auth, $command)))
{
if ($filepath) {
$command_namespace = $this->getFileNamespace($filepath);
} else {
$command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands';
}
$command_class = $command_namespace . '\\' . $this->ucFirstUnicode($command) . 'Command';
}
$which = [Command::AUTH_SYSTEM];
$this->isAdmin() && $which[] = Command::AUTH_ADMIN;
$which[] = Command::AUTH_USER;

foreach ($which as $auth) {
$command_class = $this->getCommandClassName($auth, $command, $filepath);

if (class_exists($command_class)) {
if ($command_class) {
$command_obj = new $command_class($this, $this->update);

switch ($auth) {
case self::AUTH_SYSTEM:
if ($command_obj instanceof SystemCommand) {
return $command_obj;
}
break;

case self::AUTH_ADMIN:
if ($command_obj instanceof AdminCommand) {
return $command_obj;
}
break;

case self::AUTH_USER:
if ($command_obj instanceof UserCommand) {
return $command_obj;
}
break;
if ($auth === Command::AUTH_SYSTEM && $command_obj instanceof SystemCommand) {
return $command_obj;
}
if ($auth === Command::AUTH_ADMIN && $command_obj instanceof AdminCommand) {
return $command_obj;
}
if ($auth === Command::AUTH_USER && $command_obj instanceof UserCommand) {
return $command_obj;
}
}
}
Expand Down Expand Up @@ -605,7 +589,7 @@ public function processUpdate(Update $update): ServerResponse
$reason = 'Update denied by update_filter';
try {
$allowed = (bool) call_user_func_array($this->update_filter, [$update, $this, &$reason]);
} catch (\Exception $e) {
} catch (Exception $e) {
$allowed = false;
}

Expand Down Expand Up @@ -793,49 +777,54 @@ public function isDbEnabled(): bool
}

/**
* Add a single custom commands class
* Add a single custom command class
*
* @param string $command_class Full command class name
*
* @param string $className Set full classname
* @return Telegram
*/
public function addCommandsClass(string $className): Telegram
public function addCommandClass(string $command_class): Telegram
{
if (!$className || !class_exists($className))
{
$error = 'Command class name: "' . $className . '" does not exist.';
if (!$command_class || !class_exists($command_class)) {
$error = sprintf('Command class "%s" does not exist.', $command_class);
TelegramLog::error($error);
throw new InvalidArgumentException($error);
}

if (!is_array($this->commandsClasses))
{
$this->commandsClasses = [];
}

if (!is_a($className, Command::class, true)) {
$error = 'Command class is not a base command class';
if (!is_a($command_class, Command::class, true)) {
$error = sprintf('Command class "%s" does not extend "%s".', $command_class, Command::class);
TelegramLog::error($error);
throw new InvalidArgumentException($error);
}

$commandObject = new $className($this);
// Dummy object to get data from.
$command_object = new $command_class($this);

$command = $commandObject->getName();
$auth = null;
switch (true) {
case $commandObject->isSystemCommand():
$auth = self::AUTH_SYSTEM;
break;
case $commandObject->isAdminCommand():
$auth = self::AUTH_ADMIN;
break;
case $commandObject->isUserCommand():
$auth = self::AUTH_USER;
break;
}
$command_object->isSystemCommand() && $auth = Command::AUTH_SYSTEM;
$command_object->isAdminCommand() && $auth = Command::AUTH_ADMIN;
$command_object->isUserCommand() && $auth = Command::AUTH_USER;

if ($auth) {
$this->commandsClasses[$auth][$command] = $className;
$command = mb_strtolower($command_object->getName());

$this->command_classes[$auth][$command] = $command_class;
}

return $this;
}

/**
* Add multiple custom command classes
*
* @param array $command_classes List of full command class names
*
* @return Telegram
*/
public function addCommandClasses(array $command_classes): Telegram
{
foreach ($command_classes as $command_class) {
$this->addCommandClass($command_class);
}

return $this;
Expand Down Expand Up @@ -892,13 +881,13 @@ public function getCommandsPaths(): array
}

/**
* Return the list of commands classes
* Return the list of command classes
*
* @return array
*/
public function getCommandsClasses(): array
public function getCommandClasses(): array
{
return $this->commandsClasses;
return $this->command_classes;
}

/**
Expand Down
47 changes: 47 additions & 0 deletions tests/Unit/Commands/CustomTestCommands/DummyAdminCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/**
* This file is part of the TelegramBot package.
*
* (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Dummy\AdminCommands;

use Longman\TelegramBot\Commands\AdminCommand;
use Longman\TelegramBot\Entities\ServerResponse;
use Longman\TelegramBot\Request;

/**
* Test "/dummyadmin" command
*/
class DummyAdminCommand extends AdminCommand
{
/**
* @var string
*/
protected $name = 'dummyadmin';

/**
* @var string
*/
protected $description = 'Dummy AdminCommand';

/**
* @var string
*/
protected $usage = '/dummyadmin';

/**
* Command execute method
*
* @return mixed
*/
public function execute(): ServerResponse
{
return Request::emptyResponse();
}
}
Loading