Skip to content

Feature/33 update filter #1045

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 6 commits into from
Jun 6, 2020
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 @@ -7,6 +7,7 @@ Exclamation symbols (:exclamation:) note something of importance e.g. breaking c
### Notes
- [:ledger: View file changes][Unreleased]
### Added
- New method `setUpdateFilter($callback)` used to filter `processUpdate(Update $update)` calls. If `$callback` returns `false` the update isn't processed and an empty falsey `ServerResponse` is returned. (@VRciF)
- Replaced 'generic' and 'genericmessage' strings with Telegram::GENERIC_COMMAND and Telegram::GENERIC_MESSAGE_COMMAND constants (@1int)
- Bot API 4.8 (Extra Poll and Dice features).
- Allow custom MySQL port to be defined for tests.
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ A Telegram Bot based on the official [Telegram Bot API]
- [getUserProfilePhoto](#getuserprofilephoto)
- [getFile and downloadFile](#getfile-and-downloadfile)
- [Send message to all active chats](#send-message-to-all-active-chats)
- [Filter Update](#filter-update)
- [Utils](#utils)
- [MySQL storage (Recommended)](#mysql-storage-recommended)
- [External Database connection](#external-database-connection)
Expand Down Expand Up @@ -414,6 +415,25 @@ $results = Request::sendToActiveChats(

You can also broadcast a message to users, from the private chat with your bot. Take a look at the [admin commands](#admin-commands) below.

#### Filter Update

Update processing can be allowed or denied by defining a custom update filter.
Let's say we only want to allow messages from a user with ID 428, we can do the following before handling the request:

```php
$telegram->setUpdateFilter(function (Update $update, Telegram $telegram, &$reason = 'Update denied by update_filter') {
$user_id = $update->getMessage()->getFrom()->getId();
if ($user_id === 428) {
return true;
}

$reason = "Invalid user with ID {$user_id}";
return false;
});
```

The reason for denying an update can be defined with the `$reason` parameter. This text gets written to the debug log.

## Utils

### MySQL storage (Recommended)
Expand Down
48 changes: 47 additions & 1 deletion src/Telegram.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class Telegram
*
* @var integer
*/
protected $last_update_id = null;
protected $last_update_id;

/**
* The command to be executed when there's a new message update and nothing more suitable is found
Expand All @@ -156,6 +156,14 @@ class Telegram
*/
const GENERIC_COMMAND = 'generic';

/**
* Update filter
* Filter updates
*
* @var callback
*/
protected $update_filter;

/**
* Telegram constructor.
*
Expand Down Expand Up @@ -455,6 +463,20 @@ public function processUpdate(Update $update)
$this->update = $update;
$this->last_update_id = $update->getUpdateId();

if (is_callable($this->update_filter)) {
$reason = 'Update denied by update_filter';
try {
$allowed = (bool) call_user_func_array($this->update_filter, [$update, $this, &$reason]);
} catch (\Exception $e) {
$allowed = false;
}

if (!$allowed) {
TelegramLog::debug($reason);
return new ServerResponse(['ok' => false, 'description' => 'denied'], null);
}
}

//Load admin commands
if ($this->isAdmin()) {
$this->addCommandsPath(TB_BASE_COMMANDS_PATH . '/AdminCommands', false);
Expand Down Expand Up @@ -990,4 +1012,28 @@ public function getLastUpdateId()
{
return $this->last_update_id;
}

/**
* Set an update filter callback
*
* @param callable $callback
*
* @return Telegram
*/
public function setUpdateFilter(callable $callback)
{
$this->update_filter = $callback;

return $this;
}

/**
* Return update filter callback
*
* @return callable|null
*/
public function getUpdateFilter()
{
return $this->update_filter;
}
}
50 changes: 50 additions & 0 deletions tests/unit/TelegramTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
namespace Longman\TelegramBot\Tests\Unit;

use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use Longman\TelegramBot\Entities\Update;
use Longman\TelegramBot\Exception\TelegramException;
use Longman\TelegramBot\Telegram;
use Longman\TelegramBot\TelegramLog;

/**
* @package TelegramTest
Expand Down Expand Up @@ -145,4 +147,52 @@ public function testGetCommandsList()
$this->assertIsArray($commands);
$this->assertNotCount(0, $commands);
}

public function testUpdateFilter()
{
$rawUpdate = '{
"update_id": 513400512,
"message": {
"message_id": 3,
"from": {
"id": 313534466,
"first_name": "first",
"last_name": "last",
"username": "username"
},
"chat": {
"id": 313534466,
"first_name": "first",
"last_name": "last",
"username": "username",
"type": "private"
},
"date": 1499402829,
"text": "hi"
}
}';

$debug_log_file = '/tmp/php-telegram-bot-update-filter-debug.log';
TelegramLog::initialize(
new \Monolog\Logger('bot_log', [
(new \Monolog\Handler\StreamHandler($debug_log_file, \Monolog\Logger::DEBUG))->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true)),
])
);

$update = new Update(json_decode($rawUpdate, true), $this->telegram->getBotUsername());
$this->telegram->setUpdateFilter(function (Update $update, Telegram $telegram, &$reason) {
if ($update->getMessage()->getChat()->getId() === 313534466) {
$reason = 'Invalid user, update denied.';
return false;
}
return true;
});
$response = $this->telegram->processUpdate($update);
$this->assertFalse($response->isOk());

// Check that the reason is written to the debug log.
$debug_log = file_get_contents($debug_log_file);
$this->assertStringContainsString('Invalid user, update denied.', $debug_log);
file_exists($debug_log_file) && unlink($debug_log_file);
}
}