0.1.0
This commit is contained in:
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
__pycache__
|
||||
.env
|
||||
.pytest_cache
|
||||
.DS_Store
|
||||
.venv
|
||||
.python-version
|
||||
uv.lock
|
||||
dist
|
||||
*.egg-info
|
||||
site
|
||||
36
pyproject.toml
Normal file
36
pyproject.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "quickbot-faststream"
|
||||
version = "0.1.0"
|
||||
description = "Enables telegram updates handling in faststream workers"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: OS Independent",
|
||||
"Development Status :: 3 - Alpha",
|
||||
]
|
||||
authors = [
|
||||
{ name = "Alexander Kalinovsky", email = "ak@botforge.biz" },
|
||||
]
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = [
|
||||
"quickbot>=0.1.1",
|
||||
"faststream[rabbit]>=0.5",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"build>=1.2.1",
|
||||
"twine>=5.0.0",
|
||||
"pytest>=8.0.0",
|
||||
"ruff>=0.5.0",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
|
||||
3
src/quickbot_faststream/__init__.py
Normal file
3
src/quickbot_faststream/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .main import FaststreamPlugin
|
||||
|
||||
__all__ = ["FaststreamPlugin"]
|
||||
12
src/quickbot_faststream/config.py
Normal file
12
src/quickbot_faststream/config.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Config(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env", env_ignore_empty=True, extra="ignore"
|
||||
)
|
||||
|
||||
RABBITMQ_URL: str
|
||||
|
||||
config = Config()
|
||||
86
src/quickbot_faststream/main.py
Normal file
86
src/quickbot_faststream/main.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from aiogram.types import Update
|
||||
from typing import Literal
|
||||
from fastapi import Depends, Request, Response
|
||||
from fastapi.datastructures import State
|
||||
from faststream.rabbit.fastapi import ContextRepo
|
||||
from faststream.rabbit import RabbitBroker
|
||||
from logging import getLogger
|
||||
from typing_extensions import Annotated
|
||||
from quickbot import QuickBot
|
||||
from quickbot.db import get_db
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from faststream.rabbit.fastapi import RabbitRouter
|
||||
|
||||
from .config import config
|
||||
from .utils import override_route
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
async def telegram_webhook(
|
||||
request: Request,
|
||||
):
|
||||
logger.debug("Webhook request %s", await request.json())
|
||||
app: QuickBot = request.app
|
||||
|
||||
if app.webhook_handler:
|
||||
return await app.webhook_handler(app=app, request=request)
|
||||
|
||||
request_token = request.headers.get("X-Telegram-Bot-Api-Secret-Token")
|
||||
if request_token != app.config.TELEGRAM_WEBHOOK_AUTH_KEY:
|
||||
logger.warning("Unauthorized request %s", request)
|
||||
return Response(status_code=403)
|
||||
try:
|
||||
update = await request.json()
|
||||
except Exception:
|
||||
logger.error("Invalid request", exc_info=True)
|
||||
return Response(status_code=400)
|
||||
|
||||
broker: RabbitBroker = request.state.broker
|
||||
|
||||
await broker.publish(
|
||||
message=update,
|
||||
queue=f"{app.config.STACK_NAME}.telegram_updates",
|
||||
)
|
||||
|
||||
return Response(status_code=200)
|
||||
|
||||
|
||||
class FaststreamPlugin:
|
||||
def __init__(
|
||||
self,
|
||||
) -> None:
|
||||
self.broker_router = None
|
||||
|
||||
def register(self, app: QuickBot) -> None:
|
||||
self.broker_router = RabbitRouter(
|
||||
url=config.RABBITMQ_URL
|
||||
)
|
||||
|
||||
@self.broker_router.subscriber(queue=f"{app.config.STACK_NAME}.telegram_updates", no_reply=True)
|
||||
async def telegram_updates_handler(
|
||||
message: dict,
|
||||
db_session: Annotated[AsyncSession, Depends(get_db)],
|
||||
context: ContextRepo,
|
||||
):
|
||||
state = State(
|
||||
{
|
||||
"broker": context.context["broker"],
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
update = Update(**message)
|
||||
await app.dp.feed_webhook_update(
|
||||
bot=app.bot,
|
||||
update=update,
|
||||
db_session=db_session,
|
||||
app=app,
|
||||
app_state=state,
|
||||
)
|
||||
except Exception:
|
||||
logger.error("Error processing update", exc_info=True)
|
||||
|
||||
app.include_router(self.broker_router)
|
||||
|
||||
override_route(app, name="telegram_webhook", new_endpoint=telegram_webhook)
|
||||
|
||||
42
src/quickbot_faststream/utils.py
Normal file
42
src/quickbot_faststream/utils.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import Callable, Optional
|
||||
from fastapi import FastAPI, APIRouter
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
def override_route(
|
||||
app: FastAPI,
|
||||
*,
|
||||
name: str,
|
||||
new_endpoint: Callable,
|
||||
):
|
||||
"""
|
||||
Replace the handler of a route with a new one, preserving all other route settings.
|
||||
"""
|
||||
found: Optional[APIRoute] = None
|
||||
for r in app.routes:
|
||||
if isinstance(r, APIRoute) and r.name == name:
|
||||
found = r
|
||||
break
|
||||
if not found:
|
||||
raise LookupError(f"Method {name} not found")
|
||||
|
||||
app.routes.remove(found)
|
||||
|
||||
app.add_api_route(
|
||||
found.path,
|
||||
new_endpoint,
|
||||
methods=list(found.methods),
|
||||
response_model=found.response_model,
|
||||
status_code=found.status_code,
|
||||
tags=found.tags,
|
||||
summary=found.summary,
|
||||
description=found.description,
|
||||
response_description=found.response_description,
|
||||
responses=found.responses,
|
||||
deprecated=found.deprecated,
|
||||
name=found.name,
|
||||
include_in_schema=found.include_in_schema,
|
||||
response_model_exclude_unset=found.response_model_exclude_unset,
|
||||
response_model_exclude_defaults=found.response_model_exclude_defaults,
|
||||
response_model_exclude_none=found.response_model_exclude_none,
|
||||
openapi_extra=found.openapi_extra,
|
||||
)
|
||||
Reference in New Issue
Block a user