Object-Oriented Bot Framework for Telegram adhering to the Telegram Bot API
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
src
.gitignore
CHANGELOG.md
LICENSE
README.md
composer.json

README.md

Telegram Bot Core

Object-Oriented Bot Framework for Telegram adhering to the Telegram Bot API. Example bot at KeythKatz/Stanley.

Requirements

  • PHP 7.2 and above

Installation

With Composer:

composer require keythkatz/telegram-bot-core

Quickstart

Create a bot class

class ExampleBot extends \KeythKatz\TelegramBotCore\TelegramBotCore {
	protected function registerHandlers()
	{
		// We will fill in this part later
	}
}

Commands

Each Command is its own class. Here we create a simple echo command.

class EchoCommand extends \KeythKatz\TelegramBotCore\Command
{
	// Name of the command, /echo
	protected $name = "echo";

	// What text to show for the command when doing /help.
	// Set to null to hide the command.
	protected $helpText = "Repeat after me";
	
	/**
	 * What to do when the command is called.
	 * @param  string $arguments The arguments following the command as a string.
	 * @param  Message $message Raw Message object that triggered this command.
	 */
	public function process($arguments, $message)
	{
		$reply = $this->sendMessageReply();
		$reply->setText($arguments);
		$response = $reply->send(); // Get Telegram's response
	}
}

Next, add the command in your bot's registerHandlers():

protected function registerHandlers()
{
	$this->addCommand(new EchoCommand());
}

Set up a Webhook

From wherever you told Telegram to send you updates, call your bot:

$bot = new ExampleBot("@BotUsername", "BotToken", "StorageDirectory"); // Storage Directory if you are using Conversations
$bot->webhook();

That's it! Now /echo and /help should work.

Docs

Methods and Types, the Telegram API

This library follows the official Telegram API docs. Classes under the Method namespace follow the Available methods section of the official API. Classes under the Type namespace follow Available types.

Parameters in Telegram are in snake_case. This library uses camelCase, however all the names are the same. To set a parameter, use setters.

This library uses the telegram-bot/api library for parsing received Updates. Types in both libraries work the same way, with getters and setters.

Example: Sending an inline keyboard from a command

$message = $this->sendMessageReply();
$message->setText("Hello World");

// Traditional way of setting up a keyboard. Helper functions are available.
// (See Keyboard section in the docs)
$keyboard = new InlineKeyboardMarkup([[["Click Me" => "https://google.com"]]]);

$message->setReplyMarkup($keyboard);
$message->send();

TelegramBotCore

abstract class TelegramBotCore {
	/**
	 * Token given by telegram for the bot.
	 * @var string
	 */
	protected static $token = null; // Override this

	/**
	 * Username of the bot, e.g. @exampleBot
	 * @var string
	 */
	protected static $username = null; // Override this

	/**
	 * Directory to store conversation files
	 * @var string
	 */
	protected static $storageLocation = __DIR__; // Optionally override this, but override if you are using Conversations

	/**
	 * Add all your commands and handlers within this function.
	 */
	abstract protected function registerHandlers();

	/**
	 * Call this function to turn on the bot when processing via webhooks.
	 */
	public static function webhook(): void
}

Handlers - Commands

abstract class Command extends ForwardableHandler {
	protected $name = null; // Override this
	protected $helpText = ""; // Optionally override this

	/**
	 * What to do when the command is called.
	 * @param  string $arguments Arguments entered by the user.
	 * @param \TelegramBot\Api\Types\Message $message Message object that triggered this command.
	 */
	abstract public function process($arguments, $message);
}

To create a new Command, extend \KeythKatz\TelegramBotCore\Command, and override $name with the name of the command, which will be triggered by /name. Optionally override $helpText to provide a help text which will be shown via the auto-generated /help. If it is left as null or as an empty string, the command will not be shown in /help.

The Command class has helper functions, also linked to the Telegram API methods:

// Create a new SendMessage linked to the bot.
$message = $this->sendMessage();

// Add Reply to any method name to create it with the chatId prefilled.
// Set $quoteOriginal to true to reply directly to the triggering message.
$message = $this->sendMessageReply($quoteOriginal = false);

// Forward the triggering message to another chat immediately.
// Set $disableNofication to true to send the message silently.
$this->forwardMessage($toChatId, $disableNotification = false);

// And other API methods...

From anywhere in the class, you can also interact directly with the bot or the triggering message using:

$this->bot;
$this->message;

Add it to your bot's registerHandlers function like so:

$this->addCommand(new EchoCommand());

To call a command without user input, call $this->runCommand(new EchoCommand(), $arguments) from any handler. $arguments is optional and is an empty string by default.

Handlers - CallbackQueryHandler

abstract class CallbackQueryHandler extends BaseHandler {
	/**
	 * What to do when the bot receives a CallbackQuery.
	 * @param  CallbackQuery $query received CallbackQuery.
	 * @param  \TelegramBot\Api\Types\Message $message Message that the callback button originated from.
	 *                          May be null if the message is too old.
	 */
	abstract public function process(CallbackQuery $query, \TelegramBot\Api\Types\Message $message);
}

The CallbackQueryHandler works similarly to Commands. Override the process() function in your own class.

From anywhere in the class, you can also interact directly with the bot or the triggering query using:

$this->bot;
$this->query;

Add it to your bot's registerHandlers function like so:

$this->setCallbackQueryHandler(new CqHandler());

Handlers - GenericMessageHandler

abstract class GenericMessageHandler extends ForwardableHandler {
	/**
	 * What to do when the command is called.
	 * @param  string $arguments Arguments entered by the user.
	 * @param \TelegramBot\Api\Types\Message $message Message object that triggered this command.
	 */
	abstract public function process(\TelegramBot\Api\Types\Message $message);
}

The GenericMessageHandler works similarly to Commands. Override the process() function in your own class.

From anywhere in the class, you can also interact directly with the bot or the triggering message using:

$this->bot;
$this->message;

Add it to your bot's registerHandlers function like so:

$this->setGenericMessageHandler(new DefaultMessageHandler());

Keyboards

InlineKeyboardMarkup and ReplyKeyboardMarkup have helper functions.

There are two ways to create a keyboard. The first is direct creation:

$keyboard = new InlineKeyboardMarkup([[["Click Me" => "https://google.com"]]]);
// or
$keyboard = new InlineKeyboardMarkup();
$keyboard->setInlineKeyboard([[["Click Me" => "https://google.com"]]]);

The second way is to use helper functions:

$keyboard = new InlineKeyboardMarkup(); // or ReplyKeyboardMarkup

// Create a new button
$button = new InlineKeyboardButton($text); // or KeyboardButton
$button->setCallbackData("12345"); // or any other field in the telegram docs

// Add to the keyboard
$keyboard->addButton($button);

// Add a new row. Added buttons after this will be on the new row.
$keyboard->addRow();

// Add a button to any row, 0-indexed
$keyboard->addButton($button, 0);

Conversations

abstract class Conversation extends ForwardableHandler {
	/**
	 * Chat ID that the conversation belongs to.
	 * @var int
	 */
	protected $chatId;

	/**
	 * ID of the user that initiated the conversation.
	 * @var int
	 */
	protected $userId;

	/**
	 * Initialise stages here by calling $this->addStage() for each stage.
	 */
	abstract public function initialize();

	/**
	 * Add a stage.
	 * @param string   $name     Name of the stage.
	 * @param string   $message  Message to send and ask a reply to.
	 * @param callable $callback Callback that is called with the replying message as a parameter.
	 */
	protected function addStage(string $name, string $message, callable $callback): void

	/**
	 * Save data that will be persistant across conversations.
	 * @param  string $name Name of the data.
	 * @param  anything $data Data to be stored.
	 */
	protected function saveData(string $name, $data): void

	/**
	 * Load saved data.
	 * @param  string $name Name of the data.
	 * @return anything       Saved data.
	 */
	protected function loadData(string $name)

	/**
	 * Repeat the current stage, for example on invalid input.
	 */
	protected function repeatStage(): void

	/**
	 * Set the current stage, i.e. move on in the conversation.
	 * @param string $name Name of the stage to move to.
	 */
	protected function setStage(string $name): void
}

Conversations allow input to be done over multiple messages. They are handled via "stages". For each stage, provide a name, a message that is sent that is to be replied to by the user, and the callback for the reply, which takes a \TelegramBot\Api\Types\Message object. The first stage added will be the starting point of the conversation.

Data can be saved and loaded in the conversation, and is stored in a file in a specified directory.

Example:

class ExampleConversation extends \KeythKatz\TelegramBotCore\Conversation {
	public function initialize() {
		$this->addStage("start", "Enter a message.",
			function($message) {
				if ($message->getText() === null) {
					// No text found, show an error and repeat the question.
					$reply = $this->sendMessageReply();
					$reply->setText("You did not enter anything.");
					$reply->send();
					$this->repeatStage();
				} else {
					$this->saveData("message 1", $message->getText());
					$this->setStage("next message");
				}
			}
		);

		$this->addStage("next message", "Enter another message.",
			function($message) {
				if ($message->getText() === null) {
					// No text found, show an error and repeat the question.
					$reply = $this->sendMessageReply();
					$reply->setText("You did not enter anything.");
					$reply->send();
					$this->repeatStage();
				} else {
					$text1 = $this->loadData("message 1");
					$text2 = $message->getText();
					$reply = $this->sendMessageReply();
					$reply->setText("You entered $text1 and $text2");
					$reply->send();
				}
			}
		);
	}
}

Call $this->startConversation(new ExampleConversation()) from any handler, including other conversations, to start it.

Bot: Enter a message.
User: 123
Bot: Enter another message.
User: abc
Bot: You entered 123 and abc

InteractiveMessage

abstract class InteractiveMessage extends BaseHandler
{
	/**
	 * The callback query that triggered this handler.
	 * @var \TelegramBot\Api\Types\CallbackQuery
	 */
	protected $callbackQuery;

	/**
	 * ID of the chat that the message was sent in.
	 * @var int
	 */
	protected $chatId;

	/**
	 * ID of the message that this object is handling.
	 * @var int
	 */
	protected $messageId;

	/**
	 * @param \KeythKatz\TelegramBotCore\Method\Method $baseMethod Method that will be sent. Must be able to setReplyMarkup on it.
	 */
	public function __construct(\KeythKatz\TelegramBotCore\Method\Method $baseMethod = null)

	/**
	 * What to do when a CallbackQuery belonging to this message is received.
	 * @param  \TelegramBot\Api\Types\CallbackQuery $callbackQuery CallbackQuery received.
	 * @param  \TelegramBot\Api\Types\Message       $message       Message attached to the CallbackQuery
	 */
	abstract public function process(\TelegramBot\Api\Types\CallbackQuery $callbackQuery, \TelegramBot\Api\Types\Message $message);

	/**
	 * Set the inline keyboard for the message.
	 * @param \KeythKatz\TelegramBotCore\Type\InlineKeyboardMarkup $keyboard InlineKeyboardMarkup to send with the message.
	 */
	public function setReplyMarkup(\KeythKatz\TelegramBotCore\Type\InlineKeyboardMarkup $keyboard): void

	/**
	 * Send the message. The InteractiveMessage and its data will then be saved locally.
	 */
	public function send(): void

	/**
	 * Save data that will be persistent.
	 * @param  string $name Name of the data.
	 * @param  anything $data Data to be stored.
	 */
	public function saveData(string $name, $data): void

	/**
	 * Load saved data.
	 * @param  string $name Name of the data.
	 * @return anything       Saved data.
	 */
	public function loadData(string $name)

	/**
	 * Create a new EditMessageText object. The chatId and messageId are automatically filled.
	 * @return [type] [description]
	 */
	public function editMessageText(): \KeythKatz\TelegramBotCore\Method\EditMessageText
}

InteractiveMessages allow you to easily handle CallbackQueries for a message. They work similarly to Conversations and allow you to store data that can be used throughout the lifespan of the message.

InputFile

Anywhere where InputFile is specified in the Telegram API, you may just send a resource, e.g. fopen($file, 'r'). Alternatively, you may encapsulate it in a InputFile class, e.g. new InputFile(fopen($file, 'r')).

Async

This library supports synchronous sending of messages via Guzzle/Promises. To send a message asynchronously, just change send() to sendAsync(). Both methods return Telegram's response, whatever it is according to the Telegram API.

Checklist

Bot functionality

  • Process Commands
  • Handle CallbackQueries
  • Process generic messages without command
  • Block user

Methods

  • sendMessage
  • forwardMessage
  • sendPhoto
  • sendAudio
  • sendDocument
  • sendVideo
  • sendVoice
  • sendVideoNote
  • sendMediaGroup
  • sendLocation
  • editMessageLiveLocation
  • stopMessageLiveLocation
  • sendVenue
  • sendContact
  • sendChatAction
  • getFile
  • ... chat and sticker functions out of scope for now
  • answerCallbackQuery
  • editMessageText
  • editMessageCaption
  • editMessageMedia
  • editMessageReplyMarkup
  • deleteMessage

Types

  • ReplyKeyboardMarkup
  • KeyboardButton
  • ReplyKeyboardRemove
  • InlineKeyboardMarkup
  • InlineKeyboardButton
  • ForceReply
  • InputMedia
  • InputMediaPhoto
  • InputMediaVideo
  • InputFile

Types that are receive-only (from an Update object) are handled by telegram-bot/api and are not tracked.