In this series, we will build a Telegram bot together! Follow the posts if you are familiar with PHP and interested in Telegram bots. I try to explain what I do and make it easy for you to understand how we can build bots and implement the features that we need.
I also have created a repository for this series on Github. You may star it and see the commits:
https://github.com/WebPajooh/FeedReaderBot
What we want to build
We will build a bot that reads website feeds and sends us the new posts. Suppose that we do not have enough time to check many websites, blogs and twitter accounts to see if they have published something new. Hence, our bot fetches them and makes a list of links for us. In the beginning, we just support dev.to feeds, but we will extend it in the future; Keeping it as clean as possible.
Hello, Composer!
My experience says that building a bot without using a library is like running with no shoes! You can write some functions to avoid duplication and other problems, but I prefer using my TeleBot.
Create a directory for our bot, get inside and run this command:
composer init
After answering some questions, it is time to install TeleBot:
composer require webpajooh/telebot
Now we have a composer.json
file, including:
{
"name": "webpajooh/feedreaderbot",
"type": "project",
"authors": [
{
"name": "WebPajooh",
"email": "webpajooh@gmail.com"
}
],
"require": {
"webpajooh/telebot": "^1.5"
}
}
Creating a telegram bot
There is an official bot called @BotFather and people manage their bots there. Just start a conversation and use /newbot
command to create yours.
Now, you have an access token; such a string for communicating with the bot and run methods:
2472141014:AAGJdPk3Vv9vjQ1TfKa2JT58AtiDPZa-yXT
What is the plan?
Let's summarize our goals:
- A command for adding a feed.
- A command that shows a list of followed feeds.
- A command for removing a feed from the list.
- Checking every minute if there is a new post.
- Making a list of new posts and sent it to the developer id.
Of course, we are still a few steps away from implementing these features. We must save the mandatory configurations somewhere because we do not want to hardcode everything.
Configurations
In the root directory, create a config
folder (look at the structure):
.
├── config
│ ├── bot.php
│ └── feeds.json
├── vendor
├── bot.php
├── composer.json
└── composer.lock
We use config/bot.php
to store information such as token and user id, and feeds.json is a JSON file (is it too hard to guess this?) that keeps the feed links. Why do not we use MySQL or SQLite? Because it is extremely easier to work with JSON files and we do not need features that relational databases provide.
config/bot.php
returns an array:
<?php
return [
'bot_token' => 'your token here',
'owner_user_id' => 'your user id here',
];
And this is how our bot.php
looks like:
<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = require_once __DIR__ . '/config/bot.php';
This file is the start point that receives the Telegram incoming updates and responds our commands.
Setting the webhook address
When you send messages to bots, Telegram sends an update to some URL that processes the update and responds:
Currently, we have a token and our files are on a server. So we can connect the bot to the script by using setWebhook
method:
https://api.telegram.org/bot[token]/setWebhook?url=[https://example.com/feedreader]/bot.php
The next step is to implement the first feature!
The /add
command
We decided to store the feeds in a JSON file and it should have such a structure:
{
"feeds": [
{
"url": "https://example.com/feed",
"reader": "example"
}
]
}
Now, we can write the first command in bot.php
:
<?php
use TeleBot\TeleBot;
require_once __DIR__ . '/vendor/autoload.php';
$config = require_once __DIR__ . '/config/bot.php';
$feeds = json_decode(file_get_contents(__DIR__ . '/config/feeds.json'))->feeds;
$tg = new TeleBot($config['bot_token']);
try {
$tg->listen('/add %p', function ($url) use ($tg, $config, $feeds) {
foreach ($feeds as $feed) {
if ($url === $feed->url) {
return $tg->sendMessage([
'chat_id' => $config['owner_user_id'],
'reply_to_message_id' => $tg->message->message_id,
'text' => '❗️ Feed exists!',
]);
}
}
$feeds[] = [
'url' => $url,
'reader' => 'dev.to',
];
file_put_contents(__DIR__ . '/config/feeds.json', json_encode(['feeds' => $feeds]));
$tg->sendMessage([
'chat_id' => $config['owner_user_id'],
'reply_to_message_id' => $tg->message->message_id,
'text' => '✅ Feed added successfully!',
]);
});
} catch (Throwable $th) {
$tg->sendMessage([
'chat_id' => $config['owner_user_id'],
'text' => $th->getMessage(),
]);
}
What happened?
- The
listen()
method, gets a string as command and a callback function to run, if the user message matches the command. - If the URL has been added before, we return an error message to the user. I know this part is buggy and unreliable, but is it fine for now!
- If the URL does not exist in our array, we put it into the array (we also store a reader field for the future features), save the JSON file and inform the user that things go well.
The /remove
command
By now, the user can add a URL to follow, but we need a /remove command as well to unfollow URLs. We should filter the feeds array and remove the entered URL:
$tg->listen('/remove %p', function ($url) use ($tg, $config, $feeds) {
$newFeeds = array_filter($feeds, function ($feed) use ($url) {
return $feed->url !== $url;
});
if (count($newFeeds) === count($feeds)) {
return $tg->sendMessage([
'chat_id' => $config['owner_user_id'],
'reply_to_message_id' => $tg->message->message_id,
'text' => '❗️ Feed not found!',
]);
}
file_put_contents(__DIR__ . '/config/feeds.json', json_encode(['feeds' => $newFeeds]));
$tg->sendMessage([
'chat_id' => $config['owner_user_id'],
'reply_to_message_id' => $tg->message->message_id,
'text' => '✅ Feed removed successfully!',
]);
});
I think it is very clear, we will continue...
The /list
command
This is the last feature that we implement in this part of the article. We should show a list of followed URLs to the user:
$tg->listen('/list', function () use ($tg, $config, $feeds) {
$links = "<b>Followed feeds:</b>\n";
foreach ($feeds as $feed) {
$links .= "🔗 <code>{$feed->url}</code>\n";
}
$tg->sendMessage([
'chat_id' => $config['owner_user_id'],
'reply_to_message_id' => $tg->message->message_id,
'text' => $links,
'parse_mode' => 'html',
]);
});
It is clever to use the <code>
tag and let the user copy the URL.
Okay, it is time to say goodbye! In the next part, we will fetch the links after finding new posts. Like the post if you liked it, and feel free to write a comment or ask questions.
This is what we built:
https://github.com/muhammadmp97/FeedReaderBot/tree/374b8e2af020e48c5ad7bddc2f45417a88e246f9
Top comments (4)
Thank you for sharing!
You're welcome, dear Mahdiyar.
It's so interesting and useful and you explained everything clearly.
I appreciate you.
Thank you for your time, Mr. Roohani.