mirror of
https://github.com/cupcakearmy/nicco.io.git
synced 2026-07-28 10:16:07 +00:00
astro first commit
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
---
|
||||
title: 'Telegram bots are easy'
|
||||
date: '2019-06-24'
|
||||
categories:
|
||||
- 'coding'
|
||||
tags:
|
||||
- 'automatization'
|
||||
- 'bot'
|
||||
- 'python'
|
||||
- 'telegram'
|
||||
coverImage: './images/arseny-togulev-1513013-unsplash-scaled.jpg'
|
||||
---
|
||||
|
||||
Recently I've been frustrated with mobile stock apps so I started thinking about solutions. a whole app would have been an unnecessary overkill, after all I just want to get market data about my stocks with some indicators to go along. A perfect use case for a telegram bot!
|
||||
|
||||
Today we will have a look at how telegram bots are written and show some base concepts around the platform. We are going to leverage [python-telegram-bot](https://python-telegram-bot.org/) ([latest docs](https://python-telegram-bot.readthedocs.io/en/latest/)).
|
||||
|
||||
#### What we will build today:
|
||||
|
||||
1. A very simple bot that just replies a picture of a doggo whenever we send `/woof`.
|
||||
2. A bot that has **persistence** and will save a list of items we can add.
|
||||
3. (bonus round) Dockerize the bot for running it everywhere.
|
||||
|
||||
<figure>
|
||||
|
||||

|
||||
|
||||
<figcaption>
|
||||
|
||||
Photo by [Arseny Togulev](https://unsplash.com/@tetrakiss?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/robot?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
|
||||
|
||||
</figcaption>
|
||||
|
||||
</figure>
|
||||
|
||||
#### Here we go 🤖
|
||||
|
||||
The first thing we need is a bot. To register one you need to talk to the [@BotFather](https://telegram.me/BotFather) (with your normal telegram account). Then simply enter the `[/newbot](tg://bot_command?command=newbot)` command. You will be asked the name and the @handle for your bot. The handle need to end in `bot`.
|
||||
After that he will answer with your access token.
|
||||
|
||||
<figure>
|
||||
|
||||

|
||||
|
||||
<figcaption>
|
||||
|
||||
Response after successful bot registration
|
||||
|
||||
</figcaption>
|
||||
|
||||
</figure>
|
||||
|
||||
## How do bots work?
|
||||
|
||||
<figure>
|
||||
|
||||

|
||||
|
||||
<figcaption>
|
||||
|
||||
very badly drawn representation of the bot
|
||||
|
||||
</figcaption>
|
||||
|
||||
</figure>
|
||||
|
||||
In its most simple form bots are simple programs that listen to _commands_ send by a user. The cannot initiate a conversation, a user must always message the bot first. After initial contact, the bot can send as many messages as he wants.
|
||||
|
||||
Your code connects to the telegram servers and gets notified if new messages/commands have been sent. Then based on the user input your bot can reply with a message, audio, photo or use external services like sending a slack notification, whatever, there are no real limits.
|
||||
|
||||
When the user firsts contacts a bot telegram automatically sends a `/start` command which you can use to initialise the bot. When a bot is deleted by a user the corresponding `/stop` command is issued.
|
||||
|
||||
## Doggo Bot
|
||||
|
||||
We are going to create a virtual environment for the project and install the dependencies. Our starting point will be the basic bot skeleton from which we will fill out the functionality.
|
||||
|
||||
Note: we need version 12 of _python-telegram-bot_, as the API changed quite a bit in the previous releases.
|
||||
|
||||
```bash
|
||||
python3 -m venv env
|
||||
source env/bin/activate
|
||||
pip install python-telegram-bot==12.0.0b1
|
||||
```
|
||||
|
||||
```python
|
||||
from telegram.ext import Updater, CommandHandler
|
||||
|
||||
TOKEN = 'myTelegramBotToken'
|
||||
|
||||
def main():
|
||||
updater = Updater(TOKEN, use_context=True)
|
||||
dp = updater.dispatcher
|
||||
|
||||
updater.start_polling()
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
Great, now lets write a function to respond to a simple `/ping` message.
|
||||
|
||||
```
|
||||
// ...
|
||||
|
||||
def pong(update, context):
|
||||
update.message.reply_text('Pong')
|
||||
|
||||
|
||||
def main():
|
||||
updater = Updater(TOKEN, use_context=True)
|
||||
dp = updater.dispatcher
|
||||
|
||||
dp.add_handler(CommandHandler('ping', pong))
|
||||
|
||||
updater.start_polling()
|
||||
updater.idle()
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
When we now send `/ping` to the bot we get a _Pong_ back. Neat!
|
||||
|
||||
<figure>
|
||||
|
||||

|
||||
|
||||
<figcaption>
|
||||
|
||||
result of pong message
|
||||
|
||||
</figcaption>
|
||||
|
||||
</figure>
|
||||
|
||||
#### But we want doggos! 🐶
|
||||
|
||||
```python
|
||||
def get_image_url():
|
||||
allowed_extension = ['jpg', 'jpeg', 'png']
|
||||
while True:
|
||||
url = requests.get('https://random.dog/woof.json').json()['url']
|
||||
file_extension = re.search("([^.]*)$", url).group(1).lower()
|
||||
if file_extension in allowed_extension:
|
||||
break
|
||||
return url
|
||||
|
||||
|
||||
def woof(update, context):
|
||||
update.message.reply_photo(photo=get_image_url())
|
||||
|
||||
|
||||
def main():
|
||||
//...
|
||||
|
||||
dp.add_handler(CommandHandler('ping', pong))
|
||||
dp.add_handler(CommandHandler('woof', woof))
|
||||
|
||||
//...
|
||||
```
|
||||
|
||||
We define a second command that listens to the `woof` word. Then we have a little helper function that retrieves a url. That url is then passed to the `reply_photo` method and we are done!
|
||||
|
||||
We could also pass a [bite-like](https://docs.python.org/3/glossary.html#term-bytes-like-object) object to the `photo` parameter instead of a string containing the url if we would have an actual photo.
|
||||
|
||||
<figure>
|
||||
|
||||

|
||||
|
||||
<figcaption>
|
||||
|
||||
reply to woof
|
||||
|
||||
</figcaption>
|
||||
|
||||
</figure>
|
||||
|
||||
## Persistence Bot
|
||||
|
||||
The doggo bot is a good hello world example, but without persistence a lot of bots would not work. If you want to save something you will need to have some kind of **user base storage**.
|
||||
|
||||
For this we will implement a little bot that saved a list of items that a user can add/delete. A bit like a shopping list if you want.
|
||||
|
||||
#### Commands
|
||||
|
||||
- Add `/add avocados`
|
||||
- Delete `/delete avocados`
|
||||
- List all `/all`
|
||||
- Delete all `/clear`
|
||||
|
||||
First the basic setup again. We will import `PicklePersistence` which uses the python _pickle_ object to save the data on your drive. We need to initialise it and pass it to the `Updater` class.
|
||||
|
||||
```python
|
||||
from telegram.ext import Updater, CommandHandler, CallbackContext, PicklePersistence
|
||||
from telegram import Update
|
||||
|
||||
TOKEN = 'myTelegramBotToken'
|
||||
|
||||
persistence = PicklePersistence('./db')
|
||||
|
||||
|
||||
def main():
|
||||
print('Started 🚀')
|
||||
updater = Updater(TOKEN, use_context=True, persistence=persistence)
|
||||
dp = updater.dispatcher
|
||||
|
||||
updater.start_polling()
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
Before we beginn lets define 2 helper functions:
|
||||
|
||||
```python
|
||||
def get_list(context: CallbackContext) -> dict:
|
||||
return context.user_data.setdefault('list', {})
|
||||
```
|
||||
|
||||
`get_list` will return the object that is unique to every user. The python library generously offers a `user_data` object inside of the `context` parameter that gets passed to each handler. We use `setdefault` in order to get the already present dict of the user or set it to an empty one if undefined.
|
||||
|
||||
```python
|
||||
def parse_command(update: Update) -> (str, str):
|
||||
key, value = update.message.text.split(' ', 1)
|
||||
return key, value
|
||||
```
|
||||
|
||||
`parse_command` simply helps us interpret the message of the user. If a user sends us `/add avocado` we only want the _avocado_ part. We simply split at the first space.
|
||||
|
||||
Lets finally add all the methods.
|
||||
|
||||
```python
|
||||
//...
|
||||
|
||||
def get_list(context: CallbackContext) -> list:
|
||||
return context.user_data.setdefault('list', [])
|
||||
|
||||
|
||||
def parse_command(update: Update) -> (str, str):
|
||||
key, value = update.message.text.split(' ', 1)
|
||||
return key, value
|
||||
|
||||
|
||||
def list_add(update: Update, context: CallbackContext):
|
||||
key, value = parse_command(update)
|
||||
get_list(context).append(value)
|
||||
update.message.reply_text('Saved 💾')
|
||||
|
||||
|
||||
def list_delete(update: Update, context: CallbackContext):
|
||||
key, value = parse_command(update)
|
||||
get_list(context).remove(value)
|
||||
update.message.reply_text('Deleted 🗑')
|
||||
|
||||
|
||||
def list_all(update: Update, context: CallbackContext):
|
||||
items = get_list(context)
|
||||
update.message.reply_text('\n'.join(items) if len(items) > 0 else 'List empty 😢')
|
||||
|
||||
|
||||
def list_clear(update: Update, context: CallbackContext):
|
||||
get_list(context).clear()
|
||||
update.message.reply_text('Cleared 🧼')
|
||||
|
||||
|
||||
def main():
|
||||
//...
|
||||
|
||||
dp.add_handler(CommandHandler('add', list_add))
|
||||
dp.add_handler(CommandHandler('delete', list_delete))
|
||||
dp.add_handler(CommandHandler('all', list_all))
|
||||
dp.add_handler(CommandHandler('clear', list_clear))
|
||||
|
||||
//...
|
||||
```
|
||||
|
||||

|
||||
|
||||
I believe telegram bots can be a great alternative if a full fledged app is an overkill. There are a lot of featured that where not mentioned here, especially custom keyboards.
|
||||
|
||||
## Dockerize it
|
||||
|
||||
```
|
||||
# requirements.txt
|
||||
|
||||
python-telegram-bot==12.0.0b1
|
||||
```
|
||||
|
||||
```Dockerfile
|
||||
# Dockerfile
|
||||
|
||||
FROM python:3.7-slim
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt && rm -rf /root/.cache
|
||||
|
||||
COPY src .
|
||||
CMD ["python", "-u", "/app/Mercatus.py"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
|
||||
version: '3.6'
|
||||
|
||||
services:
|
||||
bot:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
command: ['python', '-u', '/app/Mercatus.py']
|
||||
volumes:
|
||||
- ./data.db:/app/data.db
|
||||
```
|
||||
|
||||
Thats it! I hope you enjoyed the guide. 👋
|
||||
Reference in New Issue
Block a user