Эта статья написана для ламеров
Я как обычно учился кодить, и вдруг заметил что телеграм выпустил веб апи и теперь там есть фронт...
На тот момент я уже владел React, Flask и Django, и решил повысить планку с помощью FastAPI.
Веря в свои силы, я установил FastAPI и aiogram. Очевидно, мне следовало бы использовать нативные методы Telegram API, но aiogram предоставляет множество удобных функций, которые ускоряют разработку.
Проблема
Django и Flask используют шаблонизаторы (я использовал Jinja2), что означает, что код, выполняемый в браузере (HTML, JS, CSS, TS и т. д.), отправляется непосредственно с сервера. Таким образом, у такого приложения нет отдельного сервера для фронтэнда.
однако Fast-api и React, нуждаются в отдельных серверах ...
Я понимаю, что найдутся люди, которые скажут: "Зачем использовать отдельный сервер для React, если можно добавить реактивность с помощью Knockout.js и создать веб-приложение на одном сервере?" - Да, это возможно, но мне кажется, что лучше использовать нормальные технологии, а не костыли на костылях, чтобы к моменту завершения проекта у вас был ценный опыт в резюме, а не повод для пенсии))
Я сталкивался с корпоративными проектами, написанными с использованием устаревшего Knockout,js и Python 2, все на одном монолите. Схема работает, к тому же можно нанимать джунов и платить им меньше, ведь всех обучают Django на курсах).
Решение
Вот у нас есть два сервера - FastAPI и React. Далее нам нужно их связать с Telegram API. Рассмотрим конкретный пример такой связи: разумеется, FastAPI и Telegram взаимодействуют через webhook. Чтобы их свести вместе, мы отправляем запрос Telegram на сервер, в котором указываем публичный адрес, полученный от ngrok, и токен бота из файла .env. В моем случае, я добавил адрес ngrok в .env, и FastAPI автоматически устанавливает связь с Telegram при запуске. Основная проблема заключается в том, что ngrok, даже в платной версии, поддерживает только один адрес, а у меня фронтэнд работает на отдельном порту. Однако здесь на помощь приходит localhost.run. Она имеет схожий функционал с ngrok, но настраивается чутка сложнее.
Вот план которого я бы придерживался
Запускаем localhost.run на порту 3000 и добавляем адрес в окружение бэка
запускаем React на порту 3000
Запускаем ngrok на порту 8000 и добавляем адрес в окружение
Запускаем Fast-api локально на порту 8000
План простой и надежный, как швейцарские часы.
1. localhost.run
Заходим на https://admin.localhost.run/ регаемся\логинимся.
Делаем shh ключ ориг. инструкция на английсском. Ключ состоит из 2х частей публичной и приватной. заходим в терминал и пишем
ssh-keygen -t rsa -b 2048 -C "<comment>"
*в комментарии рекомендуют указывать емейл
выбираем название ключа. и запоминаем путь. если не вводить имя то ключи будут сгенерированы с дефолтными именами id_rsa и id_rsa.pub
идем в директорию ключа, жмем ls -1 чтобы убедиться что пришли в нужное место и ключи готовы и ждут нас.
ls -1
id_rsa и id_rsa.pub это приватный и публичный ключи соответсвенно. Открываем id_rsa.pub с помощью vim или nano. Если нет vim то стаvim vim))
vim id_rsa.pub
копируем все что внутри(начиная с shh и до конца текста)
дальше выходим из вима для этого жмем “:” и печатаем q! и жмем ентыр.
идем в браузере на https://admin.localhost.run/#/keys вводим там этот ключ
дальше идем в консоль
ssh -R 80:localhost:3000 localhost.run
Что означет эта команда можнопочтитать в докуменции сервиса
Получаем наш белый адрес на порту 3000
добавляем полученный адрес в .env бэка
теперь настало время REACT
2. REACT
Итак чтобы чтобы обходиться без танцев, я сделяль виззард скрипт. Отмечу что он дает выбрать js/jsx, yarn/npm, и создает докер и докер компоуз с прокинутыми портами, а весь код вынесен в Volume поэтому проект не требует постоянной пересборки.
Итак, создаем create_react_compose_app.sh или скачиваем с github.
Делаем его исполняемым , на мак это делается вот так:
chmod +x create_react_compose_app.sh
Запустим скрипт:
bash create_react_copmpose_app.sh
Запускаем докер, после вводим в консоль
docker-compose up
И вот приложение открыто на порту 3000, теперь браузере идем по адресу который получили в localhost.run
Идем в
Открываем index.html и добавляем туда библиотеку телеграм
<script src="https://telegram.org/js/telegram-web-app.js"></script>
И заменим
<p> Edit <code>src/App.js</code> and save to reload.</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
На
<p> Hellow Habr. </p>
В src/App.js
проверяем по адресу который дал localhost.run
3. Ngrok
идем в браузере на https://ngrok.com/ Регаемся/логинимся
идем в https://dashboard.ngrok.com/get-started/setup
делам все по инструкции:
разархивировать zip
-
подставить ключ в конфиг, удобно что в документации сразу даются персональные команды с подставленным ключом
-
запускаем ngrok на порту 8000
ngrok http 8000
4. Fast-API
заходим в исходную папку
создадим папку Fast-API используя команду
mkdir Fast-API
перейдём в нее
cd Fast-API
создадим main.py и README.md, .env c помощью уже известной команды touch. Легко запомнить про неё есть песня у Daft Punk - Touch
создаем фаил и кладем туда адрес из localhost.run , ngrok , токенбота ...
touch .env
Создаем venv
Да, нормальные пацаны юзают Poetry, но этот текст исключительно для ламеров, если готовы по уму делать сразу то вперед, тут будет через venv.
python3 -m venv venv
активируем его
source venv/bin/activate
обновляем
pip install --upgrade pip
ставим uvicorn
pip install uvicorn
ставим Fast-api
pip install fastapi
ставим aiogram
pip install aiogram
ставим python-dotenv, это один из тех пакетов которые в pip называются не так как импортируются
pip install python-dotenv
при помощи уже известной команды touch создаем README.md
touch README.md
в README.md прописываем команду старта
uvicorn main:app --reload
открываем main.py
прописываем импорты и загружаем переменные из .env
from dotenv import load_dotenv
from fastapi import FastAPI
load_dotenv() # загружем .env в оперативку
TOKEN = os.environ["TELEGRAM_TOKEN"] # получаем занчения переменных
BACK_URL = os.environ["BACK_URL"]
REACT_URL = os.environ["REACT_URL"]
WEBHOOK_PATH = f"/bot/{TOKEN}" # формируем системные переменные из переменных окружения
WEBHOOK_URL = BACK_URL + WEBHOOK_PATH
создаем приложение и прописываем проверочную функцию
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello habr"}
запускаем сервер uvicorn с приложением app = FastAPI командой из README.md
uvicorn main:app --reload
Переходим на BACK_URL в браузере и проверяем, что все работает. Убедимся, что мы правильно установили и импортировали файл .env, и также правильно запустили FastAPI.
импортируем aiogram и создаем функции обработчики сообщений И так далее
@app.on_event("startup") #обработчик запуска приложения, засылает вебхук в телеграм
async def on_startup():
webhook_info = await bot.get_webhook_info()
if webhook_info.url != WEBHOOK_URL:
await bot.set_webhook(
url=WEBHOOK_URL
)
@app.post(WEBHOOK_PATH) #обработчик событий телгарма
async def bot_webhook(update: dict):
telegram_update = types.Update(**update)
Dispatcher.set_current(dp)
Bot.set_current(bot)
await dp.process_update(telegram_update)
добавляем обработчик команды старт в телеграм
@dp.message_handler(commands="start")
async def new_message(message: types.Message):
text = 'REACT'
keyboard = types.InlineKeyboardMarkup()
keyboard.add(types.InlineKeyboardButton('Launch react', web_app=WebAppInfo(url=REACT_URL)))
await bot.send_message(message.chat.id, text, reply_markup=keyboard)
@app.on_event("shutdown") #и обработчик закрытия сесии
async def on_shutdown():
await bot.get_session()
await bot.session.close()
logging.info("Bot stopped")
добавлю favicon.iso и вот полный код
import logging
import os
from aiogram import types, Dispatcher, Bot
from aiogram.types import WebAppInfo
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.responses import FileResponse
load_dotenv()
TOKEN = os.environ["TELEGRAM_TOKEN"]
BACK_URL = os.environ["BACK_URL"]
REACT_URL = os.environ["REACT_URL"]
WEBHOOK_PATH = f"/bot/{TOKEN}"
WEBHOOK_URL = BACK_URL + WEBHOOK_PATH
bot = Bot(token=TOKEN)
dp = Dispatcher(bot)
app = FastAPI()
favicon_path = 'favicon.ico'
@app.get('/favicon.ico', include_in_schema=False)
async def favicon():
return FileResponse(favicon_path)
@app.get("/")
async def root():
return {"message": "Hello habr"}
@app.on_event("startup")
async def on_startup():
webhook_info = await bot.get_webhook_info()
if webhook_info.url != WEBHOOK_URL:
await bot.set_webhook(
url=WEBHOOK_URL
)
@app.post(WEBHOOK_PATH)
async def bot_webhook(update: dict):
telegram_update = types.Update(**update)
Dispatcher.set_current(dp)
Bot.set_current(bot)
await dp.process_update(telegram_update)
@app.on_event("shutdown")
async def on_shutdown():
await bot.get_session()
await bot.session.close()
logging.info("Bot stopped")
@dp.message_handler(commands="start")
async def new_message(message: types.Message):
text = 'REACT'
keyboard = types.InlineKeyboardMarkup()
keyboard.add(types.InlineKeyboardButton('Launch react', web_app=WebAppInfo(url=REACT_URL)))
await bot.send_message(message.chat.id, text, reply_markup=keyboard)
@app.post(WEBHOOK_PATH)
async def bot_webhook(update: dict):
telegram_update = types.Update(**update)
Dispatcher.set_current(dp)
Bot.set_current(bot)
await dp.process_update(telegram_update)
сохраняем, запускаем, проверяем
В данной статье я описал настройку веб-приложения в контексте #Telegram и #Web_Apps for Bots. При желании, можно также включить использование FAST-API в контейнере с помощью #poetry, но это уже не для новичков. Кроме того, мы также коснулись вопросов #ssh ключей, #react, #python, #fast-api, #docker, #docker-compose, #ngrok, #localhost.run, #venv, #uvicorn и использовали #shell-script.
Expany
*англиЙской
*ls -l
Дальше читать не стал
palyaros02
ключь ещё.
dopusteam
англисском
Hellow (Хотя может в англисском так и должно быть)
Передем
Легко запомнить про не есть песня у Daft Punk - Touch
также верное запустили FastAPI
добавляем обработчик команды старь
А ещё практически полное отсутствие пунктуации
dan_daniels Автор
спасибо, исправил
dan_daniels Автор
ls -1 это верная команда