DEV Community

Cover image for 15 minutes to create a Telegram Bot that can answer any question
Leonardo Bonetti
Leonardo Bonetti

Posted on

15 minutes to create a Telegram Bot that can answer any question

Introduction

In last article we create a personal assistant in 15 minutes,
due to its good acceptance, I decided to bring other challenges of 15 minutes, today, a telegram bot called Genius, he will try to answer anything you ask

The rules:

- interact with the system by telegram chat (Obviously)
- Searches must be carried out using wikipedia
- The robot must be able to change its response if it is not satisfactory to the user
Enter fullscreen mode Exit fullscreen mode

Let's start

Create project

Create a folder to your project, on terminal execute the following command:
npm init -y && npx ts-init

Check that your tsconfig.json file is as follows

{
  "compilerOptions": {
    "lib": [
      "es6",
      "DOM"
    ],
    "alwaysStrict": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "resolveJsonModule": true
  },
  "files": [
    "src/index.ts"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Lets get our packages

  • npm i node-telegram-bot-api --save && npm i @types/node-telegram-bot-api -D node-telegram-bot-api is an amazing library to abstract Telegram Bot API and make our lives easier.
  • npm i wikipedia --save Wikipedia is a simple lib that abstract the wiki endpoints.

Create our bot

  • Open your telegram and search for @botfather searchBotFather
  • Open the conversation and click in start startBotFather
  • Type /newBot typeNewBot
  • Give the common name and the name of the robot as indicated by the telegram instructions giveBotName
  • Save the token key in some place to we use later

Let's code

First of all, wee need to import the libraries that will be used, so, create a file named src/index.ts.

import telegram from 'node-telegram-bot-api';
import wikipedia from 'wikipedia';

const TELEGRAM_KEY = "YOUR-API-KEY-HERE";
Enter fullscreen mode Exit fullscreen mode

remember to replace where YOUR-API-KEY-HERE is written with your robot key

Create some interaction

Paste this code inside your src/index.ts

...
const Bot = new telegram(TELEGRAM_KEY, {polling: true});

Bot.onText(/\/start/, async (msg) => {
    if(!msg.from) return Bot.sendMessage(msg.chat.id, 'I not accept you!');
    Bot.sendMessage(msg.chat.id, 'Wellcome to GeniusAnswer, ask me something');
});
Enter fullscreen mode Exit fullscreen mode

When a user starts the bot, we send a message asking the user to ask something

The main function

Paste this code inside your src/index.ts

...
const wikisearch = async (topic: string, pageIndex: number) => {
    const search = await wikipedia.search(topic);

    if(pageIndex > search.results.length) throw new Error('Invalid page index');

    const page = await wikipedia.page(search.results[pageIndex].title);

    const summary = await page.summary();

    return {text: summary.extract, pageIndex: 0, pageLength: search.results.length};
};

Bot.on("text", async (msg) => {
  if (!msg.from) return Bot.sendMessage(msg.chat.id, "I not accept you!");
  if (!msg.text) return Bot.sendMessage(msg.chat.id, "Invalid message");
  if (msg.text[0] === "/") return;

  Bot.sendMessage(msg.chat.id, `Searching for ${msg.text} ...`);

  const search = await wikisearch(msg.text, 0);

  console.log(search);

  let options_button = {};
  if (search.pageIndex < search.pageLength) {
    options_button = {
      reply_markup: {
        inline_keyboard: [
          [
            {
              text: "Next Answer ->",
              callback_data: JSON.stringify({ topic: msg.text, pageIndex: 1 }),
            },
          ],
        ],
      },
    };
  }

  return Bot.sendMessage(
    msg.chat.id,
    `${search.text} \n Answer ${search.pageIndex + 1}/${search.pageLength}`,
    options_button
  );
});

});
Enter fullscreen mode Exit fullscreen mode

Here we only create a search function thata can make searchs on wikipedia and return the index of this result, if wee need a diferent result for this question, we only need to pass a diferent index for the function.
On next function will listen for text messages send to your bot, on result wee put a button that can change the index of search.

The callback function

Paste this code inside your src/index.ts

...
Bot.on("callback_query", async (callback) => {
  if (!callback.data || !callback.message) return;

  console.log(callback.data);

  const data = JSON.parse(callback.data) as {
    topic: string;
    pageIndex: number;
  };

  try {
    const search = await wikisearch(data.topic, data.pageIndex);

    console.log(search);

    let options_button = {};
    let inline_keyboard_buttons = [];
    if (search.pageIndex + 1 < search.pageLength) {
      inline_keyboard_buttons.unshift({
        text: "Next Answer ->",
        callback_data: JSON.stringify({
          topic: data.topic,
          pageIndex: search.pageIndex + 1,
        }),
      });

      if (search.pageIndex > 0) {
        inline_keyboard_buttons.unshift({
          text: "<- Previous Answer",
          callback_data: JSON.stringify({
            topic: data.topic,
            pageIndex: search.pageIndex - 1,
          }),
        });
      }
    } else if (search.pageIndex + 1 === search.pageLength) {
      inline_keyboard_buttons.unshift({
        text: "<- Previous Answer",
        callback_data: JSON.stringify({
          topic: data.topic,
          pageIndex: search.pageIndex - 1,
        }),
      });
    }

    if (inline_keyboard_buttons.length > 0) {
      options_button = {
        reply_markup: {
          inline_keyboard: [inline_keyboard_buttons],
        },
      };
    }

    return Bot.editMessageText(
      `${search.text} \n Answer ${search.pageIndex + 1}/${search.pageLength}`,
      {
        chat_id: callback.message.chat.id,
        message_id: callback.message.message_id,
        ...options_button,
      }
    );
  } catch (error) {
    return Bot.editMessageText(
      "Sorry, an error seems to have happened, please try again later",
      {
        chat_id: callback.message.chat.id,
        message_id: callback.message.message_id,
      }
    );
  }
});
Enter fullscreen mode Exit fullscreen mode

Ok, although the callback function is very long, is easily to be understood, we just capture the search topic and change the index, depending on whether or not you have a next or previous page, we add the respective buttons to the message.

Now your code need to be like this

import telegram from "node-telegram-bot-api";
import wikipedia from "wikipedia";

const TELEGRAM_KEY = "YOUR-TELEGRAM-KEY-HERE";

const Bot = new telegram(TELEGRAM_KEY, { polling: true });

Bot.onText(/\/start/, (msg) => {
  if (!msg.from) return Bot.sendMessage(msg.chat.id, "I not accept you!");
  Bot.sendMessage(msg.chat.id, "Wellcome to GeniusAnswer, ask me something");
});

const wikisearch = async (topic: string, pageIndex: number) => {
  const search = await wikipedia.search(topic);

  if (pageIndex > search.results.length) throw new Error("Invalid page index");

  const page = await wikipedia.page(search.results[pageIndex].title);

  const summary = await page.summary();

  return {
    text: summary.extract,
    pageIndex: pageIndex,
    pageLength: search.results.length,
  };
};

Bot.on("text", async (msg) => {
  if (!msg.from) return Bot.sendMessage(msg.chat.id, "I not accept you!");
  if (!msg.text) return Bot.sendMessage(msg.chat.id, "Invalid message");
  if (msg.text[0] === "/") return;

  Bot.sendMessage(msg.chat.id, `Searching for ${msg.text} ...`);

  const search = await wikisearch(msg.text, 0);

  console.log(search);

  let options_button = {};
  if (search.pageIndex < search.pageLength) {
    options_button = {
      reply_markup: {
        inline_keyboard: [
          [
            {
              text: "Next Answer ->",
              callback_data: JSON.stringify({ topic: msg.text, pageIndex: 1 }),
            },
          ],
        ],
      },
    };
  }

  return Bot.sendMessage(
    msg.chat.id,
    `${search.text} \n Answer ${search.pageIndex + 1}/${search.pageLength}`,
    options_button
  );
});

Bot.on("callback_query", async (callback) => {
  if (!callback.data || !callback.message) return;

  console.log(callback.data);

  const data = JSON.parse(callback.data) as {
    topic: string;
    pageIndex: number;
  };

  try {
    const search = await wikisearch(data.topic, data.pageIndex);

    console.log(search);

    let options_button = {};
    let inline_keyboard_buttons = [];
    if (search.pageIndex + 1 < search.pageLength) {
      inline_keyboard_buttons.unshift({
        text: "Next Answer ->",
        callback_data: JSON.stringify({
          topic: data.topic,
          pageIndex: search.pageIndex + 1,
        }),
      });

      if (search.pageIndex > 0) {
        inline_keyboard_buttons.unshift({
          text: "<- Previous Answer",
          callback_data: JSON.stringify({
            topic: data.topic,
            pageIndex: search.pageIndex - 1,
          }),
        });
      }
    } else if (search.pageIndex + 1 === search.pageLength) {
      inline_keyboard_buttons.unshift({
        text: "<- Previous Answer",
        callback_data: JSON.stringify({
          topic: data.topic,
          pageIndex: search.pageIndex - 1,
        }),
      });
    }

    if (inline_keyboard_buttons.length > 0) {
      options_button = {
        reply_markup: {
          inline_keyboard: [inline_keyboard_buttons],
        },
      };
    }

    return Bot.editMessageText(
      `${search.text} \n Answer ${search.pageIndex + 1}/${search.pageLength}`,
      {
        chat_id: callback.message.chat.id,
        message_id: callback.message.message_id,
        ...options_button,
      }
    );
  } catch (error) {
    return Bot.editMessageText(
      "Sorry, an error seems to have happened, please try again later",
      {
        chat_id: callback.message.chat.id,
        message_id: callback.message.message_id,
      }
    );
  }
});
Enter fullscreen mode Exit fullscreen mode

Now, our code is complete, let's test?

Run npm run ts on your terminal and open your telegram.
Search for your bot name (the same that you create in the begin of this article, generally ends with _bot) and press START.

Enjoy yourself!

There are some ways to optimize the response time of this robot, if you are interested I can show them in another article later, but I believe this is an interesting challenge for those who liked the idea of ​​the robot, leave in the comments your solutions and ideas

Discussion (1)

Collapse
youmovise123 profile image
youmovise123

سایت خیلی خوبی دارید
امیدوارم موفق باشید
youmovise.com/