Building a Telegram client in Python
Telegram is an application for encrypted chats that has gained enormous popularity. Fully open source, it has a complete API both for endpoints (TDLIB) and one for creating Bots.
C++, Java and .NET are supported, plus an API that they call JSON which supposedly can be used from multiple languages.
Since I wanted to investigate the framework itself, and not the language APIs, I opted to choose a Python-library called python-telegram. There is also a variant for Bots called (surprisingly) python-telegram-bot.
Installing the library is as always with Python a simple story:
# optionally create a virtual environment python -m venv venv venv/bin/activate # install the library pip install python-telegram
Next you must create yourself an api_id and and api_hash. You do that by going to https://my.telegram.org and registering an account. Enter an App Title and a Short Name. Copy the api_id and api_hash, you will use them in your app later.
The application
from telegram.client import Telegram
tg = Telegram(
api_id='<the_api_id>',
api_hash='<the_api_hash>',
phone='+467012345678',
database_encryption_key='mysecretencryptionkey',)
tg.login()
Define a helper method for sending messages
def send_message(chat_id, msg):
result = tg.send_message(
chat_id=chat_id,
text=msg,
)
result.wait()
Message structure
This is the structure of the most common message, a simple text message. The most imoprtant fields are emphasized.
{
"@type": "updateNewMessage",
"message": {
"@type": "message",
"id": <the-message-id>,
"sender": {
"@type": "messageSenderUser",
"user_id": <the-user-id>
},
"chat_id": <the-chat-id>,
"is_outgoing": False,
"is_pinned": False,
"can_be_edited": False,
"can_be_forwarded": True,
"can_be_deleted_only_for_self": True,
"can_be_deleted_for_all_users": True,
"can_get_statistics": False,
"can_get_message_thread": False,
"can_get_viewers": False,
"can_get_media_timestamp_links": False,
"has_timestamped_media": True,
"is_channel_post": False,
"contains_unread_mention": False,
"date": 1657206884,
"edit_date": 0,
"reply_in_chat_id": 0,
"reply_to_message_id": 0,
"message_thread_id": 0,
"ttl": 0,
"ttl_expires_in": 0.0,
"via_bot_user_id": 0,
"author_signature": "",
"media_album_id": "0",
"restriction_reason": "",
"content": {
"@type": "messageText",
"text": {
"@type": "formattedText",
"text": "Hello, world!",
"entities": []
}
}
}
}
Handler for incoming messages
# this function will be called
# for each received message
def new_message_handler(msg):
# You could inspect msg['message']['@type'] here!
if msg['message']['is_outgoing'] == True:
print("(outgoing message suppressed...)")
else:
# we want to process only text messages
message_content = msg['message']['content'].get('text', {})
message_text = message_content.get('text', '').lower()
chat_id = msg['message']['chat_id']
print(f'{message_text} has been received from {chat_id}')
if message_text.startswith("//"):
if message_text == "//help":
send_message(chat_id, "type //help to get help.")
send_message(chat_id, "type //bands to get a list of good rock bands.")
elif message_text == "//bands":
send_message(chat_id, "Status Quo, Kiss and Aerosmith is a good start!")
else:
send_message(chat_id, "Sorry dont understand that! Type //help to get help")
It is very important that you check whether the “is_outgoing” is set, because messages that you send yourself will also arrive in this callback. So if you auto-respond to messages you may enter an infinite loop unless you skip messages with the “is_outgoing” is not set.
You add the message handler like this:
tg.add_message_handler(new_message_handler)
if this is the first run, library needs to preload all chats otherwise the message will not be sent
result = tg.get_chats() result.wait()
Last, setup a message loop
print("Waiting for incoming messages now...")
tg.idle()