diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..edae734 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +test.php +file.json \ No newline at end of file diff --git a/LICENSE b/LICENSE index 9cecc1d..031e226 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2016 Radya Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. diff --git a/README.md b/README.md index 87e1a8c..6e6556f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,266 @@ -# phptelebot -Telegram bot framework written in PHP +# PHPTelebot - Telegram bot framework written in PHP. + +## Features + +* Simple, easy to use. +* Support Long Polling and Webhook. + +## Requirements + +- [cURL](http://php.net/manual/en/book.curl.php) +- PHP 5.4+ +- Telegram Bot API Token - Talk to [@BotFather](https://telegram.me/@BotFather) and generate one. + +## Installation + +### Using [Composer](https://getcomposer.org) + +To install PHPTelebot with Composer, just add the following to your `composer.json` file: + +```json +{ + "require-dev": { + "radyakaze/phptelebot": "1.*" + } +} +``` + +or by running the following command: + +```shell +composer require radyakaze/phptelebot +``` + +Composer installs autoloader at `./vendor/autoloader.php`. to include the library in your script, add: + +```php +require_once 'vendor/autoload.php'; +``` + +### Install from source + +Download the PHP library from Github, then include `PHPTelebot.php` in your script: + +```php +require_once '/path/to/phptelebot/src/PHPTelebot.php'; +``` + +## Usage + +### Creating a simple bot +```php +cmd('*', 'Hi, human! I am a bot.'); + +// Simple echo command +$bot->cmd('/echo|/say', function($text) { + if ($text == '') { + $text = 'Command usage: /echo [text] or /say [text]'; + } + return Bot::sendMessage($text); +}); + +// Simple whoami command +$bot->cmd('/whoami|!whoami', function() { + // Get message properties + $message = Bot::message(); + $name = $message['from']['first_name']; + $userId = $message['from']['id']; + $text = 'You are '.$name.' and your ID is '.$userId.''; + $options = [ + 'parse_mode' => 'html' + ]; + + return Bot::sendMessage($text, $options); +}); + +$bot->run(); +``` +*NOTE:* +- If function parameters is more than one, PHPTelebot will split text by space. +- If you not set chat_id on options bot will send message to current chat. +- If you add option **reply => true**, bot will reply current message (Only work if you not set custom chat_id and reply_to_mesage_id). + +## Commands +--- +Use `$bot->cmd(, )` to handle command. +```php +// simple answer +$bot->cmd('*', 'I am a bot'); + +// catch multiple commands +$bot->cmd('/start|/help', function() { + // Do something here. +}); + +// call a function name +function googleSearch($search) { + // Do something here. +} +$bot->cmd('/google', 'googleSearch'); +``` +Use ***** to catch any command. + +#### File upload +This code will send a photo to users when type command **/upload**. +```php +// Simple photo upload +$bot->cmd('/upload', function() { + $file = '/path/to/photo.png'; // File path, file id, or url. + return Bot::sendPhoto($file); +}); +``` + +## Events +--- +Use `$bot->on(, )` to handle all possible PHPTelebot events. + +To handle inline message, just add: +```php +$bot->on('inline', function($text) { + $results[] = [ + 'type' => 'article', + 'id' => 'unique_id1', + 'title' => $text, + 'message_text' => 'Lorem ipsum dolor sit amet', + ]; + $options = [ + 'cache_time' => 3600, + ]; + + return Bot::answerInlineQuery($results, $options); +}); +``` +Also, you can catch multiple events: +```php +$bot->on('sticker|photo|document', function() { + // Do something here. + }); +``` + +## Supported events: +- ***** - any type of message +- **text** – text message +- **audio** – audio file +- **voice** – voice message +- **document** – document file (any kind) +- **photo** – photo +- **sticker** – sticker +- **video** – video file +- **contact** – contact data +- **location** – location data +- **venue** – venue data +- **edited** – edited message +- **pinned_message** – message was pinned +- **new_chat_member** – new member was added +- **left_chat_member** – member was removed +- **new_chat_title** – new chat title +- **new_chat_photo** – new chat photo +- **delete_chat_photo** – chat photo was deleted +- **group_chat_created** – group has been created +- **channel_chat_created** – channel has been created +- **supergroup_chat_created** – supergroup has been created +- **migrate_to_chat_id** – group has been migrated to a supergroup +- **migrate_from_chat_id** – supergroup has been migrated from a group +- **inline** - inline message +- **callback** - callback message +- **game** - game + +## Command with custom regex *(advanced)* +--- +Create a command: */regex string number* +```php +$bot->regex('/^\/regex (.*) ([0-9])$/i', function($matches) { + // Do something here. +}); +``` + +## Methods +---- +### PHPTelebot Methods +##### `cmd(, )` +Handle a command. +##### `on(, )` +Handles events. +##### `regex(, )` +Create custom regex for command. +##### `Bot::type()` +Return [message event](#telegram-message-events) type. +##### `Bot::message()` +Get [message properties](https://core.telegram.org/bots/api#message). + +### Telegram Methods +PHPTelebot use standard [Telegram Bot API](https://core.telegram.org/bots/api#available-methods) method names. +##### `Bot::getMe()` [?](https://core.telegram.org/bots/api#getme) +A simple method for testing your bot's auth token. +##### `Bot::sendMessage(, )` [?](https://core.telegram.org/bots/api#sendmessage) +Use this method to send text messages. +##### `Bot::forwardMessage()` [?](https://core.telegram.org/bots/api#forwardmessage) +Use this method to forward messages of any kind. +##### `Bot::sendPhoto(, )` [?](https://core.telegram.org/bots/api#sendphoto) +Use this method to send a photo. +##### `Bot::sendVideo(, )` [?](https://core.telegram.org/bots/api#sendvideo) +Use this method to send a video. +##### `Bot::sendAudio(, )` [?](https://core.telegram.org/bots/api#sendaudio) +Use this method to send a audio. +##### `Bot::sendVoice(, )` [?](https://core.telegram.org/bots/api#sendvoice) +Use this method to send a voice message. +##### `Bot::sendDocument(, )` [?](https://core.telegram.org/bots/api#senddocument) +Use this method to send a document. +##### `Bot::sendSticker(, )` [?](https://core.telegram.org/bots/api#sendsticker) +Use this method to send a sticker. +##### `Bot::sendLocation()` [?](https://core.telegram.org/bots/api#sendlocation) +Use this method to send point on the map. +##### `Bot::sendVenue()` [?](https://core.telegram.org/bots/api#sendvenue) +Use this method to send information about a venue. +##### `Bot::sendContact()` [?](https://core.telegram.org/bots/api#sendcontact) +Use this method to send phone contacts. +##### `Bot::sendAction(, )` [?](https://core.telegram.org/bots/api#sendchataction) +Use this method when you need to tell the user that something is happening on the bot's side. +##### `Bot::getUserProfilePhotos(, )` [?](https://core.telegram.org/bots/api#getuserprofilephotos) +Use this method to get a list of profile pictures for a user. +##### `Bot::getFile()` [?](https://core.telegram.org/bots/api#getfile) +Use this method to get basic info about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. +##### `Bot::answerInlineQuery(, )` [?](https://core.telegram.org/bots/api#answerinlinequery) +Use this method to send answers to an inline query. +##### `Bot::answerCallbackQuery(, )` [?](https://core.telegram.org/bots/api#answercallbackquery) +Use this method to send answers to callback queries sent from inline keyboards. +##### `getChat()` [?](https://core.telegram.org/bots/api#getchat) +Use this method to get up to date information about the chat. +##### `leaveChat()` [?](https://core.telegram.org/bots/api#leavechat) +Use this method for your bot to leave a group, supergroup or channel. +##### `getChatAdministrators()` [?](https://core.telegram.org/bots/api#getchatadministrators) +Use this method to get a list of administrators in a chat. +##### `getChatMembersCount()` [?](https://core.telegram.org/bots/api#getchatmemberscount) +Use this method to get the number of members in a chat. +##### `getChatMember()` [?](https://core.telegram.org/bots/api#getchatmember) +Use this method to get information about a member of a chat. +##### `kickChatMember()` [?](https://core.telegram.org/bots/api#kickchatmember) +Use this method to kick a user from a group or a supergroup. +##### `unbanChatMember()` [?](https://core.telegram.org/bots/api#unbanchatmember) +Use this method to unban a previously kicked user in a supergroup. +##### `editMessageText()` [?](https://core.telegram.org/bots/api#editmessagetext) +Use this method to edit text messages sent by the bot or via the bot (for inline bots). +##### `editMessageCaption()` [?](https://core.telegram.org/bots/api#editmessagecaption) +Use this method to edit captions of messages sent by the bot or via the bot (for inline bots). +##### `editMessageReplyMarkup()` [?](https://core.telegram.org/bots/api#editmessagereplymarkup) +Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). +##### `sendGame(, )` [?](https://core.telegram.org/bots/api#sendgame) +Use this method to send a game. +##### `setGameScore()` [?](https://core.telegram.org/bots/api#setgamescore) +Use this method to set the score of the specified user in a game. +##### `getGameHighScores(, )` [?](https://core.telegram.org/bots/api#getgamehighscores) +Use this method to get data for high score tables. + +---- +## Webhook installation +Open via browser `https://api.telegram.org/bot/setWebhook?url=https://yourdomain.com/your_bot.php` + +---- + +Made with ♥ by Radya \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..80ef812 --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "radya/phptelebot", + "type": "framework", + "description": "Telegram bot framework written in PHP.", + "keywords": ["bot", "telegram"], + "license": "GPL-3.0", + "authors": [ + {"name": "Radya", "email": "radya@gmx.com"} + ], + "require": { + "php": ">=5.4", + "ext-curl": "*" + }, + "autoload": { + "files": ["src/PHPTelebot.php"] + } +} \ No newline at end of file diff --git a/sample.php b/sample.php new file mode 100644 index 0000000..635096e --- /dev/null +++ b/sample.php @@ -0,0 +1,69 @@ +cmd('*', 'Hi, human! I am a bot.'); + +// Simple echo command +$bot->cmd('/echo|/say', function($text) { + if ($text == '') { + $text = 'Command usage: /echo [text] or /say [text]'; + } + + return Bot::sendMessage($text); +}); + +// Simple whoami command +$bot->cmd('/whoami', function() { + // Get message properties + $message = Bot::message(); + $name = $message['from']['first_name']; + $userId = $message['from']['id']; + $text = 'You are '.$name.' and your ID is '.$userId.''; + $options = [ + 'parse_mode' => 'html', + 'reply' => true + ]; + + return Bot::sendMessage($text, $options); +}); + +// slice text by space +$bot->cmd('/split', function($one, $two, $three) { + $text = "First word: $one\n"; + $text .= "Second word: $two\n"; + $text .= "Third word: $three"; + + return Bot::sendMessage($text); +}); + +// simple file upload +$bot->cmd('/upload', function() { + $file = 'http://www.petsftw.com/wp-content/uploads/2016/03/cutecat.jpg'; + return Bot::sendPhoto($file); +}); + +// custom regex +$bot->regex('/\/number ([0-9]+)/i', function($matches) { + return Bot::sendMessage($matches[1]); +}); + +// Inline +$bot->on('inline', function($text) { + $results[] = [ + 'type' => 'article', + 'id' => 'unique_id1', + 'title' => $text, + 'message_text' => 'Lorem ipsum dolor sit amet', + ]; + $options = [ + 'cache_time' => 3600, + ]; + + return Bot::answerInlineQuery($results, $options); +}); + +$bot->run(); diff --git a/src/Bot.php b/src/Bot.php new file mode 100644 index 0000000..a06d27f --- /dev/null +++ b/src/Bot.php @@ -0,0 +1,263 @@ + 'https://api.telegram.org/bot'.PHPTelebot::$token.'/'.$action, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true + ]; + + if (is_array($data)) { + $options[CURLOPT_POSTFIELDS] = $data; + } + + if ($upload !== false) { + $options[CURLOPT_HTTPHEADER] = ['Content-Type: multipart/form-data']; + } + + curl_setopt_array($ch, $options); + + $result = curl_exec($ch); + + if (curl_errno($ch)) { + echo curl_error($ch)."\n"; + } + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpcode == 401) { + throw new Exception('Incorect bot token'); + return false; + } else { + return $result; + } + } + + /** + * Answer Inline. + * + * @param array $options [optional] + * + * @return string + */ + public static function answerInlineQuery($results, $options = []) + { + if (!empty($options)) { + $data = $options; + } + + if (!isset($options['inline_query_id'])) { + $get = PHPTelebot::$getUpdates; + $data['inline_query_id'] = $get['inline_query']['id']; + } + + $data['results'] = json_encode($results); + + return self::send('answerInlineQuery', $data); + } + + /** + * Answer Callback. + * + * @param array $data + * + * @return string + */ + public static function answerCallbackQuery($text = '', $options = []) + { + $options['text'] = $text; + + if (!isset($options['callback_query_id'])) { + $get = PHPTelebot::$getUpdates; + $options['callback_query_id'] = $get['callback_query']['id']; + } + + return self::send('answerCallbackQuery', $options); + } + + /** + * Create curl file. + * + * @param string $path + * + * @return string + */ + private static function curlFile($path) + { + // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax + // See: https://wiki.php.net/rfc/curl-file-upload + if (function_exists('curl_file_create')) { + return curl_file_create($path); + } else { + // Use the old style if using an older version of PHP + return "@$path"; + } + } + + /** + * @return array + */ + public static function message() + { + $get = PHPTelebot::$getUpdates; + if (isset($get['message'])) { + return $get['message']; + } elseif (isset($get['callback_query'])) { + return $get['callback_query']; + } elseif (isset($get['inline_query'])) { + return $get['inline_query']; + } elseif (isset($get['edited_message'])) { + return $get['edited_message']; + } + } + + /** + * Mesage type. + * + * @return string + */ + public static function type() + { + $getUpdates = PHPTelebot::$getUpdates; + + if (isset($getUpdates['message']['text'])) { + return 'text'; + } elseif (isset($getUpdates['message']['photo'])) { + return 'photo'; + } elseif (isset($getUpdates['message']['video'])) { + return 'video'; + } elseif (isset($getUpdates['message']['audio'])) { + return 'audio'; + } elseif (isset($getUpdates['message']['voice'])) { + return 'voice'; + } elseif (isset($getUpdates['message']['document'])) { + return 'document'; + } elseif (isset($getUpdates['message']['sticker'])) { + return 'sticker'; + } elseif (isset($getUpdates['message']['venue'])) { + return 'venue'; + } elseif (isset($getUpdates['message']['location'])) { + return 'location'; + } elseif (isset($getUpdates['inline_query'])) { + return 'inline'; + } elseif (isset($getUpdates['callback_query'])) { + return 'callback'; + } elseif (isset($getUpdates['message']['new_chat_member'])) { + return 'new_chat_member'; + } elseif (isset($getUpdates['message']['left_chat_member'])) { + return 'left_chat_member'; + } elseif (isset($getUpdates['message']['new_chat_title'])) { + return 'new_chat_title'; + } elseif (isset($getUpdates['message']['new_chat_photo'])) { + return 'new_chat_photo'; + } elseif (isset($getUpdates['message']['delete_chat_photo'])) { + return 'delete_chat_photo'; + } elseif (isset($getUpdates['message']['group_chat_created'])) { + return 'group_chat_created'; + } elseif (isset($getUpdates['message']['channel_chat_created'])) { + return 'channel_chat_created'; + } elseif (isset($getUpdates['message']['supergroup_chat_created'])) { + return 'supergroup_chat_created'; + } elseif (isset($getUpdates['message']['migrate_to_chat_id'])) { + return 'migrate_to_chat_id'; + } elseif (isset($getUpdates['message']['migrate_from_chat_id '])) { + return 'migrate_from_chat_id '; + } elseif (isset($getUpdates['edited_message'])) { + return 'edited_message'; + } elseif (isset($getUpdates['message']['game'])) { + return 'game'; + } else { + return 'unknown'; + } + } + + /** + * Create an action. + * + * @param string $name + * @param array $args + * + * @return array + */ + public static function __callStatic($action, $args) + { + $param = []; + $firstParam = [ + 'sendMessage' => 'text', + 'sendPhoto' => 'photo', + 'sendVideo' => 'video', + 'sendAudio' => 'audio', + 'sendVoice' => 'voice', + 'sendDocument' => 'document', + 'sendSticker' => 'sticker', + 'sendVenue' => 'venue', + 'sendChatAction' => 'action', + 'setWebhook' => 'url', + 'getUserProfilePhotos' => 'user_id', + 'getFile' => 'file_id', + 'getChat' => 'chat_id', + 'leaveChat' => 'chat_id', + 'getChatAdministrators' => 'chat_id', + 'getChatMembersCount' => 'chat_id', + 'sendGame' => 'game_short_name', + 'getGameHighScores' => 'user_id' + ]; + + if (!isset($firstParam[$action])) { + if (isset($args[0]) && is_array($args[0])) { + $param = $args[0]; + } + } else { + $param[$firstParam[$action]] = $args[0]; + if (isset($args[1]) && is_array($args[1])) { + $param = array_merge($param, $args[1]); + } + } + + return call_user_func_array('self::send', [$action, $param]); + } +} diff --git a/src/PHPTelebot.php b/src/PHPTelebot.php new file mode 100644 index 0000000..8b4c6ac --- /dev/null +++ b/src/PHPTelebot.php @@ -0,0 +1,296 @@ +_command[$paterns] = $callback; + } + + if (strrpos($paterns, '*') !== false) { + $this->_onMessage['text'] = $callback; + } + + return true; + } + /** + * Type. + * + * @param string $paterns + * @param object|string $callback + * + * @return bool + */ + public function on($types, $callback) + { + $types = explode('|', $types); + foreach ($types as $type) { + $this->_onMessage[$type] = $callback; + } + + return true; + } + + /** + * Custom regex for command. + * + * @param string $regex + * @param object|string $callback + * + * @return bool + */ + public function regex($regex, $callback) + { + $this->_command['customRegex:'.$regex] = $callback; + + return true; + } + + /** + * Run telebot. + * + * @return bool + */ + public function run() + { + try { + if (php_sapi_name() == 'cli') { + echo 'PHPTelebot version '.self::$version; + echo "\nMode\t: Long Polling\n"; + $options = getopt('q', ['quiet']); + if (isset($options['q']) || isset($options['quiet'])) { + self::$debug = false; + } + echo "Debug\t: ".(self::$debug ? 'ON' : 'OFF')."\n"; + $this->longPoll(); + } else { + $this->webhook(); + } + + return true; + } catch (Exception $e) { + echo $e->getMessage()."\n"; + + return false; + } + } + + /** + * Webhook. + */ + private function webhook() + { + if ($_SERVER['REQUEST_METHOD'] == 'POST' && $_SERVER['CONTENT_TYPE'] == 'application/json') { + self::$getUpdates = json_decode(file_get_contents('php://input'), true); + echo $this->process(); + } else { + throw new Exception('Access not allowed!'); + } + } + + /** + * Long Poll Mode. + */ + private function longPoll() + { + $offset = 0; + while (true) { + $req = json_decode(Bot::send('getUpdates', ['offset' => $offset, 'timeout' => 30]), true); + + // Check error. + if (isset($req['error_code'])) { + if ($req['error_code'] == 404) { + $req['description'] = 'Incorrect bot token'; + } + throw new Exception($req['description']); + } + + if (!empty($req['result'])) { + foreach ($req['result'] as $update) { + self::$getUpdates = $update; + $response = $this->process(); + + if (self::$debug) { + $line = "\n--------------------\n"; + $outputFormat = "$line \033[0;33m%s\033[0m \033[0;32m$update[update_id]\033[0m $line%s"; + echo sprintf($outputFormat, 'Query ID :', json_encode($update)); + echo sprintf($outputFormat, 'Response for :', isset($response) ? $response : '--NO RESPONSE--'); + } + $offset = $update['update_id'] + 1; + } + } + + // Delay 1 second + sleep(1); + } + } + + /** + * Process the message. + * + * @return string + */ + private function process() + { + $get = self::$getUpdates; + $run = false; + $customRegex = false; + + if (Bot::type() == 'text') { + foreach ($this->_command as $cmd => $call) { + if (substr($cmd, 0, 12) == 'customRegex:') { + $regex = substr($cmd, 12); + // Remove bot username from command + if (self::$username != '') { + $get['message']['text'] = preg_replace('/^\/(.*)@'.self::$username.'(.*)/', '/$1$2', $get['message']['text']); + } + $customRegex = true; + } else { + if (self::$username != '') { + $username = '(?:@'.self::$username.')?'; + } else { + $username = ''; + } + $regex = '/^'.addcslashes($cmd, '/\+*?[^]$(){}=!<>:-').$username.'(?:\s(.*))?$/'; + } + if ($get['message']['text'] != '*' && preg_match($regex, $get['message']['text'], $matches)) { + $run = true; + if ($customRegex) { + $param = [$matches]; + } else { + $param = isset($matches[1]) ? $matches[1] : ''; + } + break; + } + } + } + + if (isset($this->_onMessage) && $run === false) { + if (in_array(Bot::type(), array_keys($this->_onMessage))) { + $run = true; + $call = $this->_onMessage[Bot::type()]; + } elseif (isset($this->_onMessage['*'])) { + $run = true; + $call = $this->_onMessage['*']; + } + + if ($run) { + switch (Bot::type()) { + case 'callback': + $param = $get['callback_query']['data']; + break; + case 'inline': + $param = $get['inline_query']['query']; + break; + case 'location': + $param = [$get['message']['location']['longitude'], $get['message']['location']['latitude']]; + break; + case 'text': + $param = $get['message']['text']; + break; + default: + $param = ''; + break; + } + } + } + + if ($run) { + if (is_callable($call)) { + if (!is_array($param)) { + $count = count((new ReflectionFunction($call))->getParameters()); + if ($count > 1) { + $param = array_pad(explode(' ', $param, $count), $count, ''); + } else { + $param = [$param]; + } + } + + return call_user_func_array($call, $param); + } else { + if (!isset($get['inline_query'])) { + return Bot::send('sendMessage', ['text' => $call]); + } + } + } + } +} + +require_once __DIR__.'/Bot.php';