Instructions: How to create bots in Telegram

On June 24, Telegram developers opened a platform for creating bots. Habr sidestepped the news of someone, however many have already begun to develop quizzes. At the same time, at least some examples of working bots are indicated in few places.

First of all, the Telegram bot bot is still an application that runs on your side and makes requests to the Telegram Bot API . Moreover, the API is quite simple - the bot accesses a specific URL with parameters, and Telegram responds with a JSON object.

Consider the API using the example of creating a trivial bot:

1. Registration


Before starting development, the bot needs to register and get its unique id, which is also a token. To do this, there is a special bot in Telegram - @BotFather .

We write him / start and get a list of all his commands.
The first and main - / newbot - we send to him and the bot asks to come up with a name for our new bot. The only restriction on the name is that it must end in "bot". If successful, BotFather returns a bot token and a link for quickly adding a bot to contacts, otherwise you will have to rack your brains over the name.

This is enough to get started. Especially pedantic ones can assign an avatar, description and a welcome message to the bot here.

Do not forget to check the received token using the api.telegram.org/bot link/ getMeThey say it does not always work the first time.

2. Programming


I will create the bot in Python3, however, due to the adequacy of this language, the algorithms can easily be transferred to any other.

Telegram allows you not to upload messages manually, but to set a webHook, and then they themselves will send each message. For Python, in order not to bother with cgi and threads, it is convenient to use some kind of reactor, so I chose tornado.web for implementation. (for GAE it is convenient to use the Python2 + Flask bundle)

The bot framework:

URL = "https://api.telegram.org/bot%s/" % BOT_TOKEN
MyURL = "https://example.com/hook"
api = requests.Session()
application = tornado.web.Application([
    (r"/", Handler),
])
if __name__ == '__main__':
    signal.signal(signal.SIGTERM, signal_term_handler)
    try:
        set_hook = api.get(URL + "setWebhook?url=%s" % MyURL)
        if set_hook.status_code != 200:
            logging.error("Can't set hook: %s. Quit." % set_hook.text)
            exit(1)
        application.listen(8888)
        tornado.ioloop.IOLoop.current().start()
    except KeyboardInterrupt:
        signal_term_handler(signal.SIGTERM, None)

Here, when the bot starts, we set the webhook to our address and catch the exit signal in order to return the behavior with manual event unloading.

The tornado application for processing requests accepts the tornado.web.RequestHandler class, in which the bot logic will be.

class Handler(tornado.web.RequestHandler):
        def post(self):
            try:
                logging.debug("Got request: %s" % self.request.body)
                update = tornado.escape.json_decode(self.request.body)
                message = update['message']
                text = message.get('text')
                if text:
                    logging.info("MESSAGE\t%s\t%s" % (message['chat']['id'], text))
                    if text[0] == '/':
                        command, *arguments = text.split(" ", 1)
                        response = CMD.get(command, not_found)(arguments, message)
                        logging.info("REPLY\t%s\t%s" % (message['chat']['id'], response))
                        send_reply(response)
            except Exception as e:
                logging.warning(str(e))

Here CMD is a dictionary of available commands, and send_reply is a function for sending a response, which receives an already formed Message object as input .

Actually, her code is pretty simple:

def send_reply(response):
    if 'text' in response:
        api.post(URL + "sendMessage", data=response)


Now that the entire logic of the bot is described, you can start to come up with commands for it.

3. Teams


First things first, you must comply with the Telegram agreement and teach the bot two commands: / start and / help:

def help_message(arguments, message):
    response = {'chat_id': message['chat']['id']}
    result = ["Hey, %s!" % message["from"].get("first_name"),
              "\rI can accept only these commands:"]
    for command in CMD:
        result.append(command)
    response['text'] = "\n\t".join(result)
    return response


The message ['from'] structure is an object of type User ; it provides the bot with information about both the user id and its name. For answers, it is more useful to use message ['chat'] ['id'] - in the case of personal communication, User will be there, and in the case of chat, the id of the chat. Otherwise, you can get a situation where the user writes in the chat, and the bot responds in PM.

The / start command without parameters is intended to display information about the bot, and with parameters, for identification. It is useful to use it for actions requiring authorization.

After that, you can add your own command, for example, / base64:

def base64_decode(arguments, message):
    response = {'chat_id': message['chat']['id']}
    try:
        response['text'] = b64decode(" ".join(arguments).encode("utf8"))
    except:
        response['text'] = "Can't decode it"
    finally:
        return response


For users of the mobile Telegram, it will be useful to tell @BotFather which commands our bot accepts: With this description, if the user types /, Telegram will helpfully show a list of all available commands.
I: /setcommands
BotFather : Choose a bot to change the list of commands.
I: @******_bot
BotFather: OK. Send me a list of commands for your bot. Please use this format:

command1 - Description
command2 - Another description
I:
whoisyourdaddy - Information about author
base64 - Base64 decode
BotFather: Success! Command list updated. /help




4. Freedom


As you can see, Telegram sends the entire message, not a broken one, and the restriction that teams start with a slash is just for the convenience of mobile users. Thanks to this, you can teach the bot to speak a little humanly.

UPD: As correctly suggested, this will only happen in person. In chats, only messages starting with the command (/) (https://core.telegram.org/bots#privacy-mode)
  • All messages that start with a slash '/' (see Commands above)
  • Messages that mention the bot by username
  • Replies to the bot's own messages
  • Service messages (people added or removed from the group, etc.)



In order for the bot to receive all messages in groups, we write @BotFather command / setprivacy and turn off privacy.

First, add a handler to Handler:

if text[0] == '/':
    ...
else:
    response = CMD[""](message)
    logging.info("REPLY\t%s\t%s" % (message['chat']['id'], response))
    send_reply(response)

And then add pseudo-speech to the list of commands:

RESPONSES = {
    "Hello": ["Hi there!", "Hi!", "Welcome!", "Hello, {name}!"],
    "Hi there": ["Hello!", "Hello, {name}!", "Hi!", "Welcome!"],
    "Hi!": ["Hi there!", "Hello, {name}!", "Welcome!", "Hello!"],
    "Welcome": ["Hi there!", "Hi!", "Hello!", "Hello, {name}!",],
}
def human_response(message):
    leven = fuzzywuzzy.process.extract(message.get("text", ""), RESPONSES.keys(), limit=1)[0]
    response = {'chat_id': message['chat']['id']}
    if leven[1] < 75:
        response['text'] = "I can not understand you"
    else:
        response['text'] = random.choice(RESPONSES.get(leven[0])).format_map(
            {'name': message["from"].get("first_name", "")}
        )
    return response

Here, the empirical constant 75 relatively well reflects the likelihood that the user still wanted to say. And format_map is convenient for the same description of strings both requiring substitution, and without it. Now the bot will respond to greetings and sometimes even call by name.

5. Not text.


Bots, like any normal Telegram user, can not only write messages, but also share pictures, music, stickers.

For example, we expand the RESPONSES dictionary:

RESPONSES["What time is it?"] = ["", "{date} UTC"]

And we will catch the text :

if response['text'] == "":
        response['sticker'] = "BQADAgADeAcAAlOx9wOjY2jpAAHq9DUC"
        del response['text']

It can be seen that now the Message structure no longer contains text, so you need to modify send_reply:

def send_reply(response):
    if 'sticker' in response:
        api.post(URL + "sendSticker", data=response)
    elif 'text' in response:
        api.post(URL + "sendMessage", data=response)

And that's it, now the bot will send a sticker from time to time instead of time:



6. Features


Thanks to the convenience of the API and a quick start, Telegram bots can become a good platform for automating their actions, setting up notifications, creating quizzes and task-based competitions (CTF, DozoR and others).

Recalling an article about a smart home , I can say that now there are fewer distortions, and the work is more transparent.

7. Limitations


Unfortunately, at the moment there is a restriction on the use of webHook - it works only on https and only with a valid certificate, which, for example, is so far critical for me due to the lack of support by certification centers for dynamic DNS.

Fortunately, Telegram can also work on manual updating, so without changing the code, you can create another Puller service that will pump them out and send them to the local address:

while True:
            r = requests.get(URL + "?offset=%s" % (last + 1))
            if r.status_code == 200:
                for message in r.json()["result"]:
                    last = int(message["update_id"])
                    requests.post("http://localhost:8888/",
                                  data=json.dumps(message),
                                  headers={'Content-type': 'application/json',
                                           'Accept': 'text/plain'}
                     )
            else:
                logging.warning("FAIL " + r.text)
            time.sleep(3)


PS Under item 7, I found a convenient solution - placing the bot is not on my own, but on heroku, since all names of the form * .herokuapp.com are protected by their own certificate.

UPD: Telegram improved the Api Bot, because of which, now it is not necessary to have a separate function for sending messages when the web hook is installed, and in response to the POST request, you can respond with the same generated JSON with a response message, where one of the fields is set as h 'method ':' sendMessage '(or any other method used by the bot).

Also popular now: