├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── composer.json └── src ├── Action.php ├── BotManager.php ├── Exception ├── BotManagerException.php ├── InvalidAccessException.php ├── InvalidActionException.php ├── InvalidParamsException.php └── InvalidWebhookException.php └── Params.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | Exclamation symbols (:exclamation:) note something of importance e.g. breaking changes. Click them to learn more. 5 | 6 | ## [Unreleased] 7 | ### Notes 8 | - [:ledger: View file changes][Unreleased] 9 | ### Added 10 | ### Changed 11 | ### Deprecated 12 | ### Removed 13 | ### Fixed 14 | ### Security 15 | 16 | ## [2.1.0] - 2023-05-27 17 | ### Notes 18 | - [:ledger: View file changes][2.1.0] 19 | ### Changed 20 | - Bumped core to version 0.81.*. 21 | 22 | ## [2.0.0] - 2022-07-04 23 | ### Notes 24 | - [:ledger: View file changes][2.0.0] 25 | ### Added 26 | - Enforce `secret_token` webhook validation check. 27 | ### Changed 28 | - Bumped core to version 0.78.*. 29 | - Upgrade code to PHP 8.0. 30 | - Moved tests to GitHub Actions. 31 | ### Security 32 | - Minimum PHP 8.0. 33 | 34 | ## [1.7.0] - 2021-06-14 35 | ### Notes 36 | - [:ledger: View file changes][1.7.0] 37 | ### Changed 38 | - Bumped core to version 0.73.*. 39 | ### Fixed 40 | - `chat_id` output for `getUpdates` method. (#65) 41 | 42 | ## [1.6.0] - 2020-12-26 43 | ### Notes 44 | - [:ledger: View file changes][1.6.0] 45 | ### Added 46 | - Allow webhook to contain custom query parameters. (#59) 47 | ### Changed 48 | - Upgraded dependencies and bumped core to version 0.70.*. 49 | - Upgrade code to PHP 7.3. 50 | - Update Travis-CI and Scrutinizer configs. 51 | ### Removed 52 | - [:exclamation:][1.6.0-bc-remove-botmanager-initlogging] Remove `BotManager::initLogging()` method and require separate logging initialisation with `TelegramLog::initialize`. 53 | ### Security 54 | - Minimum PHP 7.3, allow PHP 8.0. 55 | 56 | ## [1.5.0] - 2019-07-29 57 | ### Notes 58 | - [:ledger: View file changes][1.5.0] 59 | ### Changed 60 | - Upgraded dependencies and bumped core to version 0.59.*. (#48) 61 | - Code style is now PSR12. (#48) 62 | - Adopt issue templates and git/GitHub related meta from upstream core. (#49) 63 | - Simplify FQNs, cleanup tests and update changelog. (#51) 64 | ### Removed 65 | - Botan.io has been removed (see php-telegram-bot/core#924). (#50) 66 | ### Fixed 67 | - Fix and improve getUpdates output. (#52) 68 | - Don't output deprecation notices if no logging is enabled. (#53) 69 | ### Security 70 | - Security disclosure managed by [Tidelift]. (#49) 71 | 72 | ## [1.4.0] - 2019-06-01 73 | ### Notes 74 | - [:ledger: View file changes][1.4.0] 75 | ### Added 76 | - Test up to PHP 7.3 in Travis-CI. (#47) 77 | ### Changed 78 | - Use the new Telegram API webhook IP ranges. (#46) 79 | - Upgraded dependencies and bumped core to version 0.57.0. (#47) 80 | ### Security 81 | - Minimum PHP version is now 7.1. (#47) 82 | 83 | ## [1.3.0] - 2018-07-21 84 | ### Notes 85 | - [:ledger: View file changes][1.3.0] 86 | ### Added 87 | - Allow usage of table prefixes and custom encoding. 88 | - Add error message when trying to use getUpdates without database connection. (#41) 89 | ### Changed 90 | - Upgraded dependencies and bumped core to version 0.54.0. 91 | 92 | ## [1.2.2] - 2017-08-26 93 | ### Notes 94 | - [:ledger: View file changes][1.2.2] 95 | ### Added 96 | - Linked version numbers in changelog for easy verification of code changes. 97 | ### Changed 98 | - Upgraded dependencies and bumped core to version 0.48.0. 99 | 100 | ## [1.2.1] - 2017-07-12 101 | ### Notes 102 | - [:ledger: View file changes][1.2.1] 103 | ### Fixed 104 | - Secret should not be required when using CLI for getUpdates. (#36) 105 | 106 | ## [1.2.0] - 2017-07-10 107 | ### Notes 108 | - [:ledger: View file changes][1.2.0] 109 | ### Added 110 | - Custom output callback can be defined for getUpdates method. (#34) 111 | ### Changed 112 | - Default output of getUpdates method now shows the message type or query text, not the text message content. (#34) 113 | ### Fixed 114 | - GetUpdates method would crash if a non-text message was sent. (#34) 115 | 116 | ## [1.1.0] - 2017-05-23 117 | ### Notes 118 | - [:ledger: View file changes][1.1.0] 119 | ### Added 120 | - `webhookinfo` action to get result from `getWebhookInfo`. 121 | ### Changed 122 | - Clean up and refactor some methods. 123 | ### Fixed 124 | - Passing an empty array to `webhook.allowed_updates` parameter now correctly resets to defaults. 125 | 126 | ## [1.0.1] - 2017-05-09 127 | ### Notes 128 | - [:ledger: View file changes][1.0.1] 129 | ### Changed 130 | - Use more stable `longman/ip-tools` for IP matching. 131 | 132 | ## [1.0.0] - 2017-05-08 133 | ### Notes 134 | - [:ledger: View file changes][1.0.0] 135 | ### Changed 136 | - [:exclamation:][1.0.0-bc-move] Move to `php-telegram-bot/telegram-bot-manager` on packagist. 137 | - [:exclamation:][1.0.0-bc-move] Move to `TelegramBot\TelegramBotManager` namespace. 138 | 139 | ## [0.44.0] - 2017-05-05 140 | ### Notes 141 | - [:ledger: View file changes][0.44.0] 142 | ### Added 143 | - Ability to define custom valid IPs to access webhook. 144 | - Execute commands via cron, using `cron` action and `g` parameter. 145 | ### Changed 146 | - [:exclamation:][0.44.0-bc-parameter-structure] Remodelled the parameter array to a more flexible structure. 147 | - `bot_username` and `secret` are no longer vital parameters. 148 | ### Fixed 149 | - Initialise loggers before anything else, to allow logging of all errors. 150 | ### Security 151 | - Enforce non-empty secret when using webhook. 152 | 153 | ## [0.43.0] - 2017-04-17 154 | ### Notes 155 | - [:ledger: View file changes][0.43.0] 156 | ### Added 157 | - PHP CodeSniffer introduced and cleaned code to pass tests. 158 | - Custom exceptions for better error handling. 159 | - Request limiter options. 160 | 161 | ## [0.42.0.1] - 2017-04-11 162 | ### Notes 163 | - [:ledger: View file changes][0.42.0.1] 164 | ### Added 165 | - Changelog. 166 | ### Changed 167 | - :exclamation: Rename vital parameter `botname` to `bot_username` everywhere. 168 | ### Fixed 169 | - Some code style issues. 170 | 171 | ## [0.42.0] - 2017-04-10 172 | ### Notes 173 | - [:ledger: View file changes][0.42.0] 174 | ### Changed 175 | - Move to PHP Telegram Bot organisation. 176 | - Mirror version with core library. 177 | - Update repository links. 178 | ### Fixed 179 | - Readme formatting. 180 | 181 | ## [0.4.0] - 2017-02-26 182 | ### Notes 183 | - [:ledger: View file changes][0.4.0] 184 | ### Added 185 | - Latest Telegram Bot limiter functionality. 186 | ### Fixed 187 | - Travis tests, using MariaDB instead of MySQL. 188 | 189 | ## [0.3.1] - 2017-01-04 190 | ### Notes 191 | - [:ledger: View file changes][0.3.1] 192 | ### Fixed 193 | - Make CLI usable again after setting up Telegram API IP address limitations. 194 | 195 | ## [0.3.0] - 2016-12-25 196 | ### Notes 197 | - [:ledger: View file changes][0.3.0] 198 | ### Added 199 | - Latest changes from PHP Telegram API bot. 200 | ### Security 201 | - Request validation to secure the script to allow only Telegram API IPs of executing the webhook handle. 202 | 203 | ## [0.2.1] - 2016-10-16 204 | ### Notes 205 | - [:ledger: View file changes][0.2.1] 206 | ### Added 207 | - Interval between updates can be set via parameter. 208 | 209 | ## [0.2] - 2016-09-16 210 | ### Notes 211 | - [:ledger: View file changes][0.2] 212 | ### Changed 213 | - Force PHP7. 214 | 215 | ## [0.1.1] - 2016-08-20 216 | ### Notes 217 | - [:ledger: View file changes][0.1.1] 218 | ### Fixed 219 | - Tiny conditional fix to correct the output. 220 | 221 | ## [0.1] - 2016-08-20 222 | ### Notes 223 | - [:ledger: View file changes][0.1] 224 | ### Added 225 | - First minor version that contains the basic functionality. 226 | 227 | [Tidelift]: https://tidelift.com/subscription/pkg/packagist-php-telegram-bot-telegram-bot-manager?utm_source=packagist-php-telegram-bot-telegram-bot-manager&utm_medium=referral&utm_campaign=changelog 228 | 229 | [1.6.0-bc-remove-botmanager-initlogging]: https://github.com/php-telegram-bot/telegram-bot-manager/wiki/Breaking-backwards-compatibility#remove-botmanagerinitlogging "Remove BotManager::initLogging()" 230 | [1.0.0-bc-move]: https://github.com/php-telegram-bot/telegram-bot-manager/wiki/Breaking-backwards-compatibility#namespace-and-package-name-changed "Namespace and package name changed" 231 | [0.44.0-bc-parameter-structure]: https://github.com/php-telegram-bot/telegram-bot-manager/wiki/Breaking-backwards-compatibility#parameter-structure-changed "Parameter structure changed" 232 | 233 | [Unreleased]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/master...develop 234 | [2.1.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/2.0.0...2.1.0 235 | [2.0.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.7.0...2.0.0 236 | [1.7.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.6.0...1.7.0 237 | [1.6.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.5.0...1.6.0 238 | [1.5.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.4.0...1.5.0 239 | [1.4.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.3.0...1.4.0 240 | [1.3.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.2.2...1.3.0 241 | [1.2.2]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.2.1...1.2.2 242 | [1.2.1]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.2.0...1.2.1 243 | [1.2.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.1.0...1.2.0 244 | [1.1.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.0.1...1.1.0 245 | [1.0.1]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/1.0.0...1.0.1 246 | [1.0.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.44.0...1.0.0 247 | [0.44.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.43.0...0.44.0 248 | [0.43.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.42.0.1...0.43.0 249 | [0.42.0.1]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.42.0...0.42.0.1 250 | [0.42.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.4.0...0.42.0 251 | [0.4.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.3.1...0.4.0 252 | [0.3.1]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.3.0...0.3.1 253 | [0.3.0]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.2.1...0.3.0 254 | [0.2.1]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.2...0.2.1 255 | [0.2]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.1.1...0.2 256 | [0.1.1]: https://github.com/php-telegram-bot/telegram-bot-manager/compare/0.1...0.1.1 257 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Armando Lüscher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Telegram Bot Manager 2 | 3 | [![Join the bot support group on Telegram][support-group-badge]][support-group] 4 | [![Donate][donate-badge]](#donate) 5 | 6 | [![Scrutinizer Code Quality][code-quality-badge]][code-quality] 7 | [![Codecov][code-coverage-badge]][code-coverage] 8 | [![Tests Status][tests-status-badge]][tests-status] 9 | 10 | [![Latest Stable Version][latest-version-badge]][github-tgbot-manager] 11 | [![Dependencies][dependencies-badge]][Tidelift] 12 | [![Total Downloads][total-downloads-badge]][packagist-tgbot-manager] 13 | [![License][license-badge]][license] 14 | 15 | This project builds on top of [PHP Telegram Bot][github-tgbot-core] and as such, depends on it! 16 | 17 | The main purpose of this mini-library is to make the interaction between your webserver and Telegram easier. 18 | I strongly suggest your read the PHP Telegram Bot [instructions][github-tgbot-core-instructions] first, to understand what this library does exactly. 19 | 20 | Installation and usage is pretty straight forward: 21 | 22 | ### Require this package with [Composer] 23 | 24 | ```bash 25 | composer require php-telegram-bot/telegram-bot-manager:^2.1 26 | ``` 27 | 28 | **NOTE:** This will automatically also install [PHP Telegram Bot][github-tgbot-core] into your project (if it isn't already). 29 | 30 | **Advanced:** Due to the fact that the core library is not a stable version yet, this project is partly locked to the core version, to ensure reliable functioning. 31 | 32 | It is possible however, to override the core version that this library requires: 33 | 34 | ```yaml 35 | "require": { 36 | "php-telegram-bot/telegram-bot-manager": "^2.1", 37 | "longman/telegram-bot": "dev-master as 0.81" 38 | } 39 | ``` 40 | 41 | This example will pull the master version of the core library, making it appear to be version 0.81, which then satisfies the requirement. 42 | 43 | ### Performing actions 44 | 45 | What use would this library be if you couldn't perform any actions?! 46 | 47 | There are a few parameters available to get things rolling: 48 | 49 | | Parameter | Description | 50 | | --------- | ----------- | 51 | | s | **s**ecret: This is a special secret value defined in the main `manager.php` file. | 52 | | | This parameter is required to call the script via browser! | 53 | | a | **a**ction: The actual action to perform. (handle (default), webhookinfo, cron, set, unset, reset) | 54 | | | **handle** executes the `getUpdates` method; **webhookinfo** to get result from `getWebhookInfo`, **cron** executes cron commands; **set** / **unset** / **reset** the webhook. | 55 | | l | **l**oop: Number of seconds to loop the script for (used for getUpdates method). | 56 | | | This would be used mainly via CLI, to continually get updates for a certain period. | 57 | | i | **i**nterval: Number of seconds to wait between getUpdates requests (used for getUpdates method, default is 2). | 58 | | | This would be used mainly via CLI, to continually get updates for a certain period, every **i** seconds. | 59 | | g | **g**roup: Commands group for cron (only used together with `cron` action, default group is `default`). | 60 | | | Define which group of commands to execute via cron. Can be a comma separated list of groups. | 61 | 62 | #### via browser 63 | 64 | Simply point your browser to the `manager.php` file with the necessary **GET** parameters: 65 | - `http://example.com/manager.php?s=&a=&l=&i=` 66 | 67 | **Webhook** 68 | 69 | Set, unset and reset the webhook: 70 | - `http://example.com/manager.php?s=super_secret&a=set` 71 | - `http://example.com/manager.php?s=super_secret&a=unset` 72 | - `http://example.com/manager.php?s=super_secret&a=reset` (unset & set combined) 73 | 74 | **getUpdates** 75 | 76 | Handle updates once: 77 | - `http://example.com/manager.php?s=super_secret&a=handle` or simply 78 | - `http://example.com/manager.php?s=super_secret` (`handle` action is the default) 79 | 80 | Handle updates for 30 seconds, fetching every 5 seconds: 81 | - `http://example.com/manager.php?s=super_secret&l=30&i=5` 82 | 83 | **cron** 84 | 85 | Execute commands via cron: 86 | - `http://example.com/manager.php?s=super_secret&a=cron&g=maintenance` or multiple groups 87 | - `http://example.com/manager.php?s=super_secret&a=cron&g=maintenance,cleanup` 88 | 89 | #### via CLI 90 | 91 | When using CLI, the secret is not necessary (since it could just be read from the file itself). 92 | 93 | Call the `manager.php` file directly using `php` and pass the parameters: 94 | - `$ php manager.php a= l= i=` 95 | 96 | **Webhook** 97 | 98 | Set, unset and reset the webhook: 99 | - `$ php manager.php a=set` 100 | - `$ php manager.php a=unset` 101 | - `$ php manager.php a=reset` (unset & set combined) 102 | 103 | **getUpdates** 104 | 105 | Handle updates once: 106 | - `$ php manager.php a=handle` or simply 107 | - `$ php manager.php` (`handle` action is the default) 108 | 109 | Handle updates for 30 seconds, fetching every 5 seconds: 110 | - `$ php manager.php l=30 i=5` 111 | 112 | **cron** 113 | 114 | Execute commands via cron: 115 | - `$ php manager.php a=cron g=maintenance` or multiple groups 116 | - `$ php manager.php a=cron g=maintenance,cleanup` 117 | 118 | ### Create the manager PHP file 119 | 120 | You can name this file whatever you like, it just has to be somewhere inside your PHP project (preferably in the root folder to make things easier). 121 | (Let's assume our file is called `manager.php`) 122 | 123 | Let's start off with a simple example that uses the webhook method: 124 | ```php 125 | '12345:my_api_key', 136 | 137 | // Extras. 138 | 'bot_username' => 'my_own_bot', 139 | 'secret' => 'super_secret', 140 | 'webhook' => [ 141 | 'url' => 'https://example.com/manager.php', 142 | ] 143 | ]); 144 | $bot->run(); 145 | } catch (\Exception $e) { 146 | echo $e->getMessage() . PHP_EOL; 147 | } 148 | ``` 149 | 150 | ### Set vital bot parameters 151 | 152 | The only vital parameter is the API key: 153 | 154 | ```php 155 | $bot = new BotManager([ 156 | // (string) Bot API key provided by @BotFather. 157 | 'api_key' => '12345:my_api_key', 158 | ... 159 | ]); 160 | ``` 161 | 162 | ### Set extra bot parameters 163 | 164 | Apart from the necessary API key, the bot can be easily configured using extra parameters. 165 | 166 | Set the webhook? Enable admins? Add custom command paths? 167 | 168 | **All no problem!** 169 | 170 | The `secret` is a user-defined key that is required to execute any of the library's features via webhook. 171 | Best make it long, random and very unique! 172 | 173 | For 84 random characters: 174 | - If you have `pwgen` installed, just execute `pwgen 84 1` and copy the output. 175 | - If you have `openssl` installed, use `openssl rand -hex 84`. 176 | - Or just go [here][random-characters] and put all the output onto a single line. 177 | 178 | (You get 2 guesses why 84 is a good number :wink:) 179 | 180 | Below is a complete list of all available extra parameters. 181 | 182 | ```php 183 | $bot = new BotManager([ 184 | ... 185 | // (string) Bot username that was defined when creating the bot. 186 | 'bot_username' => 'my_own_bot', 187 | 188 | // (string) A secret password required to authorise access to the webhook. 189 | 'secret' => 'super_secret', 190 | 191 | // (array) All options that have to do with the webhook. 192 | 'webhook' => [ 193 | // (string) URL to the manager PHP file used for setting up the webhook. 194 | 'url' => 'https://example.com/manager.php', 195 | // (string) Path to a self-signed certificate (if necessary). 196 | 'certificate' => __DIR__ . '/server.crt', 197 | // (int) Maximum allowed simultaneous HTTPS connections to the webhook. 198 | 'max_connections' => 20, 199 | // (array) List the types of updates you want your bot to receive. 200 | 'allowed_updates' => ['message', 'edited_channel_post', 'callback_query'], 201 | // (string) Secret token to validate webhook requests. 202 | 'secret_token' => 'super_secret_token', 203 | ], 204 | 205 | // (bool) Only allow webhook access from valid Telegram API IPs. 206 | 'validate_request' => true, 207 | // (array) When using `validate_request`, also allow these IPs. 208 | 'valid_ips' => [ 209 | '1.2.3.4', // single 210 | '192.168.1.0/24', // CIDR 211 | '10/8', // CIDR (short) 212 | '5.6.*', // wildcard 213 | '1.1.1.1-2.2.2.2', // range 214 | ], 215 | 216 | // (array) All options that have to do with the limiter. 217 | 'limiter' => [ 218 | // (bool) Enable or disable the limiter functionality. 219 | 'enabled' => true, 220 | // (array) Any extra options to pass to the limiter. 221 | 'options' => [ 222 | // (float) Interval between request handles. 223 | 'interval' => 0.5, 224 | ], 225 | ], 226 | 227 | // (array) An array of user ids that have admin access to your bot (must be integers). 228 | 'admins' => [12345], 229 | 230 | // (array) Mysql credentials to connect a database (necessary for [`getUpdates`](#using-getupdates-method) method!). 231 | 'mysql' => [ 232 | 'host' => '127.0.0.1', 233 | 'port' => 3306, // optional 234 | 'user' => 'root', 235 | 'password' => 'root', 236 | 'database' => 'telegram_bot', 237 | 'table_prefix' => 'tbl_prfx_', // optional 238 | 'encoding' => 'utf8mb4', // optional 239 | ], 240 | 241 | // (array) List of configurable paths. 242 | 'paths' => [ 243 | // (string) Custom download path. 244 | 'download' => __DIR__ . '/Download', 245 | // (string) Custom upload path. 246 | 'upload' => __DIR__ . '/Upload', 247 | ], 248 | 249 | // (array) All options that have to do with commands. 250 | 'commands' => [ 251 | // (array) A list of custom commands paths. 252 | 'paths' => [ 253 | __DIR__ . '/CustomCommands', 254 | ], 255 | // (array) A list of all custom command configs. 256 | 'configs' => [ 257 | 'sendtochannel' => ['your_channel' => '@my_channel'], 258 | 'weather' => ['owm_api_key' => 'owm_api_key_12345'], 259 | ], 260 | ], 261 | 262 | // (array) All options that have to do with cron. 263 | 'cron' => [ 264 | // (array) List of groups that contain the commands to execute. 265 | 'groups' => [ 266 | // Each group has a name and array of commands. 267 | // When no group is defined, the default group gets executed. 268 | 'default' => [ 269 | '/default_cron_command', 270 | ], 271 | 'maintenance' => [ 272 | '/db_cleanup', 273 | '/db_repair', 274 | '/message_admins Maintenance completed', 275 | ], 276 | ], 277 | ], 278 | 279 | // (string) Override the custom input of your bot (mostly for testing purposes!). 280 | 'custom_input' => '{"some":"raw", "json":"update"}', 281 | ]); 282 | ``` 283 | 284 | ### Using getUpdates method 285 | 286 | Using the `getUpdates` method must not have a `webhook` parameter set and requires a MySQL database connection: 287 | ```php 288 | $bot = new BotManager([ 289 | ... 290 | // Extras. 291 | 'mysql' => [ 292 | 'host' => '127.0.0.1', 293 | 'port' => 3306, // optional 294 | 'user' => 'root', 295 | 'password' => 'root', 296 | 'database' => 'telegram_bot', 297 | 'table_prefix' => 'tbl_prfx_', // optional 298 | 'encoding' => 'utf8mb4', // optional 299 | ], 300 | ]); 301 | ``` 302 | 303 | Now, the updates can be done either through the [browser](#via-browser) or [via CLI](#via-cli). 304 | 305 | #### Custom getUpdates output 306 | 307 | A callback can be defined, to override the default output when updates are handled via getUpdates. 308 | 309 | Example of the default output: 310 | ``` 311 | ... 312 | 2017-07-10 14:59:25 - Updates processed: 1 313 | 123456: 314 | 2017-07-10 14:59:27 - Updates processed: 0 315 | 2017-07-10 14:59:30 - Updates processed: 0 316 | 2017-07-10 14:59:32 - Updates processed: 0 317 | 2017-07-10 14:59:34 - Updates processed: 1 318 | 123456: 319 | 2017-07-10 14:59:36 - Updates processed: 0 320 | ... 321 | ``` 322 | 323 | Using custom callback that must return a string: 324 | ```php 325 | // In manager.php after $bot has been defined: 326 | $bot->setCustomGetUpdatesCallback(function (ServerResponse $get_updates_response) { 327 | $results = array_filter((array) $get_updates_response->getResult()); 328 | 329 | return sprintf('There are %d update(s)' . PHP_EOL, count($results)); 330 | }); 331 | ``` 332 | output: 333 | ``` 334 | ... 335 | There are 0 update(s) 336 | There are 0 update(s) 337 | There are 2 update(s) 338 | There are 1 update(s) 339 | ... 340 | ``` 341 | 342 | ## Development 343 | 344 | When running live bot tests on a fork, you must enter the following environment variables to your [repository settings][github-actions-encrypted-secrets]: 345 | ``` 346 | API_KEY="12345:your_api_key" 347 | BOT_USERNAME="username_of_your_bot" 348 | ``` 349 | It probably makes sense for you to create a new dummy bot for this. 350 | 351 | ## Security 352 | 353 | See [SECURITY](SECURITY.md) for more information. 354 | 355 | ## Donate 356 | 357 | All work on this bot consists of many hours of coding during our free time, to provide you with a Telegram Bot library that is easy to use and extend. 358 | If you enjoy using this library and would like to say thank you, donations are a great way to show your support. 359 | 360 | Donations are invested back into the project :+1: 361 | 362 | Thank you for keeping this project alive :pray: 363 | 364 | - [![Patreon](https://user-images.githubusercontent.com/9423417/59235980-a5fa6b80-8be3-11e9-8ae7-020bc4ae9baa.png) Patreon.com/phptelegrambot][Patreon] 365 | - [![OpenCollective](https://user-images.githubusercontent.com/9423417/59235978-a561d500-8be3-11e9-89be-82ec54be1546.png) OpenCollective.com/php-telegram-bot][OpenCollective] 366 | - [![Ko-fi](https://user-images.githubusercontent.com/9423417/59235976-a561d500-8be3-11e9-911d-b1908c3e6a33.png) Ko-fi.com/phptelegrambot][Ko-fi] 367 | - [![Tidelift](https://user-images.githubusercontent.com/9423417/59235982-a6930200-8be3-11e9-8ac2-bfb6991d80c5.png) Tidelift.com/longman/telegram-bot][Tidelift] 368 | - [![Liberapay](https://user-images.githubusercontent.com/9423417/59235977-a561d500-8be3-11e9-9d16-bc3b13d3ceba.png) Liberapay.com/PHP-Telegram-Bot][Liberapay] 369 | - [![PayPal](https://user-images.githubusercontent.com/9423417/59235981-a5fa6b80-8be3-11e9-9761-15eb7a524cb0.png) PayPal.me/noplanman][PayPal-noplanman] (account of @noplanman) 370 | 371 | --- 372 | 373 |
374 | 375 | Get professional support for this package with a Tidelift subscription 376 | 377 |
378 | 379 | Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. 380 |
381 |
382 | 383 | [github-tgbot-core]: https://github.com/php-telegram-bot/core "PHP Telegram Bot on GitHub" 384 | [github-tgbot-core-instructions]: https://github.com/php-telegram-bot/core#instructions "PHP Telegram Bot instructions on GitHub" 385 | [github-tgbot-manager]: https://github.com/php-telegram-bot/telegram-bot-manager "PHP Telegram Bot Manager on GitHub" 386 | [packagist-tgbot-manager]: https://packagist.org/packages/php-telegram-bot/telegram-bot-manager "PHP Telegram Bot Manager on Packagist" 387 | [license]: https://github.com/php-telegram-bot/telegram-bot-manager/blob/master/LICENSE "PHP Telegram Bot Manager license" 388 | 389 | [support-group-badge]: https://img.shields.io/badge/telegram-@PHP__Telegram__Bot__Support-64659d.svg 390 | [support-group]: https://telegram.me/PHP_Telegram_Bot_Support 391 | [donate-badge]: https://img.shields.io/badge/%F0%9F%92%99-Donate%20%2F%20Support%20Us-blue.svg 392 | [code-quality-badge]: https://img.shields.io/scrutinizer/g/php-telegram-bot/telegram-bot-manager.svg 393 | [code-quality]: https://scrutinizer-ci.com/g/php-telegram-bot/telegram-bot-manager/?branch=master "Code quality on Scrutinizer" 394 | [code-coverage-badge]: https://img.shields.io/codecov/c/github/php-telegram-bot/telegram-bot-manager.svg 395 | [code-coverage]: https://codecov.io/gh/php-telegram-bot/telegram-bot-manager "Code coverage on Codecov" 396 | [tests-status-badge]: https://github.com/php-telegram-bot/telegram-bot-manager/actions/workflows/tests.yml/badge.svg?branch=master 397 | [tests-status]: https://github.com/php-telegram-bot/telegram-bot-manager/workflows/tests.yml "Tests status on GitHub Actions" 398 | 399 | [latest-version-badge]: https://img.shields.io/packagist/v/php-telegram-bot/telegram-bot-manager.svg 400 | [dependencies-badge]: https://tidelift.com/badges/github/php-telegram-bot/core?style=flat 401 | [total-downloads-badge]: https://img.shields.io/packagist/dt/php-telegram-bot/telegram-bot-manager.svg 402 | [license-badge]: https://img.shields.io/packagist/l/php-telegram-bot/telegram-bot-manager.svg 403 | 404 | [random-characters]: https://www.random.org/strings/?num=7&len=12&digits=on&upperalpha=on&loweralpha=on&unique=on&format=plain&rnd=new "Generate random characters" 405 | [github-actions-encrypted-secrets]: https://docs.github.com/en/actions/security-guides/encrypted-secrets "Encrypted Secrets for GitHub Actions" 406 | [Composer]: https://getcomposer.org/ "Composer" 407 | 408 | [Patreon]: https://www.patreon.com/phptelegrambot "Support us on Patreon" 409 | [OpenCollective]: https://opencollective.com/php-telegram-bot "Support us on Open Collective" 410 | [Ko-fi]: https://ko-fi.com/phptelegrambot "Support us on Ko-fi" 411 | [Tidelift]: https://tidelift.com/subscription/pkg/packagist-php-telegram-bot-telegram-bot-manager?utm_source=packagist-php-telegram-bot-telegram-bot-manager&utm_medium=referral&utm_campaign=readme "Support us on Tidelift" 412 | [Liberapay]: https://liberapay.com/PHP-Telegram-Bot "Donate with Liberapay" 413 | [PayPal-noplanman]: https://paypal.me/noplanman "Donate with PayPal" 414 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-telegram-bot/telegram-bot-manager", 3 | "type": "library", 4 | "description": "PHP Telegram Bot Manager", 5 | "keywords": ["telegram", "bot", "manager"], 6 | "license": "MIT", 7 | "homepage": "https://github.com/php-telegram-bot/telegram-bot-manager", 8 | "support": { 9 | "issues": "https://github.com/php-telegram-bot/telegram-bot-manager/issues", 10 | "source": "https://github.com/php-telegram-bot/telegram-bot-manager" 11 | }, 12 | "authors": [ 13 | { 14 | "name": "Armando Lüscher", 15 | "email": "armando@noplanman.ch", 16 | "homepage": "https://noplanman.ch", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.0", 22 | "longman/telegram-bot": "^0.81", 23 | "longman/ip-tools": "^1.2", 24 | "psr/log": "^1.0|^2.0|^3.0" 25 | }, 26 | "require-dev": { 27 | "php-parallel-lint/php-parallel-lint": "^1.3", 28 | "phpunit/phpunit": "^9.5", 29 | "squizlabs/php_codesniffer": "^3.7" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "TelegramBot\\TelegramBotManager\\": "src" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "TelegramBot\\TelegramBotManager\\Tests\\": "tests" 39 | } 40 | }, 41 | "scripts": { 42 | "check-code": [ 43 | "vendor/bin/parallel-lint . --exclude vendor", 44 | "vendor/bin/phpcs" 45 | ], 46 | "test": [ 47 | "vendor/bin/phpunit --exclude-group live" 48 | ], 49 | "test-live": [ 50 | "vendor/bin/phpunit" 51 | ], 52 | "test-cov": [ 53 | "vendor/bin/phpunit --coverage-clover coverage.xml --exclude-group live" 54 | ], 55 | "test-cov-live": [ 56 | "vendor/bin/phpunit --coverage-clover coverage.xml" 57 | ], 58 | "test-cov-upload": [ 59 | "curl -s https://codecov.io/bash | bash" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Action.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace TelegramBot\TelegramBotManager; 12 | 13 | use TelegramBot\TelegramBotManager\Exception\InvalidActionException; 14 | 15 | class Action 16 | { 17 | private static array $valid_actions = [ 18 | 'set', 19 | 'unset', 20 | 'reset', 21 | 'handle', 22 | 'cron', 23 | 'webhookinfo', 24 | ]; 25 | 26 | private string $action; 27 | 28 | /** 29 | * @throws InvalidActionException 30 | */ 31 | public function __construct(?string $action = 'handle') 32 | { 33 | $this->action = $action ?: 'handle'; 34 | 35 | if (!$this->isAction(self::$valid_actions)) { 36 | throw new InvalidActionException('Invalid action: ' . $this->action); 37 | } 38 | } 39 | 40 | public function isAction(array|string $actions): bool 41 | { 42 | return in_array($this->action, (array) $actions, true); 43 | } 44 | 45 | public function getAction(): string 46 | { 47 | return $this->action; 48 | } 49 | 50 | public static function getValidActions(): array 51 | { 52 | return self::$valid_actions; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/BotManager.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace TelegramBot\TelegramBotManager; 12 | 13 | use Closure; 14 | use Exception; 15 | use Longman\IPTools\Ip; 16 | use Longman\TelegramBot\Entities\CallbackQuery; 17 | use Longman\TelegramBot\Entities\ChosenInlineResult; 18 | use Longman\TelegramBot\Entities\InlineQuery; 19 | use Longman\TelegramBot\Entities\Message; 20 | use Longman\TelegramBot\Entities\ServerResponse; 21 | use Longman\TelegramBot\Entities\Update; 22 | use Longman\TelegramBot\Exception\TelegramException; 23 | use Longman\TelegramBot\Request; 24 | use Longman\TelegramBot\Telegram; 25 | use TelegramBot\TelegramBotManager\Exception\InvalidAccessException; 26 | use TelegramBot\TelegramBotManager\Exception\InvalidActionException; 27 | use TelegramBot\TelegramBotManager\Exception\InvalidParamsException; 28 | use TelegramBot\TelegramBotManager\Exception\InvalidWebhookException; 29 | 30 | class BotManager 31 | { 32 | public const VERSION = '2.0.0'; 33 | 34 | /** 35 | * @link https://core.telegram.org/bots/webhooks#the-short-version 36 | * @var array Telegram webhook servers IP ranges 37 | */ 38 | public const TELEGRAM_IP_RANGES = ['149.154.160.0/20', '91.108.4.0/22']; 39 | 40 | private string $output = ''; 41 | private Telegram $telegram; 42 | private Params $params; 43 | private Action $action; 44 | private ?Closure $custom_get_updates_callback = null; 45 | 46 | /** 47 | * @throws InvalidParamsException 48 | * @throws InvalidActionException 49 | * @throws TelegramException 50 | */ 51 | public function __construct(array $params) 52 | { 53 | $this->params = new Params($params); 54 | $this->action = new Action($this->params->getScriptParam('a')); 55 | 56 | $this->telegram = new Telegram( 57 | $this->params->getBotParam('api_key'), 58 | $this->params->getBotParam('bot_username') ?? '' 59 | ); 60 | } 61 | 62 | public static function inTest(): bool 63 | { 64 | return defined('PHPUNIT_TESTSUITE') && PHPUNIT_TESTSUITE === true; 65 | } 66 | 67 | public function getTelegram(): Telegram 68 | { 69 | return $this->telegram; 70 | } 71 | 72 | public function getParams(): Params 73 | { 74 | return $this->params; 75 | } 76 | 77 | public function getAction(): Action 78 | { 79 | return $this->action; 80 | } 81 | 82 | /** 83 | * @throws TelegramException 84 | * @throws InvalidAccessException 85 | * @throws InvalidWebhookException 86 | * @throws Exception 87 | */ 88 | public function run(): static 89 | { 90 | $this->validateSecret(); 91 | $this->validateRequest(); 92 | 93 | if ($this->action->isAction('webhookinfo')) { 94 | $webhookinfo = Request::getWebhookInfo(); 95 | /** @noinspection ForgottenDebugOutputInspection */ 96 | print_r($webhookinfo->getResult() ?: $webhookinfo->printError(true)); 97 | return $this; 98 | } 99 | if ($this->action->isAction(['set', 'unset', 'reset'])) { 100 | return $this->validateAndSetWebhook(); 101 | } 102 | 103 | $this->setBotExtras(); 104 | 105 | if ($this->action->isAction('handle')) { 106 | $this->handleRequest(); 107 | } elseif ($this->action->isAction('cron')) { 108 | $this->handleCron(); 109 | } 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * @throws InvalidAccessException 116 | */ 117 | public function validateSecret(bool $force = false): static 118 | { 119 | // If we're running from CLI, secret isn't necessary. 120 | if ($force || 'cli' !== PHP_SAPI) { 121 | $secret = $this->params->getBotParam('secret'); 122 | $secret_get = $this->params->getScriptParam('s'); 123 | if (!isset($secret, $secret_get) || $secret !== $secret_get) { 124 | throw new InvalidAccessException('Invalid access'); 125 | } 126 | } 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * @throws TelegramException 133 | * @throws InvalidWebhookException 134 | */ 135 | public function validateAndSetWebhook(): static 136 | { 137 | $webhook = $this->params->getBotParam('webhook'); 138 | if (empty($webhook['url'] ?? null) && $this->action->isAction(['set', 'reset'])) { 139 | throw new InvalidWebhookException('Invalid webhook'); 140 | } 141 | 142 | if ($this->action->isAction(['unset', 'reset'])) { 143 | $this->handleOutput($this->telegram->deleteWebhook()->getDescription() . PHP_EOL); 144 | // When resetting the webhook, sleep for a bit to prevent too many requests. 145 | $this->action->isAction('reset') && sleep(1); 146 | } 147 | 148 | if ($this->action->isAction(['set', 'reset'])) { 149 | $webhook_params = array_filter([ 150 | 'certificate' => $webhook['certificate'] ?? null, 151 | 'max_connections' => $webhook['max_connections'] ?? null, 152 | 'allowed_updates' => $webhook['allowed_updates'] ?? null, 153 | 'secret_token' => $webhook['secret_token'] ?? null, 154 | ], function ($v, $k) { 155 | if ($k === 'allowed_updates') { 156 | // Special case for allowed_updates, which can be an empty array. 157 | return is_array($v); 158 | } 159 | return !empty($v); 160 | }, ARRAY_FILTER_USE_BOTH); 161 | 162 | $webhook['url'] .= parse_url($webhook['url'], PHP_URL_QUERY) === null ? '?' : '&'; 163 | $this->handleOutput( 164 | $this->telegram->setWebhook( 165 | $webhook['url'] . 'a=handle&s=' . $this->params->getBotParam('secret'), 166 | $webhook_params 167 | )->getDescription() . PHP_EOL 168 | ); 169 | } 170 | 171 | return $this; 172 | } 173 | 174 | private function handleOutput(string $output): static 175 | { 176 | $this->output .= $output; 177 | 178 | if (!self::inTest()) { 179 | echo $output; 180 | } 181 | 182 | return $this; 183 | } 184 | 185 | /** 186 | * @throws TelegramException 187 | */ 188 | public function setBotExtras(): static 189 | { 190 | $this->setBotExtrasTelegram(); 191 | $this->setBotExtrasRequest(); 192 | 193 | return $this; 194 | } 195 | 196 | /** 197 | * @throws TelegramException 198 | */ 199 | protected function setBotExtrasTelegram(): static 200 | { 201 | $simple_extras = [ 202 | 'admins' => 'enableAdmins', 203 | 'commands.paths' => 'addCommandsPaths', 204 | 'custom_input' => 'setCustomInput', 205 | 'paths.download' => 'setDownloadPath', 206 | 'paths.upload' => 'setUploadPath', 207 | ]; 208 | // For simple telegram extras, just pass the single param value to the Telegram method. 209 | foreach ($simple_extras as $param_key => $method) { 210 | $param = $this->params->getBotParam($param_key); 211 | if (null !== $param) { 212 | $this->telegram->$method($param); 213 | } 214 | } 215 | 216 | // Database. 217 | if ($mysql_config = $this->params->getBotParam('mysql', [])) { 218 | $this->telegram->enableMySql( 219 | $mysql_config, 220 | $mysql_config['table_prefix'] ?? '', 221 | $mysql_config['encoding'] ?? 'utf8mb4' 222 | ); 223 | } 224 | 225 | // Custom command configs. 226 | $command_configs = $this->params->getBotParam('commands.configs', []); 227 | foreach ($command_configs as $command => $config) { 228 | $this->telegram->setCommandConfig($command, $config); 229 | } 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * @throws TelegramException 236 | */ 237 | protected function setBotExtrasRequest(): static 238 | { 239 | $request_extras = [ 240 | // None at the moment... 241 | ]; 242 | // For request extras, just pass the single param value to the Request method. 243 | foreach ($request_extras as $param_key => $method) { 244 | $param = $this->params->getBotParam($param_key); 245 | if (null !== $param) { 246 | Request::$method($param); 247 | } 248 | } 249 | 250 | // Special cases. 251 | $limiter_enabled = $this->params->getBotParam('limiter.enabled'); 252 | if ($limiter_enabled !== null) { 253 | $limiter_options = $this->params->getBotParam('limiter.options', []); 254 | Request::setLimiter($limiter_enabled, $limiter_options); 255 | } 256 | 257 | return $this; 258 | } 259 | 260 | /** 261 | * @throws TelegramException 262 | */ 263 | public function handleRequest(): static 264 | { 265 | if ($this->params->getBotParam('webhook.url')) { 266 | return $this->handleWebhook(); 267 | } 268 | 269 | if ($loop_time = $this->getLoopTime()) { 270 | return $this->handleGetUpdatesLoop($loop_time, $this->getLoopInterval()); 271 | } 272 | 273 | return $this->handleGetUpdates(); 274 | } 275 | 276 | /** 277 | * @throws TelegramException 278 | */ 279 | public function handleCron(): static 280 | { 281 | $groups = explode(',', $this->params->getScriptParam('g', 'default')); 282 | 283 | $commands = []; 284 | foreach ($groups as $group) { 285 | $commands[] = $this->params->getBotParam('cron.groups.' . $group, []); 286 | } 287 | $this->telegram->runCommands(array_merge(...$commands)); 288 | 289 | return $this; 290 | } 291 | 292 | public function getLoopTime(): int 293 | { 294 | $loop_time = $this->params->getScriptParam('l'); 295 | 296 | if (null === $loop_time) { 297 | return 0; 298 | } 299 | 300 | if (is_string($loop_time) && '' === trim($loop_time)) { 301 | return 604800; // Default to 7 days. 302 | } 303 | 304 | return max(0, (int) $loop_time); 305 | } 306 | 307 | public function getLoopInterval(): int 308 | { 309 | $interval_time = $this->params->getScriptParam('i'); 310 | 311 | if (null === $interval_time || (is_string($interval_time) && '' === trim($interval_time))) { 312 | return 2; 313 | } 314 | 315 | // Minimum interval is 1 second. 316 | return max(1, (int) $interval_time); 317 | } 318 | 319 | /** 320 | * @throws TelegramException 321 | */ 322 | public function handleGetUpdatesLoop(int $loop_time_in_seconds, int $loop_interval_in_seconds = 2): static 323 | { 324 | // Remember the time we started this loop. 325 | $now = time(); 326 | 327 | $this->handleOutput('Looping getUpdates until ' . date('Y-m-d H:i:s', $now + $loop_time_in_seconds) . PHP_EOL); 328 | 329 | while ($now > time() - $loop_time_in_seconds) { 330 | $this->handleGetUpdates(); 331 | 332 | // Chill a bit. 333 | sleep($loop_interval_in_seconds); 334 | } 335 | 336 | return $this; 337 | } 338 | 339 | public function setCustomGetUpdatesCallback(callable $callback): static 340 | { 341 | $this->custom_get_updates_callback = Closure::fromCallable($callback); 342 | 343 | return $this; 344 | } 345 | 346 | /** 347 | * @throws TelegramException 348 | */ 349 | public function handleGetUpdates(): static 350 | { 351 | $get_updates_response = $this->telegram->handleGetUpdates(); 352 | 353 | // Check if the user has set a custom callback for handling the response. 354 | if ($this->custom_get_updates_callback) { 355 | $this->handleOutput(($this->custom_get_updates_callback)($get_updates_response)); 356 | } else { 357 | $this->handleOutput($this->defaultGetUpdatesCallback($get_updates_response)); 358 | } 359 | 360 | return $this; 361 | } 362 | 363 | protected function defaultGetUpdatesCallback(ServerResponse $get_updates_response): string 364 | { 365 | if (!$get_updates_response->isOk()) { 366 | return sprintf( 367 | '%s - Failed to fetch updates' . PHP_EOL . '%s', 368 | date('Y-m-d H:i:s'), 369 | $get_updates_response->printError(true) 370 | ); 371 | } 372 | 373 | /** @var Update[] $results */ 374 | $results = array_filter((array) $get_updates_response->getResult()); 375 | 376 | $output = sprintf( 377 | '%s - Updates processed: %d' . PHP_EOL, 378 | date('Y-m-d H:i:s'), 379 | count($results) 380 | ); 381 | 382 | foreach ($results as $result) { 383 | $update_content = $result->getUpdateContent(); 384 | 385 | $chat_id = 'n/a'; 386 | $text = $result->getUpdateType(); 387 | 388 | if ($update_content instanceof Message) { 389 | $chat_id = $update_content->getChat()->getId(); 390 | $text .= ";{$update_content->getType()}"; 391 | } elseif ($update_content instanceof InlineQuery || $update_content instanceof ChosenInlineResult) { 392 | $chat_id = $update_content->getFrom()->getId(); 393 | $text .= ";{$update_content->getQuery()}"; 394 | } elseif ($update_content instanceof CallbackQuery) { 395 | $message = $update_content->getMessage(); 396 | if ($message && $message->getChat()) { 397 | $chat_id = $message->getChat()->getId(); 398 | } 399 | 400 | $text .= ";{$update_content->getData()}"; 401 | } 402 | 403 | $output .= sprintf( 404 | '%s: <%s>' . PHP_EOL, 405 | $chat_id, 406 | preg_replace('/\s+/', ' ', trim($text)) 407 | ); 408 | } 409 | 410 | return $output; 411 | } 412 | 413 | /** 414 | * @throws TelegramException 415 | */ 416 | public function handleWebhook(): static 417 | { 418 | $this->telegram->handle(); 419 | 420 | return $this; 421 | } 422 | 423 | public function getOutput(): string 424 | { 425 | $output = $this->output; 426 | $this->output = ''; 427 | 428 | return $output; 429 | } 430 | 431 | public function isValidRequest(): bool 432 | { 433 | // If we're running from CLI, requests are always valid, unless we're running the tests. 434 | if ((!self::inTest() && 'cli' === PHP_SAPI) || false === $this->params->getBotParam('validate_request')) { 435 | return true; 436 | } 437 | 438 | return $this->isValidRequestIp() 439 | && $this->isValidRequestSecretToken(); 440 | } 441 | 442 | protected function isValidRequestIp(): bool 443 | { 444 | $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; 445 | foreach (['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR'] as $key) { 446 | if (filter_var($_SERVER[$key] ?? null, FILTER_VALIDATE_IP)) { 447 | $ip = $_SERVER[$key]; 448 | break; 449 | } 450 | } 451 | 452 | return Ip::match($ip, array_merge( 453 | self::TELEGRAM_IP_RANGES, 454 | (array) $this->params->getBotParam('valid_ips', []) 455 | )); 456 | } 457 | 458 | protected function isValidRequestSecretToken(): bool 459 | { 460 | $secret_token = $this->params->getBotParam('webhook.secret_token'); 461 | $secret_token_api = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? null; 462 | 463 | if ($secret_token || $secret_token_api) { 464 | return $secret_token === $secret_token_api; 465 | } 466 | 467 | return true; 468 | } 469 | 470 | /** 471 | * @throws InvalidAccessException 472 | */ 473 | private function validateRequest(): void 474 | { 475 | if (!$this->isValidRequest()) { 476 | throw new InvalidAccessException('Invalid access'); 477 | } 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /src/Exception/BotManagerException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace TelegramBot\TelegramBotManager\Exception; 12 | 13 | class BotManagerException extends \Exception 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Exception/InvalidAccessException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace TelegramBot\TelegramBotManager\Exception; 12 | 13 | class InvalidAccessException extends BotManagerException 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Exception/InvalidActionException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace TelegramBot\TelegramBotManager\Exception; 12 | 13 | class InvalidActionException extends BotManagerException 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Exception/InvalidParamsException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace TelegramBot\TelegramBotManager\Exception; 12 | 13 | class InvalidParamsException extends BotManagerException 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Exception/InvalidWebhookException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace TelegramBot\TelegramBotManager\Exception; 12 | 13 | class InvalidWebhookException extends BotManagerException 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Params.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace TelegramBot\TelegramBotManager; 12 | 13 | use TelegramBot\TelegramBotManager\Exception\InvalidParamsException; 14 | 15 | class Params 16 | { 17 | private static array $valid_script_params = [ 18 | 's', // secret 19 | 'a', // action 20 | 'l', // loop 21 | 'i', // interval 22 | 'g', // group (for cron) 23 | ]; 24 | 25 | private static array $valid_vital_bot_params = [ 26 | 'api_key', 27 | ]; 28 | 29 | private static array $valid_extra_bot_params = [ 30 | 'bot_username', 31 | 'secret', 32 | 'validate_request', 33 | 'valid_ips', 34 | 'webhook', 35 | 'logging', 36 | 'limiter', 37 | 'admins', 38 | 'mysql', 39 | 'paths', 40 | 'commands', 41 | 'cron', 42 | 'custom_input', 43 | ]; 44 | 45 | private array $script_params = []; 46 | 47 | private array $bot_params = [ 48 | 'validate_request' => true, 49 | ]; 50 | 51 | /** 52 | * @throws InvalidParamsException 53 | */ 54 | public function __construct(array $params) 55 | { 56 | $this->validateAndSetBotParams($params); 57 | $this->validateAndSetScriptParams(); 58 | } 59 | 60 | /** 61 | * @throws InvalidParamsException 62 | */ 63 | private function validateAndSetBotParams(array $params): self 64 | { 65 | $this->validateAndSetBotParamsVital($params); 66 | $this->validateAndSetBotParamsSpecial($params); 67 | $this->validateAndSetBotParamsExtra($params); 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * @throws InvalidParamsException 74 | */ 75 | private function validateAndSetBotParamsVital(array $params): void 76 | { 77 | foreach (self::$valid_vital_bot_params as $vital_key) { 78 | if (!array_key_exists($vital_key, $params)) { 79 | throw new InvalidParamsException('Some vital info is missing: ' . $vital_key); 80 | } 81 | 82 | $this->bot_params[$vital_key] = $params[$vital_key]; 83 | } 84 | } 85 | 86 | /** 87 | * @throws InvalidParamsException 88 | */ 89 | private function validateAndSetBotParamsSpecial(array $params): void 90 | { 91 | // Special case, where secret MUST be defined if we have a webhook. 92 | if (($params['webhook']['url'] ?? null) && !($params['secret'] ?? null)) { 93 | // This does not apply when using CLI, but make sure it gets tested for! 94 | if ('cli' !== PHP_SAPI || BotManager::inTest()) { 95 | throw new InvalidParamsException('Some vital info is missing: secret'); 96 | } 97 | } 98 | } 99 | 100 | private function validateAndSetBotParamsExtra(array $params): void 101 | { 102 | foreach (self::$valid_extra_bot_params as $extra_key) { 103 | if (!array_key_exists($extra_key, $params)) { 104 | continue; 105 | } 106 | 107 | $this->bot_params[$extra_key] = $params[$extra_key]; 108 | } 109 | } 110 | 111 | /** 112 | * Handle all script params, via web server handler or CLI. 113 | * 114 | * https://url/entry.php?s=&a=&l= 115 | * $ php entry.php s= a= l= 116 | */ 117 | private function validateAndSetScriptParams(): self 118 | { 119 | $this->setScriptParams(); 120 | $this->validateScriptParams(); 121 | 122 | return $this; 123 | } 124 | 125 | private function setScriptParams(): void 126 | { 127 | $this->script_params = $_GET; 128 | 129 | // If we're not running from CLI, script parameters are already set from $_GET. 130 | if ('cli' !== PHP_SAPI) { 131 | return; 132 | } 133 | 134 | // We don't need the first arg (the file name). 135 | $args = array_slice($_SERVER['argv'], 1); 136 | 137 | foreach ($args as $arg) { 138 | @list($key, $val) = explode('=', $arg); 139 | isset($key, $val) && $this->script_params[$key] = $val; 140 | } 141 | } 142 | 143 | private function validateScriptParams(): void 144 | { 145 | $this->script_params = array_intersect_key( 146 | $this->script_params, 147 | array_fill_keys(self::$valid_script_params, null) 148 | ); 149 | } 150 | 151 | public function getBotParam(string $param, mixed $default = null): mixed 152 | { 153 | $param_path = explode('.', $param); 154 | 155 | $value = $this->bot_params[array_shift($param_path)] ?? null; 156 | foreach ($param_path as $sub_param_key) { 157 | $value = $value[$sub_param_key] ?? null; 158 | if (null === $value) { 159 | break; 160 | } 161 | } 162 | 163 | return $value ?? $default; 164 | } 165 | 166 | public function getBotParams(): array 167 | { 168 | return $this->bot_params; 169 | } 170 | 171 | public function getScriptParam(string $param, mixed $default = null): mixed 172 | { 173 | return $this->script_params[$param] ?? $default; 174 | } 175 | 176 | public function getScriptParams(): array 177 | { 178 | return $this->script_params; 179 | } 180 | } 181 | --------------------------------------------------------------------------------