Mr Baumgarten

Computer Science teacher and self confessed geek

Telegram

Telegram is social media messaging platform like Whatsapp or iMessage. They have quite a "developer friendly" framework, so I recommend it as the starting point if you want to build a project that interacts with a messaging platform that you can use via your phone.

Requirements

Requires the following Python packages:

  • python-telegram-bot==12.0.0b1
  • pillow (for the camera part of the demo)
  • opencv-contrib-python (for the camera part of the demo)
  • numpy (for the camera part of the demo)

If you use pip run pip install python-telegram-bot pillow opencv-contrib-python numpy (remember to use sudo or pip3 if you usually have do)

Note: to install the version 12 beta, the pip command is

pip install python-telegram-bot==12.0.0b1 --upgrade

Step 1 - Chat with the BotFather

  • Install the Telegram app if you don't already have it
  • When creating your account, you will need to allow the app to verify your phone number, but once that is done you can safely revoke that permission in your phone settings
  • Not sure about iOS, but on Android it is safe to deny permission to import your contacts, access your photos etc (unless, of course, you want to send photos to your bot later)
  • Following the screenshots below: Search for the BotFather account, send it a /newbot command, answer the questions and write down your API token.

Step 2 - Test with demo code

  • Note: the camera module used in the following example is my example one provided here
from telegram.ext import Updater, CommandHandler, Filters, MessageHandler
from datetime import datetime
import logging
import telegram
import camera
from PIL import Image

api_key = "---insert-your-api-key---"
owner_id = 000000000 # Add your telegram ID number here

# Initiate logging
logging.basicConfig(filename="python.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)

# Webcam
cam = camera.Camera()

def photo(update, context):
    print("photo request received")
    cam.save_photo("photo.jpg")
    update.message.reply_photo(photo=open('photo.jpg', 'rb'))

def hello(update, context):
    print("hello received ",update.message.chat_id)
    date_str = datetime.now().strftime("%d/%m/%Y %H:%M")
    uid = update.message.from_user
    update.message.reply_text("Hello {}! Test was received at {}".format(uid.username, date_str))

def echo(update, context):
    print("echo received ",update.message.chat_id)
    date_str = datetime.now().strftime("%d/%m/%Y %H:%M")
    uid = update.message.from_user 
    msg = update.message.text
    update.message.reply_text("Hello {}! You said: {}".format(uid.first_name, msg))

def error(update, context):
    logger.warning('Update "%s" caused error "%s"', update, context.error)

if __name__ == "__main__":
    print("Welcome")
    cam.save_photo("photo.jpg")
    # Create the bot object
    updater = Updater(token=api_key, use_context=True)
    me = updater.bot.get_me()
    print("Welcome to my Telegram bot")
    print("username:     "+me['username'])
    print("display name: "+me['first_name'])
    # Setup response paths
    updater.dispatcher.add_handler(CommandHandler("hello", hello))
    updater.dispatcher.add_handler(CommandHandler("photo", photo))
    updater.dispatcher.add_handler(MessageHandler(Filters.text, echo))
    updater.dispatcher.add_error_handler(error)
    updater.start_polling() # Start the bot (non-blocking)
    updater.bot.send_message(chat_id=owner_id, text="PhotoBot has started.")
    updater.idle() # Run the bot until you press Ctrl-C or the process receives SIGINT, SIGTERM or SIGABRT.

Step 3 - Make it your own

Responding to commands

Telegram commands are messages that begin with a / such as /whoami.

To create a handler that responds to this command that will run the who_am_i() function, add a CommandHandler as follows:

    updater.dispatcher.add_handler(CommandHandler('whoai', who_am_i))

The template for your response function would then look like

def who_am_i(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /whoami command")
    # do something interesting
    update.message.reply_text("It's too early in the morning for such a deep question!")

It is possible to receive commands with various parameters. For instance, you could use /getfile photo1.jpg (please implement security checks on who you are replying to!). The parameters are in a python list at context.args. For instance:

trusted_whitelist = [999999999]

def sendfile(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /sendfile command")
    if update.message.from_user.id in trusted_whitelist:
        for filename in context.args:
            print(sender+": Sending file "+filename)
            update.message.reply_document(text="Have a file...", document=open(filename, 'rb'))

Responding to text messages

Messages that do not start with a / are simply text. You can process these as you like as well. Really there's no reason you couldn't run commands based off their content as well, it's just a Telegram convention that commands start with the slash so there is a built in handler to help deal with them.

The normal handler for a text message that would call a function called echo() would be:

    updater.dispatcher.add_handler(MessageHandler(Filters.text, echo))

The template for your response function would then look like

def echo(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent a message")
    # do something interesting
    update.message.reply_text("You said: "+update.message.text)

Sending a text message

  • In reply to an existing message
    update.message.reply_text("Hello!")
  • As a new message
    updater.bot.send_message(chat_id=owner_id, text="PhotoBot has started.")

Receiving a photo/video/audio/file

This helpful got_file() function will accept most file types and save them for you automatically into a downloads folder of your choosing.

def got_file(update, context):
    global folder # eg, folder = "downloads/"
    file_id = None
    bot = None
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    if update.message.audio:
        print(sender+": Send an audio message")
        file_id = update.message.audio.file_id
        bot = update.message.audio.bot
    elif update.message.document:
        print(sender+": Sent a document")
        file_id = update.message.document.file_id
        bot = update.message.document.bot
    elif update.message.photo:
        print(sender+": Sent a photo")
        file_id = update.message.photo[-1].file_id
        bot = update.message.photo[-1].bot
    elif update.message.video:
        print(sender+": Sent a video")
        file_id = update.message.video.file_id
        bot = update.message.video.bot
    elif update.message.video_note:
        print(sender+": Sent a video note")
        file_id = update.message.video_note.file_id
        bot = update.message.video_note.bot
    elif update.message.voice:
        print(sender+": Sent a voice message")
        file_id = update.message.voice.file_id
        bot = update.message.voice.bot
    if file_id and bot:
        new_file = bot.get_file(file_id)
        extension = (new_file.file_path).split(".")[-1] # get file extension of received file
        datetime_str = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
        file_name = folder + datetime_str + "-" + str(update.message.chat_id) + "." + extension
        new_file.download(file_name)
        print("Downloaded file: {}".format(file_name))
    else:
        print("Unable to download file")
  • Handler(s) to add
    # Response paths for various file attachment types, can all go to same handler
    updater.dispatcher.add_handler(MessageHandler(Filters.audio, got_file))
    updater.dispatcher.add_handler(MessageHandler(Filters.document, got_file))
    updater.dispatcher.add_handler(MessageHandler(Filters.photo, got_file))
    updater.dispatcher.add_handler(MessageHandler(Filters.voice, got_file))
    updater.dispatcher.add_handler(MessageHandler(Filters.video, got_file))
    updater.dispatcher.add_handler(MessageHandler(Filters.video_note, got_file))

Sending a photo message

  • In reply to an existing message
folder = "downloads/"
photo_whitelist = [ 111111111, 222222222, 333333333 ] # Telegram userid of trusted accounts

def photo(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /photo")
    if update.message.from_user.id in photo_whitelist: # only those we trust
        print("Photo request allowed")
        cam.save_photo("photo.jpg")
        update.message.reply_photo(photo=open('photo.jpg', 'rb'))
    else:
        print("Photo request denied")
        update.message.reply_text("Denied")

The photo= parameter can be a file from disk, website address, of PIL image from memory. Refer to the examples in the "as a new message" section as they are the same format.

  • As a new message
# from your disk
updater.bot.send_photo(chat_id=chat_id, photo=open('tests/test.png', 'rb'))

# from a website or online source
updater.bot.send_photo(chat_id=chat_id, photo='https://telegram.org/img/t_logo.png')

# from an image loaded into memory
img = Image() # PIL Image object
# or .... img = camera.Camera().get_photo() # to use the above linked camera module
updater.bot.send_photo(chat_id, photo=img)

# An animated gif
url = "https://www.website.com/animated.gif"
settings = {
    duration=None, 
    width=None, 
    height=None, 
    thumb=None, 
    caption=None, 
    parse_mode=None, 
    disable_notification=False, 
    reply_to_message_id=None, 
    reply_markup=None, 
    timeout=20
}
updater.bot.send_animation(chat_id, url, **settings)

Sending a video message

  • In reply to an existing message
def send_video(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /sendvideo command")
    # do something exciting
    update.message.reply_audio(text="Have a video...", audio=open('recording.mp4', 'rb'))
  • As a new message
updater.bot.send_video(chat_id=chat_id, video=open('recording.mp4', 'rb'))

Sending an audio message

  • In reply to an existing message
def send_audio(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /sendaudio command")
    # do something exciting
    update.message.reply_audio(text="Have a video...", audio=open('recording.mp3', 'rb'))
  • As a new message
updater.bot.send_audio(chat_id=chat_id, audio=open('recording.mp3', 'rb'))

Note: for OGG files, use reply_voice and send_voice as appropriate;

  • update.message.reply_voice(text="Have a document...", audio=open('recording.ogg', 'rb'))
  • updater.bot.send_voice(chat_id=chat_id, voice=open('recording.ogg', 'rb'))

Sending a different type of file

  • In reply to an existing message
def sendfile(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /sendfile command")
    update.message.reply_document(text="Have a document...", document=open('tests/test.zip', 'rb'))
  • As a new message
    updater.bot.send_document(chat_id=chat_id, document=open('tests/test.zip', 'rb'))

Sending buttons in a message / receiving response

  • Import
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
  • Send buttons as a message reply
def paperrocksissors(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /play command")
    # send buttons as a reply
    buttons = [
        [ # start a row
            InlineKeyboardButton("Paper", callback_data="button1"),
            InlineKeyboardButton("Rock", callback_data="button2"),
        ],[ # start next row
            InlineKeyboardButton("Sissors", callback_data="button3")
        ] # end of row
    ] # end of buttons
    buttons_markup = InlineKeyboardMarkup(buttons)
    update.message.reply_text("What will it be?", reply_markup=buttons_markup)
  • Send buttons as a new message
    buttons = [
        [ # start a row
            InlineKeyboardButton("Paper", callback_data="button1"),
            InlineKeyboardButton("Rock", callback_data="button2"),
        ],[ # start next row
            InlineKeyboardButton("Sissors", callback_data="button3")
        ] # end of row
    ] # end of buttons
    buttons_markup = InlineKeyboardMarkup(buttons)
    updater.bot.send_message(chat_id=owner_id, text="What will it be?", reply_markup=buttons_markup)
  • Process response
def button(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Clicked a button!")
    # this function is run when the button click is received
    clicked = update.callback_query.data # callback_query.data contains the text that was set in `callback_data`
    print("button was pressed! clicked = {}".format(clicked))
    update.callback_query.edit_message_text(text="Selected option: {}".format(clicked))
  • Handler to add

To handle the callback_data, you need to set a CallbackQueryHandler specifying the callback function.

    updater.dispatcher.add_handler(CallbackQueryHandler(button))

Sending a request for GPS / receiving GPS

  • Request a contact as a reply
def whereami(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /whereami command")
    location_keyboard = telegram.KeyboardButton(text="Send my GPS location", request_location=True)
    reply_markup = telegram.ReplyKeyboardMarkup([[ location_keyboard ]])
    update.message.reply_text(text="Share location?", reply_markup=reply_markup)
  • Request a contact as a new message
    location_keyboard = telegram.KeyboardButton(text="Send my GPS", request_location=True)
    reply_markup = telegram.ReplyKeyboardMarkup([[ location_keyboard ]])
    updater.bot.send_message(chat_id=owner_id, text="PhotoBot has started.", reply_markup=reply_markup)
  • Processing the response
def got_location(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent GPS location")
    gps = update.message.location
    print("Latitude: {}, Longitude: {}".format(gps.latitude, gps.longitude))
  • Handler to add
    updater.dispatcher.add_handler(MessageHandler(Filters.location, got_location))

Sending a request for contact / receiving contact info

  • Request a contact as a reply
def whoami(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent /whoami command")
    contact_keyboard = telegram.KeyboardButton(text="Send my contact info", request_contact=True)
    reply_markup = telegram.ReplyKeyboardMarkup([[ contact_keyboard ]])
    text = "Share your contact info?"
    update.message.reply_text(text, reply_markup=reply_markup)
  • Request a contact as a new message
    send_to_id = 00000000 # telegram id number
    contact_keyboard = telegram.KeyboardButton(text="Send my contact", request_contact=True)
    reply_markup = telegram.ReplyKeyboardMarkup([[ contact_keyboard ]])
    updater.bot.send_message(chat_id=send_to_id, text="PhotoBot has started.", reply_markup=reply_markup)
  • Processing the response
def got_contact(update, context):
    sender = update.message.from_user.username + " ("+str(update.message.from_user.id)+")"
    print(sender+": Sent contact information")
    contact = update.message.contact
    print("Given name:   "+contact.first_name)
    print("Family name:  "+contact.last_name)
    print("Telegram ID:  "+str(contact.user_id))
    print("Phone number: "+contact.phone_number)
  • Handler to add
    updater.dispatcher.add_handler(MessageHandler(Filters.contact, got_contact))

Structure of some common objects

update.message.from_user

{
    'id': 999999999, 
    'first_name': 'John', 
    'is_bot': False, 
    'last_name': 'Doe', 
    'username': 'johndoe', 
    'language_code': 'en'
}

update.message

    {
        'message_id': 124, 
        'date': 1550868972, 
        'chat': {
            'id': 999999999, 
            'type': 'private', 
            'username': 'johndoe', 
            'first_name': 'John', 
            'last_name': 'Doe'
        }, 
        'text': 'What will it be?', 
        'entities': [], 
        'caption_entities': [], 
        'photo': [], 
        'new_chat_members': [], 
        'new_chat_photo': [], 
        'delete_chat_photo': False, 
        'group_chat_created': False, 
        'supergroup_chat_created': False, 
        'channel_chat_created': False, 
        'from': {
            'id': 999999999, 
            'first_name': 'demo', 
            'is_bot': True, 
            'username': 'demo_bot'
        }
    }

bot

{
    'id': 999999999, 
    'username': 'demo', 
    'first_name': 'demo_bot'
}

video_note

{
    'file_id': 'DQADBAADigUAAruhkFNv9oDDBD4AAZwC', 
    'length': 240, 
    'duration': 2, 
    'thumb': {
        'file_id': 'AAQEABPw-iobAASmnLpP_6jZ4vCIAAIC', 
        'width': 240, 'height': 240, 'file_size': 6180
    }, 
    'file_size': 68268
}

References

https://github.com/python-telegram-bot/python-telegram-bot/wiki/Code-snippets#send-a-chat-action