├── .gitignore ├── README.md ├── get_updates.php ├── include ├── functions.php └── use_db.php ├── install.php ├── lib ├── db.class.php ├── random_bytes │ ├── LICENSE │ ├── byte_safe_strings.php │ ├── cast_to_int.php │ ├── error_polyfill.php │ ├── random.php │ ├── random_bytes_com_dotnet.php │ ├── random_bytes_dev_urandom.php │ ├── random_bytes_libsodium.php │ ├── random_bytes_libsodium_legacy.php │ ├── random_bytes_mcrypt.php │ └── random_int.php ├── telegrambot.class.php └── uploader.class.php ├── send_message.php └── send_photo.php /.gitignore: -------------------------------------------------------------------------------- 1 | include/config.php 2 | tmp/ 3 | custom/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram API WebServer in PHP/RDBMS 2 | Web server to manage your Telegram bot (with its own Telegram API wrapper), written in PHP and SQL. 3 | 4 | Useful to **integrate** Telegram API with **any client application** (Java Desktop, .NET apps, Android APKs, iOS apps, frontend AJAX requests, whatever) with a **RESTful web service** such as this web server; you can send messages, photos and receive data through/towards bot. 5 | 6 | Useful if you want to **manage** a Telegram bot from a web server AND secure your key, using a VPS or any web hosting service (even free ones) that provides you PHP and any RDBMS. 7 | 8 | Easy to use, to configure and lightweight. 9 | 10 | ## Requirements 11 | 1. **Any** web hosting service with **PHP+SQL plan** (Even free tier options, for example https://www.000webhost.com/ or https://it.altervista.org/ etc.); 12 | 2. The **API key of your Telegram bot** (Read Step 1 to discover how to create a bot: https://github.com/fabnicolas/telegram-bot-readytouse/blob/master/README.md) 13 | 14 | ## Setup (Web Server) 15 | 1. **Create** file `include/config.php` with the following content: 16 | ```php 17 | "your_key", 20 | 'db_host' => "localhost", 21 | 'db_user' => "your_username", 22 | 'db_password' => "your_password", 23 | 'db_name' => "your_database" 24 | ]; 25 | ?> 26 | ``` 27 | 28 | 2. **Run** https://your_url.com/install.php . It will **create the following table inside your database**: 29 | ```sql 30 | updates(update_id, message_id, from_id, from_username, date, text) 31 | ``` 32 | ...Ah, the installation script will self-destruct after use. *PHP magic. Don't worry - your server will not explode (I hope :P)* 33 | 34 | 3. **Start use the web server!** 35 | 36 | ## Understanding the project 37 | The project consists on: 38 | - A working web server that can be deployed where you want (On a web hosting service, or in local...); 39 | - API wrapper classes: 40 | 41 | -- **TelegramBot** (To manage bot); 42 | 43 | -- **DB** (To interact with any SQL database); 44 | 45 | -- **Uploader** (To upload media). 46 | 47 | 48 | This web server offers built-in endpoints; if you are interested in a ready-to-use solution, routes are already set up. 49 | Check "Web server endpoints" to have a detailed understanding on how to use those endpoints to integrate this server a REST service in your applications. 50 | 51 | The offered endpoints actually supports: 52 | - Sending messages to a particular user; 53 | - Sending photos to a particular user; 54 | - Getting incoming messages from a particular user. 55 | 56 | If you want to customize behaviors - Check "Coded functionalities"; it provides you insights on how the code works, how you can rework it and how to use classes at your advantage to provide additional functionalities. 57 | 58 | 59 | ## Web server endpoints 60 | The following endpoints were heavily tested with Advanced Rest Client. Postman is an equivalent. 61 | 62 | Treat this server like a REST server. 63 | 64 | As base URL we will consider https://your_url.com/ for examples. 65 | Remember that each Telegram user has its own `chat_id`: for testing purposes, retrieve yours by contacting https://t.me/RawDataBot ; also remember the user you want to send requests to **must have `/start`-ed** your Telegram bot. 66 | 67 | ### Send a message 68 | ``` 69 | POST https://your_url.com/send_message.php 70 | Body Content-Type: application/x-www-form-urlencoded 71 | Payload: chat_id=YOUR_CHAT_ID; message: Your message 72 | ``` 73 | Answer (success): 74 | ```json 75 | {status: 0, message: 'Message sent.'} 76 | ``` 77 | Effect: Telegram bot will send **"Your message"** to the given user with that `chat_id`. 78 | 79 | ### Send a photo 80 | ``` 81 | POST https://your_url.com/send_photo.php 82 | Body Content-Type: multipart/form-data 83 | Payload: chat_id=YOUR_CHAT_ID; image=[YOUR IMAGE] 84 | ``` 85 | Answer (success): 86 | ```json 87 | {status: 0, message: 'Photo sent.'} 88 | ``` 89 | Effect: Telegram bot will send **the photo attached to the request** to the given user with that `chat_id`. 90 | 91 | ### Get incoming messages from a specific user 92 | This is a wrapped and modified version of getUpdates Telegram API: https://core.telegram.org/bots/api#getting-updates 93 | 94 | The wrapped version: 95 | - **Simplifies** the original API by cutting unnecessary data for a client-to-bot successful communication, showing only `status` of the request, a `message` list and `next_update_id`; 96 | - **Caches** info provided by the original API in a RDBMS. It's eventually possible to separate the caching process from the read requests themselves; 97 | - **Provides** you the last `next_update_id` in a dedicated JSON attribute so that your application can make new requests without recalcolating the new `update_id` parameter. 98 | 99 | **Notice**: `update_id` in the request payload should be implemented as follow: 100 | - = `0` at start of your application; 101 | - = `next_update_id` **of the previous request** to read newest possible messages beginning from that `update_id`. 102 | ``` 103 | POST https://your_url.com/get_updates.php 104 | Body Content-Type: application/x-www-form-urlencoded 105 | Payload: chat_id=YOUR_CHAT_ID; update_id=UPDATE_ID_CURRENT_VALUE 106 | ``` 107 | Sample answer (success): 108 | ```json 109 | { 110 | status: 0, 111 | message: [ 112 | { 113 | "update_id": 100001, 114 | "text": "Hello! Are you there?" 115 | }, 116 | { 117 | "update_id": 100002, 118 | "text": "Are you a bot?" 119 | } 120 | ], 121 | next_update_id: 100003 122 | } 123 | ``` 124 | Effect: the effect is determined by your application *(That parses this JSON as an array of messages and decides what to do for each message)*. 125 | 126 | ## Coded functionalities 127 | Make sure you use the following snippets when `TelegramBot` and `DB` objects are ready to use. 128 | Copy `send_message.php` and use the copy as easiest template to extend functionalities. 129 | 130 | ### Send a message 131 | ```php 132 | $chat_id=12345678; // Chat ID of the target the bot sends message to. 133 | $telegram_bot->sendMessage("I'm a bot; I'll be loyal forever to you, unless you make me crash.", $chat_id); 134 | ``` 135 | 136 | ### Send a photo 137 | ```php 138 | $image_path='./image.png'; // Any path; send_photo.php contains an implementation to handle images sent by user 139 | $chat_id=12345678; // Chat ID of the target the bot sends message to. 140 | $telegram_bot->sendPhoto($image_path, $chat_id, function() use ($image_path){ 141 | unlink($image_path); // Callback to remove image from web server after you sent it. 142 | }); 143 | ``` 144 | 145 | ### Get incoming messages from users to Telegram bot 146 | This code can turn useful in case you want to handle getUpdates API messages **in your way**. 147 | 148 | **Be careful** to **NOT** expose this code as an endpoint for users; keep user privacy in mind. 149 | 150 | This function without parameters will return the list of messages that every user sent **within 24 hours**. 151 | 152 | The easiest way is: 153 | ```php 154 | $result = $telegram_bot->getParsedUpdates(); 155 | if($result){ 156 | foreach($result as $key=>$message){ 157 | // Analyze each $message as an array (update_id, message_id, from_id, from_username, date, text). 158 | } 159 | }else{ 160 | // No new messages incoming. 161 | } 162 | ``` 163 | Check https://core.telegram.org/bots/api#getting-updates , `get_updates.php` and `TelegramBot` class for more details about the structure and the underlying APIs. 164 | 165 | In particular, `get_updates.php` is designed to read new messages from Telegram API, save them inside a database table, then read them - delete records read from the database - and send those back to the user. 166 | 167 | The database structure is necessary in case you want to separate **caching script** (Using Telegram API and storing messages) from **reading script** (Reading from the database, sending data to the clients and deleting records read). 168 | 169 | 170 | 171 | To retrieve more details and handle data *manually*: 172 | ```php 173 | // For some parameters details/usage, check get_updates.php, TelegramBot class and . 174 | $update_id=0; // Update ID; clients send 0 the first time, then uses last possible number. 175 | $offset=0; // Optional, default 0; used to acknowledge messages. Read Telegram API for details. 176 | $limit=100; // Optional, default 100; used to limit the number of updates handled at the same time. 177 | $timeout=0; // Optional, default 0 (short polling); if you want to use long polling, set a value >0. 178 | $result = json_decode($telegram_bot->getUpdates($update_id)); 179 | // Analyze the JSON structure to find interesting data. 180 | ``` 181 | 182 | ### Get incoming messages from a specific user 183 | This code is an extension of *"Coded functionalities -> Get incoming messages from users to Telegram bot"*. 184 | 185 | You can expose this code as an endpoint for users but **keep in mind that it can be bruteforced to detect all messages sent by users within 24 hours**. 186 | 187 | The easiest way is: 188 | ```php 189 | $chat_id=12345678; // Chat ID of the messages sent by the target with that chat ID. 190 | $result = $telegram_bot->getParsedUpdates($chat_id); 191 | if($result){ 192 | foreach($result as $key=>$message){ 193 | // Analyze each $message as an array (update_id, message_id, from_id, from_username, date, text). 194 | } 195 | }else{ 196 | // No new messages incoming from the target with that chat ID. 197 | } 198 | 199 | ``` 200 | In alternative, use the customized approach to parse the JSON object and compare `from_id` values that matches a given `chat_id`. 201 | 202 | ## Pull Requests 203 | PR requests are welcome. You can: 204 | - Add/edit/improve underlying functionalities on classes inside `/lib/` folder; 205 | - Add PHP scripts to provide **additional functionalities** inside project root; 206 | - Add functions for **data validation** inside `/include/functions.php`; 207 | - Ask questions regarding **any** possible concern; 208 | - **Link your repository** down below *if you have a Telegram bot and a web server customized by yourself thanks to this project*. 209 | 210 | You're welcome! -------------------------------------------------------------------------------- /get_updates.php: -------------------------------------------------------------------------------- 1 | getParsedUpdates(null,$update_id); 17 | if($result){ 18 | foreach($result as $key=>$message_params){ 19 | // Insert all the updates (messages) inside the DB table for further reuses (row by row). 20 | $statement = $db->getPDO()->prepare( 21 | "INSERT INTO updates (update_id, message_id, from_id, from_username, date, text) 22 | VALUES (:update_id, :message_id, :from_id, :from_username, :date, :text)"); 23 | $statement->execute($message_params); 24 | } 25 | } 26 | // Note: you can separate this script in a separate file as 'caching script' for a cron job. 27 | 28 | 29 | $telegram_id = post_parameter('chat_id'); // chat_id of the target we want to analyze data from. 30 | if(!empty($telegram_id) && is_numeric($telegram_id)){ 31 | // Let's retrieve from RDBMS all new incoming messages. 32 | $statement = $db->getPDO()->prepare("SELECT update_id,text FROM updates WHERE update_id >= :update_id 33 | AND from_id = :from_id ORDER BY update_id ASC"); 34 | $statement->execute(array('update_id'=>$update_id, 'from_id'=>$telegram_id)); 35 | $result = $statement->fetchAll(); 36 | 37 | // Let's parse those data and get IDs of the messages to mark as read (By deleting their records from database). 38 | $parsed_result = array(); 39 | $update_ids_todelete = array(); 40 | foreach($result as $message){ 41 | $current_update_id=$message['update_id']; 42 | $parsed_result[]=array('update_id'=>$current_update_id, 'text'=>$message['text']); 43 | $update_ids_todelete[]=$current_update_id; 44 | } 45 | $next_update_id=$update_id; 46 | if(!empty($update_ids_todelete)){ 47 | // Deleting procedure. 48 | $update_ids_string=$db->in_composer($update_ids_todelete); 49 | $statement = $db->getPDO()->prepare("DELETE FROM `updates` WHERE FIND_IN_SET(update_id, :update_id)"); 50 | $statement->execute(array('update_id'=>$update_ids_string)); 51 | $next_update_id=$update_ids_todelete[count($update_ids_todelete)-1]+1; 52 | } 53 | 54 | $success=true; 55 | } 56 | 57 | if(!$success) {echo json(array('status'=>1, 'message'=>'Error on backend.')); http_response_code(400);} 58 | else {echo json(array('status'=>0, 'message'=>$parsed_result, 'next_update_id'=>$next_update_id)); http_response_code(200);} 59 | ?> 60 | -------------------------------------------------------------------------------- /include/functions.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /include/use_db.php: -------------------------------------------------------------------------------- 1 | $config['db_host'], 7 | 'dbname' => $config['db_name'] 8 | ); 9 | $admin_params = array( 10 | 'username' => $config['db_user'], 11 | 'password' => $config['db_password'] 12 | ); 13 | $db = new DB($connection_params, $admin_params); 14 | return $db; 15 | ?> -------------------------------------------------------------------------------- /install.php: -------------------------------------------------------------------------------- 1 | pdo->query( 13 | "CREATE TABLE IF NOT EXISTS `updates` ( 14 | `update_id` bigint(20) NOT NULL, 15 | `message_id` bigint(20) NOT NULL, 16 | `from_id` bigint(20) NOT NULL, 17 | `from_username` varchar(255), 18 | `date` bigint(20) NOT NULL, 19 | `text` text NOT NULL, 20 | PRIMARY KEY (`update_id`,`message_id`) 21 | )"); 22 | 23 | class SelfDestroy{function __destruct(){unlink(__FILE__);}} 24 | $installation_finished = new SelfDestroy(); 25 | ?> -------------------------------------------------------------------------------- /lib/db.class.php: -------------------------------------------------------------------------------- 1 | $value){ 11 | $str_params.=$key."=".$value.";"; 12 | } 13 | return (substr($str_params,0,strlen($str_params)-1)); 14 | } 15 | 16 | function in_composer($arr_values){ 17 | $str_params=""; 18 | foreach($arr_values as $key=>$value){ 19 | $str_params.=$value.","; 20 | } 21 | return (substr($str_params,0,strlen($str_params)-1)); 22 | } 23 | 24 | function __construct($connection_params, $admin_params, $connect=true){ 25 | $this->connection_params=$connection_params; 26 | $this->admin_params=$admin_params; 27 | if($connect) $this->pdo = new PDO("mysql:".($this->pdo_params_composer($this->connection_params)), $this->admin_params['username'], $this->admin_params['password']); 28 | } 29 | 30 | function getPDO(){return $this->pdo;} 31 | } 32 | ?> -------------------------------------------------------------------------------- /lib/random_bytes/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Paragon Initiative Enterprises 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. -------------------------------------------------------------------------------- /lib/random_bytes/byte_safe_strings.php: -------------------------------------------------------------------------------- 1 | RandomCompat_strlen($binary_string)) { 133 | return ''; 134 | } 135 | 136 | return (string) mb_substr($binary_string, $start, $length, '8bit'); 137 | } 138 | 139 | } else { 140 | 141 | /** 142 | * substr() implementation that isn't brittle to mbstring.func_overload 143 | * 144 | * This version just uses the default substr() 145 | * 146 | * @param string $binary_string 147 | * @param int $start 148 | * @param int $length (optional) 149 | * 150 | * @throws TypeError 151 | * 152 | * @return string 153 | */ 154 | function RandomCompat_substr($binary_string, $start, $length = null) 155 | { 156 | if (!is_string($binary_string)) { 157 | throw new TypeError( 158 | 'RandomCompat_substr(): First argument should be a string' 159 | ); 160 | } 161 | 162 | if (!is_int($start)) { 163 | throw new TypeError( 164 | 'RandomCompat_substr(): Second argument should be an integer' 165 | ); 166 | } 167 | 168 | if ($length !== null) { 169 | if (!is_int($length)) { 170 | throw new TypeError( 171 | 'RandomCompat_substr(): Third argument should be an integer, or omitted' 172 | ); 173 | } 174 | 175 | return (string) substr($binary_string, $start, $length); 176 | } 177 | 178 | return (string) substr($binary_string, $start); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /lib/random_bytes/cast_to_int.php: -------------------------------------------------------------------------------- 1 | operators might accidentally let a float 38 | * through. 39 | * 40 | * @param int|float $number The number we want to convert to an int 41 | * @param bool $fail_open Set to true to not throw an exception 42 | * 43 | * @return float|int 44 | * @psalm-suppress InvalidReturnType 45 | * 46 | * @throws TypeError 47 | */ 48 | function RandomCompat_intval($number, $fail_open = false) 49 | { 50 | if (is_int($number) || is_float($number)) { 51 | $number += 0; 52 | } elseif (is_numeric($number)) { 53 | $number += 0; 54 | } 55 | 56 | if ( 57 | is_float($number) 58 | && 59 | $number > ~PHP_INT_MAX 60 | && 61 | $number < PHP_INT_MAX 62 | ) { 63 | $number = (int) $number; 64 | } 65 | 66 | if (is_int($number)) { 67 | return (int) $number; 68 | } elseif (!$fail_open) { 69 | throw new TypeError( 70 | 'Expected an integer.' 71 | ); 72 | } 73 | return $number; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/random_bytes/error_polyfill.php: -------------------------------------------------------------------------------- 1 | = 70000) { 48 | return; 49 | } 50 | 51 | if (!defined('RANDOM_COMPAT_READ_BUFFER')) { 52 | define('RANDOM_COMPAT_READ_BUFFER', 8); 53 | } 54 | 55 | $RandomCompatDIR = dirname(__FILE__); 56 | 57 | require_once $RandomCompatDIR . '/byte_safe_strings.php'; 58 | require_once $RandomCompatDIR . '/cast_to_int.php'; 59 | require_once $RandomCompatDIR . '/error_polyfill.php'; 60 | 61 | if (!is_callable('random_bytes')) { 62 | /** 63 | * PHP 5.2.0 - 5.6.x way to implement random_bytes() 64 | * 65 | * We use conditional statements here to define the function in accordance 66 | * to the operating environment. It's a micro-optimization. 67 | * 68 | * In order of preference: 69 | * 1. Use libsodium if available. 70 | * 2. fread() /dev/urandom if available (never on Windows) 71 | * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) 72 | * 4. COM('CAPICOM.Utilities.1')->GetRandom() 73 | * 74 | * See RATIONALE.md for our reasoning behind this particular order 75 | */ 76 | if (extension_loaded('libsodium')) { 77 | // See random_bytes_libsodium.php 78 | if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { 79 | require_once $RandomCompatDIR . '/random_bytes_libsodium.php'; 80 | } elseif (method_exists('Sodium', 'randombytes_buf')) { 81 | require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php'; 82 | } 83 | } 84 | 85 | /** 86 | * Reading directly from /dev/urandom: 87 | */ 88 | if (DIRECTORY_SEPARATOR === '/') { 89 | // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast 90 | // way to exclude Windows. 91 | $RandomCompatUrandom = true; 92 | $RandomCompat_basedir = ini_get('open_basedir'); 93 | 94 | if (!empty($RandomCompat_basedir)) { 95 | $RandomCompat_open_basedir = explode( 96 | PATH_SEPARATOR, 97 | strtolower($RandomCompat_basedir) 98 | ); 99 | $RandomCompatUrandom = (array() !== array_intersect( 100 | array('/dev', '/dev/', '/dev/urandom'), 101 | $RandomCompat_open_basedir 102 | )); 103 | $RandomCompat_open_basedir = null; 104 | } 105 | 106 | if ( 107 | !is_callable('random_bytes') 108 | && 109 | $RandomCompatUrandom 110 | && 111 | @is_readable('/dev/urandom') 112 | ) { 113 | // Error suppression on is_readable() in case of an open_basedir 114 | // or safe_mode failure. All we care about is whether or not we 115 | // can read it at this point. If the PHP environment is going to 116 | // panic over trying to see if the file can be read in the first 117 | // place, that is not helpful to us here. 118 | 119 | // See random_bytes_dev_urandom.php 120 | require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php'; 121 | } 122 | // Unset variables after use 123 | $RandomCompat_basedir = null; 124 | } else { 125 | $RandomCompatUrandom = false; 126 | } 127 | 128 | /** 129 | * mcrypt_create_iv() 130 | * 131 | * We only want to use mcypt_create_iv() if: 132 | * 133 | * - random_bytes() hasn't already been defined 134 | * - the mcrypt extensions is loaded 135 | * - One of these two conditions is true: 136 | * - We're on Windows (DIRECTORY_SEPARATOR !== '/') 137 | * - We're not on Windows and /dev/urandom is readabale 138 | * (i.e. we're not in a chroot jail) 139 | * - Special case: 140 | * - If we're not on Windows, but the PHP version is between 141 | * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will 142 | * hang indefinitely. This is bad. 143 | * - If we're on Windows, we want to use PHP >= 5.3.7 or else 144 | * we get insufficient entropy errors. 145 | */ 146 | if ( 147 | !is_callable('random_bytes') 148 | && 149 | // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. 150 | (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) 151 | && 152 | // Prevent this code from hanging indefinitely on non-Windows; 153 | // see https://bugs.php.net/bug.php?id=69833 154 | ( 155 | DIRECTORY_SEPARATOR !== '/' || 156 | (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) 157 | ) 158 | && 159 | extension_loaded('mcrypt') 160 | ) { 161 | // See random_bytes_mcrypt.php 162 | require_once $RandomCompatDIR . '/random_bytes_mcrypt.php'; 163 | } 164 | $RandomCompatUrandom = null; 165 | 166 | /** 167 | * This is a Windows-specific fallback, for when the mcrypt extension 168 | * isn't loaded. 169 | */ 170 | if ( 171 | !is_callable('random_bytes') 172 | && 173 | extension_loaded('com_dotnet') 174 | && 175 | class_exists('COM') 176 | ) { 177 | $RandomCompat_disabled_classes = preg_split( 178 | '#\s*,\s*#', 179 | strtolower(ini_get('disable_classes')) 180 | ); 181 | 182 | if (!in_array('com', $RandomCompat_disabled_classes)) { 183 | try { 184 | $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); 185 | if (method_exists($RandomCompatCOMtest, 'GetRandom')) { 186 | // See random_bytes_com_dotnet.php 187 | require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php'; 188 | } 189 | } catch (com_exception $e) { 190 | // Don't try to use it. 191 | } 192 | } 193 | $RandomCompat_disabled_classes = null; 194 | $RandomCompatCOMtest = null; 195 | } 196 | 197 | /** 198 | * throw new Exception 199 | */ 200 | if (!is_callable('random_bytes')) { 201 | /** 202 | * We don't have any more options, so let's throw an exception right now 203 | * and hope the developer won't let it fail silently. 204 | * 205 | * @param mixed $length 206 | * @return void 207 | * @throws Exception 208 | */ 209 | function random_bytes($length) 210 | { 211 | unset($length); // Suppress "variable not used" warnings. 212 | throw new Exception( 213 | 'There is no suitable CSPRNG installed on your system' 214 | ); 215 | } 216 | } 217 | } 218 | 219 | if (!is_callable('random_int')) { 220 | require_once $RandomCompatDIR . '/random_int.php'; 221 | } 222 | 223 | $RandomCompatDIR = null; 224 | -------------------------------------------------------------------------------- /lib/random_bytes/random_bytes_com_dotnet.php: -------------------------------------------------------------------------------- 1 | GetRandom($bytes, 0)); 72 | if (RandomCompat_strlen($buf) >= $bytes) { 73 | /** 74 | * Return our random entropy buffer here: 75 | */ 76 | return RandomCompat_substr($buf, 0, $bytes); 77 | } 78 | ++$execCount; 79 | } while ($execCount < $bytes); 80 | 81 | /** 82 | * If we reach here, PHP has failed us. 83 | */ 84 | throw new Exception( 85 | 'Could not gather sufficient random data' 86 | ); 87 | } 88 | } -------------------------------------------------------------------------------- /lib/random_bytes/random_bytes_dev_urandom.php: -------------------------------------------------------------------------------- 1 | 0); 146 | 147 | /** 148 | * Is our result valid? 149 | */ 150 | if (is_string($buf)) { 151 | if (RandomCompat_strlen($buf) === $bytes) { 152 | /** 153 | * Return our random entropy buffer here: 154 | */ 155 | return $buf; 156 | } 157 | } 158 | } 159 | 160 | /** 161 | * If we reach here, PHP has failed us. 162 | */ 163 | throw new Exception( 164 | 'Error reading from source device' 165 | ); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /lib/random_bytes/random_bytes_libsodium.php: -------------------------------------------------------------------------------- 1 | 2147483647) { 64 | $buf = ''; 65 | for ($i = 0; $i < $bytes; $i += 1073741824) { 66 | $n = ($bytes - $i) > 1073741824 67 | ? 1073741824 68 | : $bytes - $i; 69 | $buf .= \Sodium\randombytes_buf($n); 70 | } 71 | } else { 72 | $buf = \Sodium\randombytes_buf($bytes); 73 | } 74 | 75 | if ($buf !== false) { 76 | if (RandomCompat_strlen($buf) === $bytes) { 77 | return $buf; 78 | } 79 | } 80 | 81 | /** 82 | * If we reach here, PHP has failed us. 83 | */ 84 | throw new Exception( 85 | 'Could not gather sufficient random data' 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/random_bytes/random_bytes_libsodium_legacy.php: -------------------------------------------------------------------------------- 1 | 2147483647) { 69 | for ($i = 0; $i < $bytes; $i += 1073741824) { 70 | $n = ($bytes - $i) > 1073741824 71 | ? 1073741824 72 | : $bytes - $i; 73 | $buf .= Sodium::randombytes_buf((int) $n); 74 | } 75 | } else { 76 | $buf .= Sodium::randombytes_buf((int) $bytes); 77 | } 78 | 79 | if (is_string($buf)) { 80 | if (RandomCompat_strlen($buf) === $bytes) { 81 | return $buf; 82 | } 83 | } 84 | 85 | /** 86 | * If we reach here, PHP has failed us. 87 | */ 88 | throw new Exception( 89 | 'Could not gather sufficient random data' 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/random_bytes/random_bytes_mcrypt.php: -------------------------------------------------------------------------------- 1 | operators might accidentally let a float 50 | * through. 51 | */ 52 | 53 | try { 54 | $min = RandomCompat_intval($min); 55 | } catch (TypeError $ex) { 56 | throw new TypeError( 57 | 'random_int(): $min must be an integer' 58 | ); 59 | } 60 | 61 | try { 62 | $max = RandomCompat_intval($max); 63 | } catch (TypeError $ex) { 64 | throw new TypeError( 65 | 'random_int(): $max must be an integer' 66 | ); 67 | } 68 | 69 | /** 70 | * Now that we've verified our weak typing system has given us an integer, 71 | * let's validate the logic then we can move forward with generating random 72 | * integers along a given range. 73 | */ 74 | if ($min > $max) { 75 | throw new Error( 76 | 'Minimum value must be less than or equal to the maximum value' 77 | ); 78 | } 79 | 80 | if ($max === $min) { 81 | return (int) $min; 82 | } 83 | 84 | /** 85 | * Initialize variables to 0 86 | * 87 | * We want to store: 88 | * $bytes => the number of random bytes we need 89 | * $mask => an integer bitmask (for use with the &) operator 90 | * so we can minimize the number of discards 91 | */ 92 | $attempts = $bits = $bytes = $mask = $valueShift = 0; 93 | 94 | /** 95 | * At this point, $range is a positive number greater than 0. It might 96 | * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to 97 | * a float and we will lose some precision. 98 | */ 99 | $range = $max - $min; 100 | 101 | /** 102 | * Test for integer overflow: 103 | */ 104 | if (!is_int($range)) { 105 | 106 | /** 107 | * Still safely calculate wider ranges. 108 | * Provided by @CodesInChaos, @oittaa 109 | * 110 | * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 111 | * 112 | * We use ~0 as a mask in this case because it generates all 1s 113 | * 114 | * @ref https://eval.in/400356 (32-bit) 115 | * @ref http://3v4l.org/XX9r5 (64-bit) 116 | */ 117 | $bytes = PHP_INT_SIZE; 118 | $mask = ~0; 119 | 120 | } else { 121 | 122 | /** 123 | * $bits is effectively ceil(log($range, 2)) without dealing with 124 | * type juggling 125 | */ 126 | while ($range > 0) { 127 | if ($bits % 8 === 0) { 128 | ++$bytes; 129 | } 130 | ++$bits; 131 | $range >>= 1; 132 | $mask = $mask << 1 | 1; 133 | } 134 | $valueShift = $min; 135 | } 136 | 137 | $val = 0; 138 | /** 139 | * Now that we have our parameters set up, let's begin generating 140 | * random integers until one falls between $min and $max 141 | */ 142 | do { 143 | /** 144 | * The rejection probability is at most 0.5, so this corresponds 145 | * to a failure probability of 2^-128 for a working RNG 146 | */ 147 | if ($attempts > 128) { 148 | throw new Exception( 149 | 'random_int: RNG is broken - too many rejections' 150 | ); 151 | } 152 | 153 | /** 154 | * Let's grab the necessary number of random bytes 155 | */ 156 | $randomByteString = random_bytes($bytes); 157 | 158 | /** 159 | * Let's turn $randomByteString into an integer 160 | * 161 | * This uses bitwise operators (<< and |) to build an integer 162 | * out of the values extracted from ord() 163 | * 164 | * Example: [9F] | [6D] | [32] | [0C] => 165 | * 159 + 27904 + 3276800 + 201326592 => 166 | * 204631455 167 | */ 168 | $val &= 0; 169 | for ($i = 0; $i < $bytes; ++$i) { 170 | $val |= ord($randomByteString[$i]) << ($i * 8); 171 | } 172 | 173 | /** 174 | * Apply mask 175 | */ 176 | $val &= $mask; 177 | $val += $valueShift; 178 | 179 | ++$attempts; 180 | /** 181 | * If $val overflows to a floating point number, 182 | * ... or is larger than $max, 183 | * ... or smaller than $min, 184 | * then try again. 185 | */ 186 | } while (!is_int($val) || $val > $max || $val < $min); 187 | 188 | return (int) $val; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /lib/telegrambot.class.php: -------------------------------------------------------------------------------- 1 | token=$token; 9 | $this->base_bot_url="https://api.telegram.org/bot".$token."/"; 10 | } 11 | 12 | function useCertificate($path){ 13 | $this->ca_cert=$path; 14 | } 15 | 16 | private function httpRequest($url){ 17 | if($this->ca_cert){ 18 | $ssl_options = array("ssl"=>array( 19 | "cafile" => ($this->ca_cert), 20 | "verify_peer"=> true, 21 | "verify_peer_name"=> true, 22 | )); 23 | return file_get_contents($url, false, stream_context_create($ssl_options)); 24 | }else{ 25 | return $this->url_get_contents($url); 26 | } 27 | } 28 | 29 | function url_get_contents($url){ 30 | if(!function_exists('curl_init')){ 31 | return file_get_contents($url); 32 | } 33 | $ch = curl_init(); 34 | curl_setopt($ch, CURLOPT_URL, $url); 35 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 36 | $output = curl_exec($ch); 37 | curl_close($ch); 38 | return $output; 39 | } 40 | 41 | private function callTelegramAPI($command, $data){ 42 | return $this->httpRequest(($this->base_bot_url).$command."?".str_replace("&", "&", http_build_query($data))); 43 | } 44 | 45 | function sendMessage($text, $chat_id, $disable_notifications=false){ 46 | $this->callTelegramAPI("sendMessage",[ 47 | 'text'=>$text, 48 | 'chat_id'=>$chat_id, 49 | 'disable_notification'=>$disable_notifications 50 | ]); 51 | } 52 | 53 | function sendPhoto($image_url, $chat_id, $caption='', $callback=null){ 54 | $url = ($this->base_bot_url)."sendPhoto?chat_id=".$chat_id; 55 | $post_fields = array( 56 | 'chat_id' => $chat_id, 57 | 'photo' => new CURLFile(realpath($image_url)), 58 | 'caption' => $caption 59 | ); 60 | $ch = curl_init(); 61 | curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type:multipart/form-data")); 62 | curl_setopt($ch, CURLOPT_URL, $url); 63 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 64 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields); 65 | $output = curl_exec($ch); 66 | curl_close($ch); 67 | if($callback!=null) $callback(); 68 | } 69 | 70 | function getUpdates($offset=0,$limit=100,$timeout=0){ 71 | return $this->callTelegramAPI("getUpdates",['offset'=>$offset,'limit'=>$limit,'timeout'=>$timeout]); 72 | } 73 | 74 | function parseUpdates($updates,$chat_id=null){ 75 | $result = json_decode($updates); 76 | if($result->ok){ 77 | if(array_key_exists('result', $result)){ 78 | $updates=$result->result; 79 | $parsed_update=array(); 80 | foreach($updates as $key=>$update){ 81 | $message=$update->message; 82 | 83 | // Reconstruct user message in a more human readable way with only necessary info. 84 | $parsed_message=array(); 85 | $parsed_message['update_id']=$update->update_id; 86 | $parsed_message['message_id']=$message->message_id; 87 | $parsed_message['from_id']=$message->from->id; 88 | $parsed_message['from_username']=isset($message->from->username) ? $message->from->username : ''; 89 | $parsed_message['date']=$message->date; 90 | $parsed_message['text']=$message->text; 91 | 92 | // If we are parsing all messages or we are parsing only messages from a certain chat_id... 93 | if($chat_id==null || $chat_id==$parsed_message['from_id']){ 94 | // Save the parsed message to a list. 95 | $parsed_update[]=$parsed_message; 96 | } 97 | } 98 | return $parsed_update; 99 | }else return false; 100 | }else return false; 101 | } 102 | 103 | function getParsedUpdates($chat_id=null,$offset=0,$limit=100,$timeout=0){ 104 | return $this->parseUpdates($this->getUpdates($offset,$limit,$timeout),$chat_id); 105 | } 106 | } 107 | ?> -------------------------------------------------------------------------------- /lib/uploader.class.php: -------------------------------------------------------------------------------- 1 | upload_dir=$upload_dir; 17 | } 18 | 19 | function getDirectory(){ 20 | return $this->upload_dir; 21 | } 22 | 23 | function uploadImage($file){ // $_FILES['image'] 24 | $verifyimg = getimagesize($file['tmp_name']); 25 | $mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file['tmp_name']); 26 | $supported_types = array('image/png', 'image/jpg', 'image/jpeg', 'image/gif'); 27 | if(!in_array($verifyimg['mime'], $supported_types) || !in_array($mime, $supported_types)) return false; 28 | $upload_file = ($this->uuidv4()).".".(explode("/",$mime)[1]); 29 | if(move_uploaded_file($file['tmp_name'], (($this->getDirectory()).$upload_file))) return $upload_file; 30 | else return false; 31 | } 32 | 33 | function destroyImage($file){ 34 | unlink(($this->getDirectory()).$file); 35 | } 36 | } 37 | ?> -------------------------------------------------------------------------------- /send_message.php: -------------------------------------------------------------------------------- 1 | sendMessage($message, $telegram_id); 15 | $success=true; // The operation was successful. 16 | } 17 | } 18 | 19 | if(!$success) {echo json(array('status'=>1, 'message'=>'Error on backend.')); http_response_code(400);} 20 | else {echo json(array('status'=>0, 'message'=>'Message sent.')); http_response_code(200);} 21 | ?> 22 | -------------------------------------------------------------------------------- /send_photo.php: -------------------------------------------------------------------------------- 1 | uploadImage($_FILES['image'])))!=false){ 15 | // If the image was uploaded successfully to our server, let's send it to the target. 16 | $telegram_bot = new TelegramBot($config['telegram_bot_API_key']); 17 | $telegram_bot->sendPhoto(($uploader->getDirectory()).$file, $telegram_id, function() use ($uploader, $file){ 18 | // When communication is finished, remove the image from the server. 19 | $uploader->destroyImage($file); 20 | }); 21 | $success=true; // The operation was successful. 22 | } 23 | } 24 | } 25 | 26 | if(!$success) {echo json(array('status'=>1, 'message'=>'Error on backend.')); http_response_code(400);} 27 | else {echo json(array('status'=>0, 'message'=>'Photo sent.')); http_response_code(200);} 28 | ?> 29 | --------------------------------------------------------------------------------