Create a feed reader bot with PHP (1)

muhammadmp

Muhammad MP

Posted on July 28, 2022

Create a feed reader bot with PHP (1)

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"
    }
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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',
];


Enter fullscreen mode Exit fullscreen mode

And this is how our bot.php looks like:



<?php

require_once __DIR__ . '/vendor/autoload.php';

$config = require_once __DIR__ . '/config/bot.php';


Enter fullscreen mode Exit fullscreen mode

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:
How Telegram bots work

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"
        }
    ]
}


Enter fullscreen mode Exit fullscreen mode

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(),
    ]);
}


Enter fullscreen mode Exit fullscreen mode

It is ready to test:
Feed reader / add command

What happened?

  1. The listen() method, gets a string as command and a callback function to run, if the user message matches the command.
  2. 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!
  3. 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!',
    ]);
});


Enter fullscreen mode Exit fullscreen mode

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',
    ]);
});


Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
muhammadmp
Muhammad MP

Posted on July 28, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related