├── requirements.txt ├── fs └── images │ └── webhook_vs_polling_timeline.png ├── env ├── echo.py ├── README.md ├── bot ├── filters.py ├── misc.py ├── __main__.py ├── handlers │ ├── commands.py │ └── messages.py └── config.py ├── tutorials └── ru │ ├── 03_proxy.md │ ├── 05_formatting.md │ ├── 02_project.md │ ├── 01_echo.md │ ├── 00_welcome.md │ ├── 06_webhook.md │ └── 04_filters.md └── .gitignore /requirements.txt: -------------------------------------------------------------------------------- 1 | aiogram<3 2 | uvloop 3 | ujson 4 | python-dotenv 5 | aiohttp_socks -------------------------------------------------------------------------------- /fs/images/webhook_vs_polling_timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arwichok/tgBotTutorial/HEAD/fs/images/webhook_vs_polling_timeline.png -------------------------------------------------------------------------------- /env: -------------------------------------------------------------------------------- 1 | BOT_TOKEN= 2 | SKIP_UPDATES=True 3 | BOT_OWNER=0 4 | 5 | PROXY_URL= 6 | PROXY_PASS= 7 | PROXY_LOGIN= 8 | 9 | USE_WEBHOOK=False 10 | WEBHOOK_HOST= 11 | WEBHOOK_PORT=8443 12 | WEBHOOK_PATH= 13 | APP_PORT= 14 | APP_HOST=localhost 15 | SSL_CERT= 16 | SSL_KEY= 17 | -------------------------------------------------------------------------------- /echo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from aiogram import Bot, Dispatcher, executor 3 | 4 | BOT_TOKEN = '' 5 | logging.basicConfig(level=logging.INFO) 6 | 7 | bot = Bot(BOT_TOKEN, proxy='') 8 | dp = Dispatcher(bot) 9 | 10 | 11 | @dp.message_handler() 12 | async def echo(msg): 13 | await msg.answer(msg.text) 14 | 15 | 16 | executor.start_polling(dp, skip_updates=True) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Bot Tutorial on python with aiogram 2 | 3 | 4 | ## Ru Tutorial 5 | - [Welcome](/tutorials/ru/00_welcome.md) 6 | - [Echo](/tutorials/ru/01_echo.md) 7 | - [Project](/tutorials/ru/02_project.md) 8 | - [Proxy](/tutorials/ru/03_proxy.md) 9 | - [Filters](/tutorials/ru/04_filters.md) 10 | - [Formatting](/tutorials/ru/05_formatting.md) 11 | - [Webhook](/tutorials/ru/06_webhook.md) -------------------------------------------------------------------------------- /bot/filters.py: -------------------------------------------------------------------------------- 1 | from aiogram.dispatcher.filters import BoundFilter 2 | from aiogram import types as ats 3 | 4 | from misc import dp 5 | 6 | 7 | class IsReply(BoundFilter): 8 | key = 'is_reply' 9 | 10 | def __init__(self, is_reply): 11 | self.is_reply = is_reply 12 | 13 | async def check(self, msg: ats.Message): 14 | if msg.reply_to_message: 15 | return {'reply': msg.reply_to_message} 16 | 17 | 18 | dp.filters_factory.bind(IsReply) 19 | -------------------------------------------------------------------------------- /bot/misc.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from aiogram import Bot, Dispatcher 4 | from aiogram.utils.executor import Executor 5 | from aiogram.types import ParseMode as pm 6 | 7 | from config import BOT_TOKEN, PROXY_AUTH, PROXY_URL, SKIP_UPDATES 8 | 9 | logging.basicConfig(level=logging.INFO) 10 | 11 | bot = Bot( 12 | token=BOT_TOKEN, 13 | proxy=PROXY_URL, 14 | proxy_auth=PROXY_AUTH, 15 | parse_mode=pm.HTML 16 | ) 17 | dp = Dispatcher(bot) 18 | executor = Executor(dp, skip_updates=SKIP_UPDATES) 19 | -------------------------------------------------------------------------------- /tutorials/ru/03_proxy.md: -------------------------------------------------------------------------------- 1 | # Настраиваем Прокси 2 | 3 | По дефолту мы можем использувать `http` прокси. Что бы использовать `socks`-прокси нужно установить `aiohttp_socks` 4 | 5 | Если требуется использовать логин и пароль для прокси добавим авторизацию в код 6 | 7 | #### bot/config.py 8 | ```py 9 | from aiohttp import BasicAuth 10 | 11 | ... 12 | 13 | PROXY_PASS = getenv('PROXY_PASS', '') 14 | PROXY_LOGIN = getenv('PROXY_LOGIN', '') 15 | PROXY_AUTH = BasicAuth(PROXY_LOGIN, PROXY_PASS) 16 | ``` 17 | 18 | #### bot/misc.py 19 | ```py 20 | ... 21 | 22 | from config import BOT_TOKEN, PROXY_AUTH, PROXY_URL, ... 23 | 24 | ... 25 | bot = Bot( 26 | token=BOT_TOKEN, 27 | proxy=PROXY_URL, 28 | proxy_auth=PROXY_AUTH 29 | ) 30 | ... 31 | ``` 32 | 33 | [Предыдущая часть](02_project.md) | [Следующая часть](04_filters.md) -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Dispatcher 2 | 3 | from misc import executor 4 | import filters 5 | from handlers import ( 6 | messages, 7 | commands, 8 | ) 9 | from config import ( 10 | USE_WEBHOOK, 11 | WEBHOOK_SERVER, 12 | WEBHOOK_URL, 13 | SSL_CERT 14 | ) 15 | 16 | 17 | async def rm_webhook(dp: Dispatcher): 18 | await dp.bot.delete_webhook() 19 | 20 | 21 | async def set_webhook(dp: Dispatcher): 22 | cert = open(SSL_CERT, 'rb') if SSL_CERT else None 23 | await dp.bot.set_webhook(WEBHOOK_URL, certificate=cert) 24 | 25 | 26 | executor.on_startup(set_webhook, polling=False) 27 | executor.on_startup(rm_webhook, webhook=False) 28 | executor.on_shutdown(rm_webhook, polling=False) 29 | 30 | if USE_WEBHOOK: 31 | executor.start_webhook(**WEBHOOK_SERVER) 32 | else: 33 | executor.start_polling() 34 | -------------------------------------------------------------------------------- /bot/handlers/commands.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from aiogram import types as ats, filters 4 | 5 | from misc import dp 6 | 7 | 8 | @dp.message_handler(commands=['help']) 9 | async def cmd_help(msg: ats.Message): 10 | await msg.answer('Hello, i MyEverBestBot') 11 | 12 | 13 | @dp.message_handler(filters.CommandPrivacy()) 14 | async def cmd_privacy(msg: ats.Message): 15 | await msg.answer('Privacy') 16 | 17 | @dp.message_handler(commands=['test'], commands_prefix='/!') 18 | async def cmd_test(msg: ats.Message): 19 | await msg.answer('Test') 20 | 21 | 22 | @dp.message_handler(regexp_commands=[r'r_(\d+)']) 23 | async def cmd_r(msg: ats.Message, regexp_command): 24 | await msg.answer(f'ref is {regexp_command[1]}') 25 | 26 | 27 | @dp.message_handler( 28 | filters.CommandStart(re.compile(r'u(\d+)'))) 29 | async def cmd_start(msg: ats.Message, deep_link): 30 | await msg.answer(f'Deep link u is {deep_link[1]}') 31 | 32 | 33 | @dp.message_handler(text='some') 34 | @dp.message_handler(commands=['some']) 35 | async def some_start(msg: ats.Message): 36 | await msg.answer('Hello from some') 37 | -------------------------------------------------------------------------------- /bot/config.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | from os import getenv 3 | 4 | from dotenv import load_dotenv 5 | from aiohttp import BasicAuth 6 | 7 | 8 | load_dotenv() 9 | 10 | BOT_TOKEN = getenv('BOT_TOKEN') 11 | SKIP_UPDATES = bool(getenv('SKIP_UPDATES') or True) 12 | BOT_OWNER = int(getenv('BOT_OWNER') or 0) 13 | 14 | PROXY_URL = getenv('PROXY_URL') 15 | PROXY_PASS = getenv('PROXY_PASS', '') 16 | PROXY_LOGIN = getenv('PROXY_LOGIN', '') 17 | PROXY_AUTH = BasicAuth(PROXY_LOGIN, PROXY_PASS) 18 | 19 | USE_WEBHOOK = bool(getenv('USE_WEBHOOK') or False) 20 | WEBHOOK_HOST = getenv('WEBHOOK_HOST') 21 | WEBHOOK_PORT = int(getenv('WEBHOOK_PORT') or 8443) 22 | WEBHOOK_PATH = getenv('WEBHOOK_PATH') 23 | APP_PORT = int(getenv('APP_PORT') or WEBHOOK_PORT) 24 | APP_HOST = getenv('APP_HOST', 'localhost') 25 | SSL_CERT = getenv('SSL_CERT') 26 | SSL_KEY = getenv('SSL_KEY') 27 | 28 | WEBHOOK_URL = f'https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_PATH}' 29 | WEBHOOK_SERVER = { 30 | 'host': APP_HOST, 31 | 'port': APP_PORT, 32 | 'webhook_path': WEBHOOK_PATH, 33 | } 34 | if SSL_CERT and SSL_KEY: 35 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) 36 | context.load_cert_chain(SSL_CERT, SSL_KEY) 37 | WEBHOOK_SERVER['ssl_context'] = context 38 | -------------------------------------------------------------------------------- /bot/handlers/messages.py: -------------------------------------------------------------------------------- 1 | from aiogram import types as ats 2 | import aiogram.utils.markdown as md 3 | 4 | from misc import dp 5 | 6 | 7 | @dp.message_handler(text='1') 8 | async def one(msg: ats.Message): 9 | await msg.answer('You send 1') 10 | 11 | 12 | @dp.message_handler(text_startswith='t') 13 | async def two(msg: ats.Message): 14 | await msg.answer(f'Text startswith: t | {msg.text}') 15 | 16 | 17 | @dp.message_handler(text='admin', user_id=(329398814,)) 18 | async def user_id_test(msg: ats): 19 | await msg.answer('hello 329398814') 20 | 21 | 22 | @dp.message_handler(regexp='(^cat|puss)') 23 | async def regexp_test(msg: ats.Message, regexp): 24 | await msg.answer(f'Your cat\'s is {regexp[0]}') 25 | 26 | 27 | @dp.message_handler(hashtags=['lom']) 28 | async def hashtag_test(msg: ats.Message): 29 | await msg.answer('Hashtag Lom') 30 | 31 | 32 | @dp.message_handler(text='md') 33 | async def md_test(msg: ats.Message): 34 | username = msg.from_user.username 35 | user_id = msg.from_user.id 36 | chat_id = msg.chat.id 37 | 38 | await msg.answer( 39 | f'Hello {md.hbold(username)}\n' 40 | f'Your id: {md.hcode(user_id)}\n' 41 | f'Chat id: {md.hcode(chat_id)}\n' 42 | 43 | f'''User mention {md.hlink( 44 | username, 45 | f'tg://user?id={user_id}' 46 | )}''' 47 | ) 48 | 49 | 50 | @dp.message_handler(is_reply=True) 51 | async def test_is_reply(msg: ats.Message, reply): 52 | await msg.answer(f'Reply to {reply.text}') 53 | -------------------------------------------------------------------------------- /tutorials/ru/05_formatting.md: -------------------------------------------------------------------------------- 1 | # Formatting 2 | 3 | # Contents 4 | 5 | - [Formatting](#formatting) 6 | - [Contents](#contents) 7 | - [Bot Formatting](#bot-formatting) 8 | 9 | BotsApi позволяет форматировать текст в разные стили, бибилиотека добавляет свои методы для прощего форматирования 10 | 11 | # Bot Formatting 12 | 13 | ```py 14 | ... 15 | from aiogram.utils.markdown as md 16 | from aiogram.types import ParseMode as pm 17 | ... 18 | 19 | bot = Bot(BOT_TOKEN, parse_mode=pm.HTML) 20 | ... 21 | 22 | @dp.message_handler(text='md') 23 | async def md_test(msg: ats.Message): 24 | username = msg.from_user.username 25 | user_id = msg.from_user.id 26 | chat_id = msg.chat.id 27 | 28 | await msg.answer( 29 | f'Hello {md.hbold(username)}\n' 30 | f'Your id: {md.hcode(user_id)}\n' 31 | f'Chat id: {md.hcode(chat_id)}\n' 32 | f'''User mention {md.hlink( 33 | username, 34 | f'tg://user?id={user_id}' 35 | )}''' 36 | ) 37 | ``` 38 | 39 | | Type | Markdown | HTML | Example | 40 | |-|-|-|-| 41 | |**Bold** |`bold` |`hbold` |`('Bold')`| 42 | |_Italic_ |`italic`|`hitalic`|`('Italic')`| 43 | |[URL](http://t.me)|`link` |`hlink` |`('URL', 'http://t.me')`| 44 | |Mention |`link` |`hlink` |`('Username', 'tg://user?=12345')`| 45 | |`Inline-code` |`pre` |`hpre` |`('Inline-code')`| 46 | |```Code``` |`code` |`hcode` |`('Code')` | 47 | 48 | 49 | 50 | [Предыдущая часть](04_filters.md) | [Следующая часть](06_webhook.md) -------------------------------------------------------------------------------- /tutorials/ru/02_project.md: -------------------------------------------------------------------------------- 1 | # Создаём проект 2 | 3 | По мере создания бота нам прийдется разделить код на части, что-бы не тулить всё в один файл, в этой части я покажу пример, а делать вам так же или нет это ваш выбор. 4 | 5 | Здесь мы используем библиотеку `dotenv` 6 | поэтому скачаем и добавим её в `requirements.txt` 7 | 8 | ### Файловая структура проекта 9 | ```bash 10 | bot/ 11 | hanlders/ 12 | messages.py 13 | __main__.py 14 | misc.py 15 | config.py 16 | .gitignore 17 | .env 18 | env 19 | venv/ 20 | requirements.txt 21 | ``` 22 | 23 | #### `.env` and `env`: 24 | ```txt 25 | BOT_TOKEN= 26 | SKIP_UPDATES=True 27 | 28 | PROXY_URL=http://ip:port 29 | ``` 30 | в `.env` введите настоящие данные 31 | 32 | #### `bot/config.py` 33 | ```py 34 | from os import getenv 35 | 36 | import dotenv 37 | 38 | 39 | dotenv.load_dotenv() 40 | 41 | BOT_TOKEN = getenv('BOT_TOKEN') 42 | SKIP_UPDATES = bool(getenv('SKIP_UPDATES') or True) 43 | 44 | PROXY_URL = getenv('PROXY_URL') 45 | ``` 46 | 47 | #### `bot/misc.py` 48 | ```py 49 | import logging 50 | 51 | from aiogram import Bot, Dispatcher 52 | from aiogram.executor import Executor 53 | 54 | from config import BOT_TOKEN, PROXY_URL, SKIP_UPDATES 55 | 56 | 57 | logging.basicConfig(level=logging.INFO) 58 | 59 | bot = Bot(token=BOT_TOKEN, proxy=PROXY_URL) 60 | dp = Dispatcher(bot) 61 | executor = Executor(dp, skip_updates=SKIP_UPDATES) 62 | ``` 63 | 64 | #### `bot/handlers/messages.py` 65 | ```py 66 | from misc import dp 67 | 68 | 69 | @dp.message_handler() 70 | async def echo(msg): 71 | await msg.answer(msg.text) 72 | ``` 73 | 74 | 75 | #### `bot/__main__.py` 76 | ```py 77 | from misc import executor 78 | from handlers import ( 79 | messages 80 | ) 81 | 82 | 83 | executor.start_polling() 84 | ``` 85 | 86 | ## Запуск бота 87 | 88 | $ python bot 89 | 90 | 91 | 92 | [Предыдущая часть](01_echo.md) | [Следующая часть](03_proxy.md) -------------------------------------------------------------------------------- /tutorials/ru/01_echo.md: -------------------------------------------------------------------------------- 1 | # Echobot 2 | 3 | В этом розделе мы создадим элементарного эхо бота. создадим файл `echo.py` и запишем туда этот код: 4 | 5 | #### `echo.py` 6 | ```py 7 | import logging 8 | from aiogram import Bot, Dispatcher, executor 9 | 10 | BOT_TOKEN = '' 11 | logging.basicConfig(level=logging.INFO) 12 | 13 | bot = Bot(BOT_TOKEN) 14 | dp = Dispatcher(bot) 15 | 16 | 17 | @dp.message_handler() 18 | async def echo(msg): 19 | await msg.answer(msg.text) 20 | 21 | 22 | executor.start_polling(dp, skip_updates=True) 23 | ``` 24 | 25 | В переменную BOT_TOKEN вставим токен бота. 26 | 27 | > Если у вас заблокирован https://api.telegram.org/ воспользуйтесь прокси добавив его в вызов `Bot(BOT_TOKEN, proxy='http://address:port')`, подробней будет далее в туториале 28 | 29 | 30 | ### Запускаем бота 31 | (venv) $ python echo.py 32 | INFO:aiogram:Bot: MyEverBestBot [@MyEverBestBot] 33 | WARNING:aiogram:Updates were skipped successfully. 34 | INFO:aiogram.dispatcher.dispatcher:Start polling. 35 | 36 | Что бы остановить бота нажимаем клавиши `Ctrl+C` 37 | 38 | ^CINFO:aiogram.dispatcher.dispatcher:Stop polling... 39 | WARNING:aiogram:Goodbye! 40 | 41 | 42 | ### Что здесь происходит 43 | 44 | 45 | Устанавливаем уровень логирования, что-бы видеть что происходит в программе через консоль: 46 | 47 | ```py 48 | logging.basicConfig(level=logging.INFO) 49 | ``` 50 | 51 | Создаём обьекты бота и дисптачера, один для управления ботом, другой для работы с обновлениям: 52 | ```py 53 | bot = Bot(BOT_TOKEN) 54 | dp = Dispatcher(bot) 55 | ``` 56 | 57 | Регистрируем функцию `echo` что бы она срабатывала на все сообщения. 58 | ```py 59 | @dp.message_handler() 60 | async def echo(msg): 61 | ``` 62 | 63 | Зарегистрировать можно без декоратора: 64 | ```py 65 | dp.register_message_handler(echo) 66 | ``` 67 | 68 | Функция отвечает тому пользователю кто написал, тот же текст: 69 | ```py 70 | await msg.answer(msg.text) 71 | ``` 72 | 73 | Запускаем бота с пропуском всех сообщений что были до запуска: 74 | 75 | ```py 76 | executor.start_polling(dp, skip_updates=True) 77 | ``` 78 | 79 | [Предыдущая часть](00_welcome.md) | [Следующая часть](02_project.md) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python 3 | # Edit at https://www.gitignore.io/?templates=python 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | pip-wheel-metadata/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # celery beat schedule file 99 | celerybeat-schedule 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # End of https://www.gitignore.io/api/python 132 | 133 | .vscode/ -------------------------------------------------------------------------------- /tutorials/ru/00_welcome.md: -------------------------------------------------------------------------------- 1 | # Начало 2 | 3 | В этом туториале мы научимся делать бота на библиотеке Aiogram в Python 3 4 | 5 | ---------------- 6 | #### Установка Python 7 | 8 | Для работы с библиотекой aiogram требуется наличие Python 3.7, если версия младше или вообще нету нужно установить отсносительно вашей ОС, скачать можете на сайте python https://www.python.org/downloads/ 9 | 10 | Проверить версию можно через команду в терминале `python3 -V`, если python 3 один тогда `python -V` 11 | 12 | $ python3 13 | Python 3.7.4 14 | 15 | #### Создадим папку проекта 16 | 17 | $ mkdir project 18 | $ cd project 19 | 20 | #### Виртуальная среда 21 | 22 | Для работы с пакетамы питона поставим виртуальную среду: 23 | 24 | $ python -m venv venv 25 | 26 | и запустим: 27 | 28 | $ source venv/bin/activate 29 | (venv) $ _ 30 | 31 | #### Скачиваем python .gitignore 32 | 33 | Если будете работать с `git` скачаем .gitignore 34 | [отсюда](https://www.gitignore.io/api/python) и сохраним как `.gitignore` 35 | 36 | Или можете через терминал 37 | 38 | $ wget -O .gitignore https://www.gitignore.io/api/python 39 | 40 | 41 | #### Устананавливаем aiogram, uvloop, ujson 42 | 43 | Установим **aiogram**: 44 | 45 | (venv) $ pip install -U aiogram 46 | 47 | Либа часто обновляется поэтому можете установить прямо с гита dev версию: 48 | 49 | (venv) $ pip install git+https://github.com/aiogram/aiogram.git 50 | 51 | Автор рекомендует для скорости работы установить `uvloop` и `ujson`: 52 | 53 | (venv) $ pip install -U uvloop ujson 54 | 55 | 56 | Создадим и запишемм в файл `requirements.txt`: 57 | ``` 58 | aiogram<3 59 | uvloop 60 | ujson 61 | ``` 62 | 63 | #### Создаём бота в [@BotFather](https://t.me/BotFather) 64 | 65 | Что-бы создать токен бота нам понадобится обратится к боту [@BotFather](https://t.me/BotFather): 66 | 67 | Будем отправлять боту такие сообщения по очереди: 68 | 1. Комманду **/newbot** 69 | 2. Название бота 70 | 3. Домен бота который должен заканчиватся на `bot` 71 | 72 | Мы получим токен бота 73 | 74 | Пример: `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11` 75 | 76 | Желательно никому не передавать этот токен, если уж произошла утечка то его можно изменить в настройках [@BotFather](https://t.me/BotFather) 77 | 78 | Написать /mybots 79 | 80 | `@mybot > API Token > Revoke current token` 81 | 82 | #### Ссылки 83 | 84 | Подробней почитать о создании ботов (en) https://core.telegram.org/bots/api 85 | 86 | ~~[Документация по aiogram](https://aiogram.readthedocs.io/en/latest/)~~ ) 87 | 88 | Я в телеграмме [@Arwichok](https://t.me/arwichok) 89 | 90 | Русский чат по aiogram [@aiogram_ru](https://t.me/aiogram_ru) 91 | 92 | 93 | [Следующая часть](01_echo.md) -------------------------------------------------------------------------------- /tutorials/ru/06_webhook.md: -------------------------------------------------------------------------------- 1 | # Ставим Вебхуки 2 | 3 | 4 | # Polling vs Webhook 5 | 6 | ![Polling vs Webhook](../../fs/images/webhook_vs_polling_timeline.png) 7 | 8 | ## Polling 9 | Поллинг - это стиль получения обновлений, с помощью запросов к сайту с длинной выдержкой 10 | 11 | ## Webhook 12 | Блягодаря вебхуку, можно просто отправить Telegram ссылку куда присылать обновления, а мы создаём сервер и получаем их. 13 | 14 | 15 | # Создаём сертификат 16 | 17 | `mkdir ../ssl` 18 | 19 | `openssl req -newkey rsa:2048 -sha256 -nodes -keyout ../ssl/private.key -x509 -days 365 -out ../ssl/public.pem -subj "/C=US/ST=New York/L=Brooklyn/O=Example Brooklyn Company/CN=YOURDOMAIN.EXAMPLE"` 20 | 21 | Вместо `YOURDOMAIN.EXAMPLE` вставте ваш ip 22 | 23 | # Пишем код 24 | 25 | ```py 26 | # config.py 27 | import ssl 28 | ... 29 | USE_WEBHOOK = bool(g('USE_WEBHOOK') or False) 30 | WEBHOOK_HOST = g('WEBHOOK_HOST') 31 | WEBHOOK_PORT = int(g('WEBHOOK_PORT') or 8443) 32 | WEBHOOK_PATH = g('WEBHOOK_PATH') 33 | APP_PORT = int(g('APP_PORT') or WEBHOOK_PORT) 34 | APP_HOST = g('APP_HOST', 'localhost') 35 | SSL_CERT = g('SSL_CERT') 36 | SSL_KEY = g('SSL_KEY') 37 | 38 | WEBHOOK_URL = f'https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_PATH}' 39 | WEBHOOK_SERVER = { 40 | 'host': APP_HOST, 41 | 'port': APP_PORT, 42 | 'webhook_path': WEBHOOK_PATH, 43 | } 44 | if SSL_CERT and SSL_KEY: 45 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) 46 | context.load_cert_chain(SSL_CERT, SSL_KEY) 47 | WEBHOOK_SERVER['ssl_context'] = context 48 | ``` 49 | 50 | ```py 51 | # bot/__main__.py 52 | ... 53 | from config import ( 54 | USE_WEBHOOK, 55 | WEBHOOK_SERVER, 56 | WEBHOOK_URL, 57 | SSL_CERT 58 | ) 59 | 60 | 61 | async def rm_webhook(dp: Dispatcher): 62 | await dp.bot.delete_webhook() 63 | 64 | 65 | async def set_webhook(dp: Dispatcher): 66 | cert = open(SSL_CERT, 'rb') if SSL_CERT else None 67 | await dp.bot.set_webhook(WEBHOOK_URL, certificate=cert) 68 | 69 | 70 | executor.on_startup(set_webhook, polling=False) 71 | executor.on_startup(rm_webhook, webhook=False) 72 | executor.on_shutdown(rm_webhook, polling=False) 73 | 74 | if USE_WEBHOOK: 75 | executor.start_webhook(**WEBHOOK_SERVER) 76 | else: 77 | executor.start_polling() 78 | ``` 79 | 80 | 81 | # Config SelfSign Cert on IP 82 | 83 | Настройки для запуска вебхука 84 | 85 | Плюсы: 86 | - Не требуются дополнительные зависимости 87 | 88 | Минусы: 89 | - Можно запустить только 4 бота 90 | - Отбирает свободный порт 91 | - Для портов 80, 443 требуются права рута 92 | 93 | ```py 94 | # .env 95 | ... 96 | WEBHOOK_HOST= 97 | WEBHOOK_PORT=8443 98 | WEBHOOK_PATH=/ 99 | APP_HOST= 100 | SSL_CERT=../ssl/public.pem 101 | SSL_KEY=../ssl/private.key 102 | ``` 103 | 104 | # Config SelfSign Cert on IP with Nginx(Best) 105 | 106 | Плюсы: 107 | - Возможен запуск большого количества ботов 108 | - Не отбирает порт 109 | 110 | ``` 111 | # .env 112 | ... 113 | WEBHOOK_HOST= 114 | WEBHOOK_PORT=8443 115 | WEBHOOK_PATH=/tgwh/token 116 | APP_HOST=localhost 117 | APP_PORT=8600[-8750] 118 | SSL_CERT=../ssl/public.pem 119 | ``` 120 | 121 | ```nginx 122 | # nginx config 123 | 124 | server { 125 | listen 8443 ssl; 126 | 127 | ssl_certificate /path/to/ssl/public.pem; 128 | ssl_certificate_key /path/to/ssl/private.key; 129 | 130 | location /tgwh/token { 131 | proxy_pass http://localhost:8600; 132 | } 133 | } 134 | ``` 135 | 136 | # Public cert and domain with nginx 137 | 138 | Плюсы: 139 | - Относительно прост в настройке 140 | - Возможен запуск рядом с сайтом 141 | 142 | Минусы: 143 | - Требуются зависимости 144 | - Домен 145 | - Сертификат 146 | 147 | ``` 148 | # .env 149 | ... 150 | WEBHOOK_HOST= 151 | WEBHOOK_PORT=443 152 | WEBHOOK_PATH=/tgwh/token 153 | APP_HOST=localhost 154 | APP_PORT=8600[-8750] 155 | ``` 156 | 157 | ```nginx 158 | server { 159 | listen 443 ssl; 160 | 161 | location /tgwh/token { 162 | proxy_pass http://localhost:8600; 163 | } 164 | 165 | # cert configs 166 | # ... 167 | } 168 | ``` 169 | 170 | # Links 171 | 172 | О создании самописного сертификата: https://core.telegram.org/bots/self-signed 173 | 174 | Подробней о вебхуках: https://core.telegram.org/bots/webhooks 175 | 176 | Бесплатные домены: https://www.freenom.com/ 177 | 178 | Бесплатный сертификат: https://certbot.eff.org/ 179 | 180 | [Предыдущая часть](05_formatting.md) -------------------------------------------------------------------------------- /tutorials/ru/04_filters.md: -------------------------------------------------------------------------------- 1 | # Filters 2 | 3 | 4 | Фильтры помогают регистрировать функции на разные типы получаемых сообщений к примеру на текст и комманду отвечают разные функции. Здесь расмотрим встроенные фильтры и создадим свой. 5 | 6 | # Contents 7 | 8 | - [Filters](#filters) 9 | - [Contents](#contents) 10 | - [Text](#text) 11 | - [ID](#id) 12 | - [RegExp](#regexp) 13 | - [Tags](#tags) 14 | - [Commands](#commands) 15 | - [Command Classes](#command-classes) 16 | - [Commands prefix](#commands-prefix) 17 | - [RegExp Commands](#regexp-commands) 18 | - [DeepLink](#deeplink) 19 | - [Two registrators](#two-registrators) 20 | - [MyFilter](#myfilter) 21 | - [Castom](#castom) 22 | - [Factory](#factory) 23 | - [Pass data](#pass-data) 24 | - [Types builtin filters](#types-builtin-filters) 25 | 26 | 27 | 28 | 29 | >`echo` в коде мы удалим, так как функции проверяются по порядку очереди регистрации, если хендлер зарегистрирован без аргументов первым, то все сообщения будут отправляться в него, если последним, тогда будет на всё что не отвечает фильтрам в предыдущих. 30 | 31 | Так же для типизации будем использовать типы от либы. 32 | 33 | 34 | # Text 35 | ```py 36 | # bot/hanlders/messages.py 37 | from aiogram import types as ats 38 | 39 | from misc import dp 40 | 41 | 42 | @dp.message_handler(text='1') 43 | async def one(msg: ats.Message): 44 | await msg.answer('You send 1') 45 | 46 | 47 | @dp.message_handler(text_startswith='t') 48 | async def two(msg: ats.Message): 49 | await msg.answer(f'Text startswith: t | {msg.text}') 50 | ``` 51 | 52 | Для текста есть такие встроенные фильтры: 53 | - `text` - проверка на соответсвие текста 54 | - `text_startswith` - если текст начинается с такого отрезка 55 | - `text_endsiwith` - если текст оканчивается на это 56 | - `text_contains` - если в тексте присутсвует часть 57 | 58 | ## ID 59 | 60 | Для проверки id от получателя используем `user_id` и `chat_id`: 61 | 62 | ```py 63 | # config.py 64 | BOT_OWNER = int(getenv('BOT_OWNER') or 0) 65 | ``` 66 | 67 | ```py 68 | @dp.message_handler(text='admin', chat_id=BOT_OWNER)) 69 | async def user_id_test(msg: ats.Message): 70 | # Отправит сообщение только пользователю с id установленным в конфигах 71 | await msg.answer('Hello Admin') 72 | ``` 73 | 74 | ## RegExp 75 | Для проверки текста сообщения по регулярному выражению используется `regexp` 76 | 77 | ```py 78 | @dp.message_handler(regexp='(^cat|puss)') 79 | async def regexp_test(msg: ats.Message, regexp): 80 | await msg.answer(f'Your cat\'s is {regexp[0]}') 81 | ``` 82 | 83 | ## Tags 84 | 85 | `hashtags`, `cashtags` - позваляют получать уведомление на присутсвие тегов в тексте с префиксом `#`, `$` 86 | 87 | ```py 88 | @dp.message_handler(hashtags=['lom']) 89 | async def hashtag_test(msg: ats.Message): 90 | await msg.answer('Hashtag Lom') 91 | ``` 92 | 93 | 94 | # Commands 95 | 96 | Для команд у нас будет `commands.py`, создадим файл для этого и добавим в импорт `bot/__main__.py` 97 | 98 | Комманды будем писать в отдельном модуле `commands` 99 | Создадим `bot/handlers/commands.py` 100 | 101 | add `commands` 102 | 103 | ```py 104 | # `bot/__main__.py` 105 | from handlers import ( 106 | messages, 107 | commands 108 | ) 109 | ``` 110 | 111 | ```py 112 | @dp.message_handler(commands=['help']) 113 | async def start(msg: ats.Message): 114 | await msg.answer('Hello i TestBot') 115 | ``` 116 | 117 | ## Command Classes 118 | 119 | ```py 120 | from aiogram import filters 121 | ... 122 | @dp.message_handler(filters.CommandSettings()) 123 | async def start(msg: ats.Message): 124 | await msg.answer('Settings') 125 | ``` 126 | 127 | `start` == **CommandStart** 128 | `help` == **CommandHelp** 129 | `settings` == **CommandSettings** 130 | `privacy` == **CommandPrivacy** 131 | 132 | ## Commands prefix 133 | 134 | ```py 135 | @dp.message(commands=['test'], commands_prefix='/!') 136 | async def help(msg: ats.Message): 137 | # answer when message is /test or !test 138 | await msg.answer('Test') 139 | ``` 140 | 141 | ## RegExp Commands 142 | 143 | ```py 144 | @dp.message_handler(regexp_commands=[r'r_(\d+)']) 145 | async def r_(msg: ats.Message, regexp_command): 146 | await msg.answer(f'ref is {regexp_command[1]}') 147 | ``` 148 | 149 | ## DeepLink 150 | Теперь можно делать ссылки типа 151 | https://t.me/MyEverBestBot?start=u34 152 | 153 | ```py 154 | @dp.message_handler(filters.CommandStart(re.compile(r'u(\d+)'))) 155 | async def cmd_start(msg: ats.Message, deep_link): 156 | await msg.answer(f'Deep link u is {deep_link[1]}') 157 | ``` 158 | 159 | # Two registrators 160 | 161 | Регистрация двух взаимоисключающих фильтров на один хендлер 162 | 163 | ```py 164 | @dp.message_handler(text='some') 165 | @dp.message_handler(commands=['some']) 166 | async def some_start(msg: ats.Message): 167 | await msg.answer('Hello from some') 168 | ``` 169 | 170 | 171 | # MyFilter 172 | 173 | ## Castom 174 | ```py 175 | @dp.message_handler(lambda m: m.text == 'foo') 176 | ``` 177 | 178 | ## Factory 179 | 180 | 181 | Импортировать фильтры перед хендлерами 182 | ```py 183 | # bot/__main__.py 184 | ... 185 | import filters 186 | from handlers import( 187 | ... 188 | ``` 189 | 190 | ```py 191 | # bot/filters.py 192 | from aiogram.dispatcher.filters import BoundFilter 193 | ... 194 | 195 | class IsReply(BoundFilter): 196 | key = 'is_reply' 197 | 198 | def __init__(self, is_reply): 199 | self.is_reply = is_reply 200 | 201 | async def check(self, msg: ats.Message): 202 | if msg.reply_to_message: 203 | return {'reply': msg.reply_to_message} 204 | 205 | 206 | dp.filters_factory.bind(IsReply) 207 | ``` 208 | 209 | ```py 210 | # bot/handlers/message.py 211 | ... 212 | 213 | @dp.message_handler(is_reply=True) 214 | async def test_is_reply(msg: ats.Message, reply): 215 | await msg.answer(f'Reply to {reply.text}') 216 | ``` 217 | 218 | ## Pass data 219 | 220 | ```py 221 | async def my_filter(msg: ats.Message): 222 | # do something here 223 | return {'foo': 'bar'} 224 | 225 | 226 | @dp.message_handler(my_filter) 227 | async def test_my_filter(msg: ats.Message, foo): 228 | await msg.answer(f'foo is {foo}') 229 | ``` 230 | 231 | 232 | # Types builtin filters 233 | 234 | | Filter | Type | 235 | |-|-| 236 | | commands | list, str | 237 | | prefixes | list, str | 238 | | ignore_case | bool | 239 | | ignore_mention | bool | 240 | | text | list, str | 241 | | text_contains | list, str | 242 | | text_startswith | list, str | 243 | | text_endswith | list, str | 244 | | hashtag | list, str | 245 | | cashtag | list, str | 246 | | regexp | re.Pattern | 247 | | regexp_commands | re.Pattern | 248 | | content_types | list | 249 | | user_id | int, str, List[str, int] | 250 | | user_id | int, str, List[str, int] | 251 | | is_chat_admin | bool, int, str, List[int, str] | 252 | 253 | 254 | 255 | 256 | [Предыдущая часть](03_proxy.md) | [Следующая часть](05_formatting.md) --------------------------------------------------------------------------------