Code Monkey home page Code Monkey logo

telepot's Introduction

telepot - Python framework for Telegram Bot API

Installation
The Basics
The Intermediate
The Advanced
Async Version (Python 3.4.3 or newer)
Reference
Mailing List
Examples

Recent changes

4.1 (2015-11-03)

  • Added openable() class decorator
  • Default on_close() prints out exception
  • Async SpeakerBot and DelegatorBot constructor accepts loop parameter

4.0 (2015-10-29)

  • Revamped Listener and ChatHandler architecture
  • Added create_open()

3.2 (2015-10-13)

  • Conforms to latest Telegram Bot API as of October 8, 2015
  • Added Chat class, removed GroupChat
  • Added glance2()

Go to full changelog »


Because things are rapidly evolving, instead of sprinkling "since version N.n" all over the place, all documentations aim at the latest version.

If you are an existing user, don't worry. Most of the changes are backward-compatible. Where changes are needed in your code, they should not be hard to fix. Feel free to bug me at [email protected]

Telepot has been tested on Raspbian and CentOS, using Python 2.7 - 3.4. Below discussions are based on Raspbian and Python 2.7, except noted otherwise.

Installation

sudo apt-get install python-pip to install the Python package manager.

sudo pip install telepot to install the telepot library.

sudo pip install telepot --upgrade to upgrade.

The Basics

To use the Telegram Bot API, you first have to get a bot account by chatting with the BotFather.

BotFather will give you a token, something like 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ. With the token in hand, you can start using telepot to access the bot account.

Test the account

>>> import telepot
>>> bot = telepot.Bot('***** PUT YOUR TOKEN HERE *****')
>>> bot.getMe()
{u'username': u'YourBot', u'first_name': u'Your Bot', u'id': 123456789}

Receive messages

Bots cannot initiate conversations with users. You have to send it a message first. It gets the message by calling getUpdates().

>>> from pprint import pprint
>>> response = bot.getUpdates()
>>> pprint(response)
[{u'message': {u'chat': {u'first_name': u'Nick',
                         u'id': 999999999,
                         u'last_name': u'Lee',
                         u'type': u'private'},
               u'date': 1444723969,
               u'from': {u'first_name': u'Nick',
                         u'id': 999999999,
                         u'last_name': u'Lee'},
               u'message_id': 4015,
               u'text': u'Hello'},
  u'update_id': 100000000}]

999999999 is obviously a fake ID. Nick Lee is my real name, though.

The chat field indicates the source of the message. Its type can be private, group, or channel (whose meanings should be obvious, I hope). In the above example, Nick Lee just sent a private message to the bot.

I encourage you to experiment sending various types of messages (e.g. voice, photo, sticker, etc) to the bot, via different sources (e.g. private chat, group chat, channel), to see the varying structures of messages. Consult the Bot API documentations to learn what fields may be present under what circumstances. Bot API's object translates directly to Python dict. In the above example, getUpdates() just returns an array of Update objects represented as Python dicts.

Note the update_id. It is an ever-increasing number. Next time you should use getUpdates(offset=100000001) to avoid getting the same old messages over and over. Giving an offset essentially acknowledges to the server that you have received all update_ids lower than offset.

>>> bot.getUpdates(offset=100000001)
[]

An easier way to receive messages

It is troublesome to keep checking messages and managing offset. Fortunately, telepot can take care of that for you, and notify you whenever new messages arrive.

>>> from pprint import pprint
>>> def handle_message(msg):
...     pprint(msg)
...
>>> bot.notifyOnMessage(handle_message)

After setting up this callback, sit back and monitor the arriving messages.

Below can be a skeleton for simple telepot programs:

import sys
import time
import pprint
import telepot

def handle(msg):
    pprint.pprint(msg)
    # Do your stuff here ...


# Getting the token from command-line is better than embedding it in code,
# because tokens are supposed to be kept secret.
TOKEN = sys.argv[1]

bot = telepot.Bot(TOKEN)
bot.notifyOnMessage(handle)
print 'Listening ...'

# Keep the program running.
while 1:
    time.sleep(10)

Quickly glance2() a message

When processing a message, a few pieces of information are so central that you almost always have to extract them. Use glance2() to extract a tuple of (content_type, chat_type, chat_id) from a message.

content_type can be: text, voice, sticker, photo, audio, document, video, contact, location, new_chat_participant, left_chat_participant, new_chat_title, new_chat_photo, delete_chat_photo, or group_chat_created.

chat_type can be: private, group, or channel.

It is a good habit to always check the content_type before further processing. Do not assume every message is a text message.

The old glance() function should not be used anymore. It relies on the from field, which becomes optional in the latest Bot API. The new glance2() will supercede it eventually. I keep it for now to maintain backward-compatibility.

A better skeleton would look like:

import sys
import time
import telepot

def handle(msg):
    content_type, chat_type, chat_id = telepot.glance2(msg)
    print content_type, chat_type, chat_id
    # Do your stuff according to `content_type` ...


TOKEN = sys.argv[1]  # get token from command-line

bot = telepot.Bot(TOKEN)
bot.notifyOnMessage(handle)
print 'Listening ...'

# Keep the program running.
while 1:
    time.sleep(10)

Download files

For a voice, sticker, photo, audio, document, or video message, look for the file_id to download the file. For example, a photo may look like:

{u'chat': {u'first_name': u'Nick', u'id': 999999999, u'type': u'private'},
 u'date': 1444727788,
 u'from': {u'first_name': u'Nick', u'id': 999999999},
 u'message_id': 4017,
 u'photo': [{u'file_id': u'JiLOABNODdbdP_q2vwXLtLxHFnUxNq2zszIABM4s1rQm6sTYG3QAAgI',
             u'file_size': 734,
             u'height': 67,
             u'width': 90},
            {u'file_id': u'JiLOABNODdbdP_q2vwXLtLxHFnUxNq2zszIABJDUyXzQs-kJGnQAAgI',
             u'file_size': 4568,
             u'height': 240,
             u'width': 320},
            {u'file_id': u'JiLOABNODdbdP_q2vwXLtLxHFnUxNq2zszIABHWm5IQnJk-EGXQAAgI',
             u'file_size': 20480,
             u'height': 600,
             u'width': 800},
            {u'file_id': u'JiLOABNODdbdP_q2vwXLtLxHFnUxNq2zszIABEn8PaFUzRhBGHQAAgI',
             u'file_size': 39463,
             u'height': 960,
             u'width': 1280}]}

It has a number of file_ids, with various file sizes. These are thumbnails of the same image. Download one of them by:

>>> bot.downloadFile(u'JiLOABNODdbdP_q2vwXLtLxHFnUxNq2zszIABEn8PaFUzRhBGHQAAgI', 'save/to/path')

Send messages

Enough about receiving messages. Sooner or later, your bot will want to send you messages. You should have discovered your own user ID from above interactions. I will keeping using my fake ID of 999999999. Remember to substitute your own (real) user ID.

>>> bot.sendMessage(999999999, 'Good morning!')

After being added as an administrator to a channel, the bot can send messages to the channel:

>>> bot.sendMessage('@channelusername', 'Hi, everybody!')

Send a custom keyboard

A custom keyboard presents custom buttons for users to tab. Check it out.

>>> show_keyboard = {'keyboard': [['Yes','No'], ['Maybe','Maybe not']]}
>>> bot.sendMessage(999999999, 'This is a custom keyboard', reply_markup=show_keyboard)

Hide the custom keyboard

>>> hide_keyboard = {'hide_keyboard': True}
>>> bot.sendMessage(999999999, 'I am hiding it', reply_markup=hide_keyboard)

Send files

>>> f = open('zzzzzzzz.jpg', 'rb')  # some file on local disk
>>> response = bot.sendPhoto(999999999, f)
>>> pprint(response)
{u'chat': {u'first_name': u'Nick', u'id': 999999999, u'type': u'private'},
 u'date': 1444728667,
 u'from': {u'first_name': u'Your Bot',
           u'id': 123456789,
           u'username': u'YourBot'},
 u'message_id': 4022,
 u'photo': [{u'file_id': u'JiLOABNODdbdPyNZjwa-sKYQW6TBqrWfsztABO2NukbYlhLYlREBAAEC',
             u'file_size': 887,
             u'height': 29,
             u'width': 90},
            {u'file_id': u'JiLOABNODdbdPyNZjwa-sKYQW6TBqrWfsztABHKq66Hh0YDrlBEBAAEC',
             u'file_size': 9971,
             u'height': 102,
             u'width': 320},
            {u'file_id': u'JiLOABNODdbdPyNZjwa-sKYQW6TBqrWfsztABNBLmxkkiqKikxEBAAEC',
             u'file_size': 14218,
             u'height': 128,
             u'width': 400}]}

The server returns a number of file_ids, with various file sizes. These are thumbnails of the uploaded image. If you want to resend the same file, just give one of the file_ids.

>>> bot.sendPhoto(999999999, u'JiLOABNODdbdPyNZjwa-sKYQW6TBqrWfsztABO2NukbYlhLYlREBAAEC')

Besides sending photos, you may also sendAudio(), sendDocument(), sendSticker(), sendVideo(), and sendVoice().

Read the reference »

The Intermediate

Defining a global message handler may lead to the proliferation of global variables quickly. Encapsulation may be achieved by extending the Bot class, defining a handle method, then calling notifyOnMessage() with no callback function. This way, the object's handle method will be used as the callback.

Here is a skeleton using this strategy:

import sys
import time
import telepot

class YourBot(telepot.Bot):
    def handle(self, msg):
        content_type, chat_type, chat_id = telepot.glance2(msg)
        print content_type, chat_type, chat_id
        # Do your stuff according to `content_type` ...


TOKEN = sys.argv[1] # get token from command-line

bot = YourBot(TOKEN)
bot.notifyOnMessage()
print 'Listening ...'

# Keep the program running.
while 1:
    time.sleep(10)

Read the reference »

The Advanced

Having a single message handler is adequate for simple programs. For more sophisticated programs where states need to be maintained across messages, a better approach is needed.

Consider this scenario. A bot wants to have an intelligent conversation with a lot of users, and if we could only use a single message-handling function, we would have to maintain some state variables about each conversation outside the function. On receiving each message, we first have to check whether the user already has a conversation started, and if so, what we have been talking about. There has to be a better way.

Let's look at my solution. Here, I implement a bot that counts how many messages have been sent by an individual user. If no message is received after 10 seconds, it starts over (timeout). The counting is done per chat - that's the important point.

import sys
import telepot
from telepot.delegate import per_chat_id, create_open

class MessageCounter(telepot.helper.ChatHandler):
    def __init__(self, seed_tuple, timeout):
        super(MessageCounter, self).__init__(seed_tuple, timeout)
        self._count = 0

    def on_message(self, msg):
        self._count += 1
        self.sender.sendMessage(self._count)

TOKEN = sys.argv[1]  # get token from command-line

bot = telepot.DelegatorBot(TOKEN, [
    (per_chat_id(), create_open(MessageCounter, timeout=10)),
])
bot.notifyOnMessage(run_forever=True)

Noteworthy are two classes: DelegatorBot and MessageCounter. Let me explain one by one.

DelegatorBot

It is a Bot with the newfound ability to spawn delegates. Its constructor takes a list of tuples telling it when and how to spawn delegates. In the example above, it is spawning one MessageCounter per chat id.

Technically, per_chat_id() returns a seed-calculating-function, and create_open() returns a delegate-producing-function. I won't get into details here. You can read the reference for that.

Simply put, a seed-calculating-function determines when to spawn a delegate, while a delegate-producing-function determines how to spawn one. The default manifestation of a delegate is a thread, although you can override that.

MessageCounter, extending from ChatHandler

This is the class that deals with an individual chat. Being a subclass of ChatHandler offers a few benefits:

  • inherits utilities for dealing with a chat, e.g. a sender object that makes it easy to send messages to a chat (as demonstrated above), a listener object that waits for messages from the chat (implicitly done, not shown above).
  • inherits most methods and attributes required by create_open(). Just implement on_message() and you are done.

on_message() is called whenever a chat message arrives. How messages are distributed to the correct object is done by the library. You don't have to worry about it.

Read the reference »

Async Version (Python 3.4.3 or newer)

Everything discussed so far assumes traditional Python. That is, network operations are blocking; if you want to serve many users at the same time, some kind of threads are usually needed. Another option is to use an asynchronous or event-driven framework, such as Twisted.

Python 3.4 introduces its own asynchronous architecture, the asyncio module. Telepot supports that, too. If your bot is to serve many people, I strongly recommend doing it asynchronously. Threads, in my opinion, is a thing of yesteryears.

Raspbian does not come with Python 3.4. You have to compile it yourself.

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install libssl-dev openssl libreadline-dev
$ cd ~
$ wget https://www.python.org/ftp/python/3.4.3/Python-3.4.3.tgz
$ tar zxf Python-3.4.3.tgz
$ cd Python-3.4.3
$ ./configure
$ make
$ sudo make install

Finally:

$ sudo pip3.4 install telepot

In case you are not familiar with asynchronous programming, let's start by learning about generators and coroutines:

... why we want asynchronous programming:

... how generators and coroutines are applied to asynchronous programming:

... and how an asyncio program is generally structured:

Very similar to the traditional, but different

The async version of Bot, SpeakerBot, and DelegatorBot basically mirror the traditional version's. Main differences are:

  • blocking methods (e.g. sendMessage()) are now coroutines, and should be called with yield from
  • delegation is achieved by coroutine and task

Because of that (and this is true of asynchronous Python in general), a lot of methods will not work in the interactive Python interpreter like regular functions would. They will have to be driven by an event loop.

Skeleton, by extending the basic Bot

import sys
import asyncio
import telepot
import telepot.async

class YourBot(telepot.async.Bot):
    @asyncio.coroutine
    def handle(self, msg):
        content_type, chat_type, chat_id = telepot.glance2(msg)
        print(content_type, chat_type, chat_id)
        # Do your stuff according to `content_type` ...


TOKEN = sys.argv[1]  # get token from command-line

bot = YourBot(TOKEN)
loop = asyncio.get_event_loop()

loop.create_task(bot.messageLoop())
print('Listening ...')

loop.run_forever()

Skeleton, by defining a global hander

import sys
import asyncio
import telepot
import telepot.async

@asyncio.coroutine
def handle(msg):
    content_type, chat_type, chat_id = telepot.glance2(msg)
    print(content_type, chat_type, chat_id)
    # Do your stuff according to `content_type` ...


TOKEN = sys.argv[1]  # get token from command-line

bot = telepot.async.Bot(TOKEN)
loop = asyncio.get_event_loop()

loop.create_task(bot.messageLoop(handle))
print('Listening ...')

loop.run_forever()

Skeleton for DelegatorBot

I have re-done the MessageCounter example here. Again, it is very similar to the traditional version.

import sys
import asyncio
import telepot
from telepot.delegate import per_chat_id
from telepot.async.delegate import create_open

class MessageCounter(telepot.helper.ChatHandler):
    def __init__(self, seed_tuple, timeout):
        super(MessageCounter, self).__init__(seed_tuple, timeout)
        self._count = 0

    @asyncio.coroutine
    def on_message(self, msg):
        self._count += 1
        yield from self.sender.sendMessage(self._count)

TOKEN = sys.argv[1]  # get token from command-line

bot = telepot.async.DelegatorBot(TOKEN, [
    (per_chat_id(), create_open(MessageCounter, timeout=10)),
])

loop = asyncio.get_event_loop()
loop.create_task(bot.messageLoop())
print('Listening ...')

loop.run_forever()

Read the reference »

Examples

Dicey Clock

Here is a tutorial teaching you how to setup a bot on Raspberry Pi. This simple bot does nothing much but accepts two commands:

  • /roll - reply with a random integer between 1 and 6, like rolling a dice.
  • /time - reply with the current time, like a clock.

Source »

Skeletons

A starting point for your telepot programs.

Traditional version 1 »
Traditional version 2 »
Async version 1 »
Async version 2 »

Indoor climate monitor

Running on a Raspberry Pi with a few sensors attached, this bot accepts these commands:

  • /now - Report current temperature, humidity, and pressure
  • /1m - Report every 1 minute
  • /1h - Report every 1 hour
  • /cancel - Cancel reporting

Source »

IP Cam using Telegram as DDNS

Running on a Raspberry Pi with a camera module attached, this bot accepts these commands:

  • /open - Open a port through the router to make the video stream accessible, and send you the URL (which includes the router's public IP address)
  • /close - Close the port

Project page »

Emodi - an Emoji Unicode Decoder

Sooner or later, you want your bots to be able to send emoji. You may look up the unicode on the web, or from now on, you may just fire up Telegram and ask Emodi 😊

Traditional version »
Async version »

I am running this bot on a CentOS server. You should be able to talk to it 24/7. Intended for multiple users, the async version is being run.

By the way, I just discovered a Python emoji package. Use it.

Message Counter

Counts number of messages a user has sent. Illustrates the basic usage of DelegateBot and ChatHandler.

Traditional version »
Async version »

Guess-a-number

  1. Send the bot anything to start a game.
  2. The bot randomly picks an integer between 0-99.
  3. You make a guess.
  4. The bot tells you to go higher or lower.
  5. Repeat step 3 and 4, until guess is correct.

This example is able to serve many players at once. It illustrates the usage of DelegateBot and ChatHandler.

Traditional version »
Async version »

Chatbox - a Mailbox for Chats

  1. People send messages to your bot.
  2. Your bot remembers the messages.
  3. You read the messages later.

It accepts the following commands from you, the owner, only:

  • /unread - tells you who has sent you messages and how many
  • /next - read next sender's messages

This example can be a starting point for customer support type of bot accounts. For example, customers send questions to a bot account; staff answers the questions behind the scene, makes it look like the bot is answering questions.

It further illustrates the use of DelegateBot and ChatHandler, and how to spawn delegates differently according to the role of users.

This example only handles text messages and stores messages in memory. If the bot is killed, all messages are lost. It is an example after all.

Traditional version »
Async version »

telepot's People

Contributors

nickoala avatar

Watchers

James Cloos avatar Murilo Ferraz avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.