add files
This commit is contained in:
parent
4b06cd8a77
commit
8f2efee524
|
|
@ -81,7 +81,6 @@ async def platform_metadata():
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Legacy index and favicon
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def index_root():
|
async def index_root():
|
||||||
return PlainTextResponse("MY Network Node", status_code=200)
|
return PlainTextResponse("MY Network Node", status_code=200)
|
||||||
|
|
@ -91,7 +90,6 @@ async def favicon():
|
||||||
return PlainTextResponse("", status_code=204)
|
return PlainTextResponse("", status_code=204)
|
||||||
|
|
||||||
|
|
||||||
# Legacy node endpoints
|
|
||||||
@router.get("/api/v1/node")
|
@router.get("/api/v1/node")
|
||||||
async def v1_node():
|
async def v1_node():
|
||||||
from app.core.crypto import get_ed25519_manager
|
from app.core.crypto import get_ed25519_manager
|
||||||
|
|
@ -105,7 +103,6 @@ async def v1_node_friendly():
|
||||||
return PlainTextResponse(f"Node ID: {cm.node_id}\nIndexer height: 0\nServices: none\n")
|
return PlainTextResponse(f"Node ID: {cm.node_id}\nIndexer height: 0\nServices: none\n")
|
||||||
|
|
||||||
|
|
||||||
# Legacy auth endpoints
|
|
||||||
@router.post("/api/v1/auth.twa")
|
@router.post("/api/v1/auth.twa")
|
||||||
async def v1_auth_twa(payload: dict):
|
async def v1_auth_twa(payload: dict):
|
||||||
user_ref = payload.get("user") or {}
|
user_ref = payload.get("user") or {}
|
||||||
|
|
@ -141,7 +138,6 @@ async def v1_storage_upload(file: UploadFile = File(...)):
|
||||||
file_path = os.path.join(backend.files_path, file_hash)
|
file_path = os.path.join(backend.files_path, file_hash)
|
||||||
async with aiofiles.open(file_path, 'wb') as f:
|
async with aiofiles.open(file_path, 'wb') as f:
|
||||||
await f.write(data)
|
await f.write(data)
|
||||||
# Возвращаем hash без записи ORM, чтобы избежать конфликтов схем
|
|
||||||
return {"hash": file_hash}
|
return {"hash": file_hash}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,10 @@ router = APIRouter(prefix="/api/node", tags=["node-communication"])
|
||||||
|
|
||||||
async def validate_node_request(request: Request) -> Dict[str, Any]:
|
async def validate_node_request(request: Request) -> Dict[str, Any]:
|
||||||
"""Валидация межузлового запроса с обязательной проверкой подписи"""
|
"""Валидация межузлового запроса с обязательной проверкой подписи"""
|
||||||
# Заголовки
|
|
||||||
required_headers = ["x-node-communication", "x-node-id", "x-node-public-key", "x-node-signature"]
|
required_headers = ["x-node-communication", "x-node-id", "x-node-public-key", "x-node-signature"]
|
||||||
for header in required_headers:
|
for header in required_headers:
|
||||||
if header not in request.headers:
|
if header not in request.headers:
|
||||||
raise HTTPException(status_code=400, detail=f"Missing required header: {header}")
|
raise HTTPException(status_code=400, detail=f"Missing required header: {header}")
|
||||||
|
|
||||||
if request.headers.get("x-node-communication") != "true":
|
if request.headers.get("x-node-communication") != "true":
|
||||||
raise HTTPException(status_code=400, detail="Not a valid inter-node communication")
|
raise HTTPException(status_code=400, detail="Not a valid inter-node communication")
|
||||||
|
|
||||||
|
|
@ -35,18 +33,15 @@ async def validate_node_request(request: Request) -> Dict[str, Any]:
|
||||||
node_id = request.headers.get("x-node-id")
|
node_id = request.headers.get("x-node-id")
|
||||||
public_key = request.headers.get("x-node-public-key")
|
public_key = request.headers.get("x-node-public-key")
|
||||||
|
|
||||||
# Тело запроса
|
|
||||||
body = await request.body()
|
body = await request.body()
|
||||||
if not body:
|
if not body:
|
||||||
raise HTTPException(status_code=400, detail="Empty message body")
|
raise HTTPException(status_code=400, detail="Empty message body")
|
||||||
|
|
||||||
# JSON
|
|
||||||
try:
|
try:
|
||||||
message_data = json.loads(body.decode())
|
message_data = json.loads(body.decode())
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
raise HTTPException(status_code=400, detail="Invalid JSON in request body")
|
raise HTTPException(status_code=400, detail="Invalid JSON in request body")
|
||||||
|
|
||||||
# Anti-replay (необязательно для обратной совместимости)
|
# Optional anti-replay
|
||||||
try:
|
try:
|
||||||
ts = message_data.get("timestamp")
|
ts = message_data.get("timestamp")
|
||||||
nonce = message_data.get("nonce")
|
nonce = message_data.get("nonce")
|
||||||
|
|
@ -62,10 +57,8 @@ async def validate_node_request(request: Request) -> Dict[str, Any]:
|
||||||
raise HTTPException(status_code=400, detail="replay detected")
|
raise HTTPException(status_code=400, detail="replay detected")
|
||||||
await cache.set(cache_key, True, ttl=600)
|
await cache.set(cache_key, True, ttl=600)
|
||||||
except Exception:
|
except Exception:
|
||||||
# ignore for backward compatibility
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Подпись
|
|
||||||
is_valid = crypto_manager.verify_signature(message_data, signature, public_key)
|
is_valid = crypto_manager.verify_signature(message_data, signature, public_key)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
logger.warning(f"Invalid signature from node {node_id}")
|
logger.warning(f"Invalid signature from node {node_id}")
|
||||||
|
|
|
||||||
|
|
@ -1,93 +1,124 @@
|
||||||
from app.core.logger import make_log, logger
|
from app.core.logger import make_log, logger
|
||||||
from app.core.models._telegram import Wrapped_CBotChat
|
from app.core.models._telegram import Wrapped_CBotChat
|
||||||
from app.core.models.user import User
|
from app.core.models.user import User
|
||||||
from app.core.storage import db_session
|
|
||||||
from aiogram import BaseMiddleware, types
|
from aiogram import BaseMiddleware, types
|
||||||
from app.core.models.messages import KnownTelegramMessage
|
from app.core.models.messages import KnownTelegramMessage
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Bot handlers historically use synchronous SQLAlchemy patterns.
|
||||||
|
# Keep a dedicated sync engine/session for bot middleware to preserve legacy behavior.
|
||||||
|
import re
|
||||||
|
from typing import Optional
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker, Session
|
||||||
|
from app.core.config import get_settings
|
||||||
|
|
||||||
|
|
||||||
|
_SYNC_ENGINE = None
|
||||||
|
_SYNC_FACTORY: Optional[sessionmaker] = None
|
||||||
|
|
||||||
|
|
||||||
|
def _to_sync_dsn(async_dsn: str) -> str:
|
||||||
|
# Convert postgresql+asyncpg:// to postgresql+psycopg2:// for synchronous engine
|
||||||
|
return re.sub(r"\+asyncpg", "+psycopg2", async_dsn)
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_sync_session() -> Session:
|
||||||
|
global _SYNC_ENGINE, _SYNC_FACTORY
|
||||||
|
if _SYNC_ENGINE is None or _SYNC_FACTORY is None:
|
||||||
|
settings = get_settings()
|
||||||
|
dsn = _to_sync_dsn(settings.DATABASE_URL)
|
||||||
|
_SYNC_ENGINE = create_engine(dsn, pool_pre_ping=True, future=True)
|
||||||
|
_SYNC_FACTORY = sessionmaker(bind=_SYNC_ENGINE, autocommit=False, autoflush=True)
|
||||||
|
return _SYNC_FACTORY()
|
||||||
|
|
||||||
|
|
||||||
class UserDataMiddleware(BaseMiddleware):
|
class UserDataMiddleware(BaseMiddleware):
|
||||||
async def __call__(self, handler, event, data):
|
|
||||||
update_body = event.message or event.callback_query or event.inline_query or event.pre_checkout_query
|
|
||||||
if not update_body:
|
|
||||||
return
|
|
||||||
|
|
||||||
if update_body.from_user.is_bot is True:
|
async def __call__(self, handler, event, data):
|
||||||
|
update_body = event.message or event.callback_query or getattr(event, 'inline_query', None) or getattr(event, 'pre_checkout_query', None)
|
||||||
|
if not update_body or getattr(update_body, 'from_user', None) is None:
|
||||||
|
return
|
||||||
|
if getattr(update_body.from_user, 'is_bot', False):
|
||||||
return
|
return
|
||||||
|
|
||||||
user_id = update_body.from_user.id
|
user_id = update_body.from_user.id
|
||||||
assert user_id >= 1
|
assert user_id >= 1
|
||||||
|
|
||||||
# TODO: maybe make users cache
|
# Use sync session for bot handlers compatibility
|
||||||
|
from app.core.models.user.user import User as DbUser
|
||||||
|
from app.core.models.messages import KnownTelegramMessage as DbKnownMsg
|
||||||
|
from app.core.logging import logger as app_logger
|
||||||
|
|
||||||
with db_session(auto_commit=False) as session:
|
session = _ensure_sync_session()
|
||||||
|
try:
|
||||||
|
# Load or create user
|
||||||
try:
|
try:
|
||||||
user = session.query(User).filter_by(telegram_id=user_id).first()
|
user = session.query(DbUser).filter(DbUser.telegram_id == user_id).first()
|
||||||
except BaseException as e:
|
except Exception as e:
|
||||||
logger.error(f"Error when middleware getting user: {e}")
|
await app_logger.aerror("Middleware get user failed", error=str(e))
|
||||||
user = None
|
user = None
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
logger.debug(f"User {user_id} not found. Creating new user")
|
await app_logger.adebug("Creating new user", telegram_id=user_id)
|
||||||
user = User(
|
user = DbUser(
|
||||||
telegram_id=user_id,
|
telegram_id=user_id,
|
||||||
username=update_body.from_user.username,
|
username=getattr(update_body.from_user, 'username', None),
|
||||||
lang_code='en',
|
language_code=getattr(update_body.from_user, 'language_code', 'en'),
|
||||||
last_use=datetime.now(),
|
meta={
|
||||||
meta=dict(first_name=update_body.from_user.first_name or '',
|
'first_name': getattr(update_body.from_user, 'first_name', '') or '',
|
||||||
last_name=update_body.from_user.last_name or '', username=update_body.from_user.username,
|
'last_name': getattr(update_body.from_user, 'last_name', '') or '',
|
||||||
language_code=update_body.from_user.language_code,
|
'username': getattr(update_body.from_user, 'username', None),
|
||||||
is_premium=update_body.from_user.is_premium),
|
'language_code': getattr(update_body.from_user, 'language_code', None),
|
||||||
created=datetime.now()
|
'is_premium': getattr(update_body.from_user, 'is_premium', False),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
session.add(user)
|
session.add(user)
|
||||||
session.commit()
|
session.commit()
|
||||||
else:
|
else:
|
||||||
if user.username != update_body.from_user.username:
|
# Update username/metadata
|
||||||
user.username = update_body.from_user.username
|
changed = False
|
||||||
|
if user.username != getattr(update_body.from_user, 'username', None):
|
||||||
updated_meta_fields = {}
|
user.username = getattr(update_body.from_user, 'username', None)
|
||||||
if user.meta.get('first_name') != update_body.from_user.first_name:
|
changed = True
|
||||||
updated_meta_fields['first_name'] = update_body.from_user.first_name
|
meta = dict(user.meta or {})
|
||||||
|
if meta.get('first_name') != getattr(update_body.from_user, 'first_name', None):
|
||||||
if user.meta.get('last_name') != update_body.from_user.last_name:
|
meta['first_name'] = getattr(update_body.from_user, 'first_name', None)
|
||||||
updated_meta_fields['last_name'] = update_body.from_user.last_name
|
changed = True
|
||||||
|
if meta.get('last_name') != getattr(update_body.from_user, 'last_name', None):
|
||||||
user.meta = {
|
meta['last_name'] = getattr(update_body.from_user, 'last_name', None)
|
||||||
**user.meta,
|
changed = True
|
||||||
**updated_meta_fields
|
user.meta = meta
|
||||||
}
|
user.last_activity = datetime.utcnow()
|
||||||
|
if changed:
|
||||||
user.last_use = datetime.now()
|
session.commit()
|
||||||
session.commit()
|
|
||||||
|
|
||||||
data['user'] = user
|
data['user'] = user
|
||||||
|
# Pass sync session for routers expecting .query()
|
||||||
data['db_session'] = session
|
data['db_session'] = session
|
||||||
|
# chat_wrap can work with sync sessions too
|
||||||
data['chat_wrap'] = Wrapped_CBotChat(data['bot'], chat_id=user_id, db_session=session, user=user)
|
data['chat_wrap'] = Wrapped_CBotChat(data['bot'], chat_id=user_id, db_session=session, user=user)
|
||||||
data['memory'] = data['dispatcher']._s_memory
|
|
||||||
|
|
||||||
|
# De-duplicate known messages
|
||||||
if getattr(update_body, 'text', None):
|
if getattr(update_body, 'text', None):
|
||||||
message_type = 'common'
|
existed = session.query(DbKnownMsg).filter(
|
||||||
if update_body.text.startswith('/start'):
|
(DbKnownMsg.chat_id == update_body.chat.id) &
|
||||||
message_type = 'start_command'
|
(DbKnownMsg.message_id == update_body.message_id) &
|
||||||
|
(DbKnownMsg.from_user == True)
|
||||||
if session.query(KnownTelegramMessage).filter_by(
|
).first()
|
||||||
chat_id=update_body.chat.id,
|
if existed:
|
||||||
message_id=update_body.message_id,
|
await app_logger.adebug("Message already processed", message_id=update_body.message_id)
|
||||||
from_user=True
|
|
||||||
).first():
|
|
||||||
make_log("UserDataMiddleware", f"Message {update_body.message_id} already processed", level='debug')
|
|
||||||
return
|
return
|
||||||
|
|
||||||
new_message = KnownTelegramMessage(
|
new_message = DbKnownMsg(
|
||||||
type=message_type,
|
type='start_command' if str(update_body.text).startswith('/start') else 'common',
|
||||||
bot_id=data['chat_wrap'].bot_id,
|
bot_id=data['chat_wrap'].bot_id,
|
||||||
chat_id=update_body.chat.id,
|
chat_id=update_body.chat.id,
|
||||||
message_id=update_body.message_id,
|
message_id=update_body.message_id,
|
||||||
from_user=True,
|
from_user=True,
|
||||||
from_telegram_id=user_id,
|
from_telegram_id=user_id,
|
||||||
created=datetime.now(),
|
created=datetime.utcnow(),
|
||||||
meta={}
|
meta={}
|
||||||
)
|
)
|
||||||
session.add(new_message)
|
session.add(new_message)
|
||||||
|
|
@ -95,3 +126,8 @@ class UserDataMiddleware(BaseMiddleware):
|
||||||
|
|
||||||
result = await handler(event, data)
|
result = await handler(event, data)
|
||||||
return result
|
return result
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
session.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
"""
|
||||||
|
Lightweight Telegram bot poller to ensure the bot responds.
|
||||||
|
Does not depend on legacy sync DB middleware. Provides minimal /start handler.
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import Bot, Dispatcher, Router, types
|
||||||
|
from aiogram.filters import Command
|
||||||
|
|
||||||
|
from app.core.config import get_settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def build_router(name: str) -> Router:
|
||||||
|
r = Router(name=name)
|
||||||
|
|
||||||
|
@r.message(Command("start"))
|
||||||
|
async def cmd_start(message: types.Message):
|
||||||
|
await message.answer(
|
||||||
|
"MY Network bot online. Use /help to get options.")
|
||||||
|
|
||||||
|
@r.message(Command("help"))
|
||||||
|
async def cmd_help(message: types.Message):
|
||||||
|
await message.answer("Available: /start, /help")
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
async def _run_single_bot(token: str, name: str):
|
||||||
|
bot = Bot(token)
|
||||||
|
dp = Dispatcher()
|
||||||
|
dp.include_router(build_router(name))
|
||||||
|
logger.info("Starting Telegram bot polling", extra={"name": name})
|
||||||
|
try:
|
||||||
|
await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Bot polling failed", extra={"name": name, "error": str(e)})
|
||||||
|
await bot.session.close()
|
||||||
|
|
||||||
|
|
||||||
|
async def start_bots_if_configured() -> list[asyncio.Task]:
|
||||||
|
settings = get_settings()
|
||||||
|
tasks: list[asyncio.Task] = []
|
||||||
|
# Prefer TELEGRAM_WEBHOOK_ENABLED to avoid double-processing
|
||||||
|
webhook_enabled = bool(getattr(settings, 'TELEGRAM_WEBHOOK_ENABLED', False))
|
||||||
|
if webhook_enabled:
|
||||||
|
logger.info("Telegram webhook enabled; skipping polling start")
|
||||||
|
return tasks
|
||||||
|
|
||||||
|
# Main bot
|
||||||
|
token: Optional[str] = getattr(settings, 'TELEGRAM_API_KEY', None)
|
||||||
|
if token:
|
||||||
|
tasks.append(asyncio.create_task(_run_single_bot(token, 'main')))
|
||||||
|
|
||||||
|
# Client bot
|
||||||
|
ct: Optional[str] = getattr(settings, 'CLIENT_TELEGRAM_API_KEY', None)
|
||||||
|
if ct:
|
||||||
|
tasks.append(asyncio.create_task(_run_single_bot(ct, 'client')))
|
||||||
|
|
||||||
|
if tasks:
|
||||||
|
logger.info("Telegram bots polling started", extra={"count": len(tasks)})
|
||||||
|
else:
|
||||||
|
logger.info("No Telegram tokens configured; polling not started")
|
||||||
|
return tasks
|
||||||
|
|
@ -5,6 +5,8 @@ from sqlalchemy import and_
|
||||||
|
|
||||||
from app.core.logger import make_log
|
from app.core.logger import make_log
|
||||||
from app.core.models.messages import KnownTelegramMessage
|
from app.core.models.messages import KnownTelegramMessage
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select
|
||||||
from app.core._config import TELEGRAM_API_KEY, CLIENT_TELEGRAM_API_KEY
|
from app.core._config import TELEGRAM_API_KEY, CLIENT_TELEGRAM_API_KEY
|
||||||
|
|
||||||
from app.core.models._telegram.templates.player import PlayerTemplates
|
from app.core.models._telegram.templates.player import PlayerTemplates
|
||||||
|
|
@ -56,25 +58,36 @@ class Wrapped_CBotChat(T, PlayerTemplates):
|
||||||
if self.db_session:
|
if self.db_session:
|
||||||
if message_type == 'common':
|
if message_type == 'common':
|
||||||
ci = 0
|
ci = 0
|
||||||
for oc_msg in self.db_session.query(KnownTelegramMessage).filter(
|
if isinstance(self.db_session, AsyncSession):
|
||||||
and_(
|
res = await self.db_session.execute(select(KnownTelegramMessage).where(
|
||||||
KnownTelegramMessage.type == 'common',
|
and_(
|
||||||
KnownTelegramMessage.bot_id == self.bot_id,
|
KnownTelegramMessage.type == 'common',
|
||||||
KnownTelegramMessage.chat_id == self._chat_id,
|
KnownTelegramMessage.bot_id == self.bot_id,
|
||||||
KnownTelegramMessage.deleted == False
|
KnownTelegramMessage.chat_id == self._chat_id,
|
||||||
)
|
KnownTelegramMessage.deleted == False
|
||||||
).all():
|
)
|
||||||
|
))
|
||||||
|
old = res.scalars().all()
|
||||||
|
else:
|
||||||
|
old = self.db_session.query(KnownTelegramMessage).filter(
|
||||||
|
and_(
|
||||||
|
KnownTelegramMessage.type == 'common',
|
||||||
|
KnownTelegramMessage.bot_id == self.bot_id,
|
||||||
|
KnownTelegramMessage.chat_id == self._chat_id,
|
||||||
|
KnownTelegramMessage.deleted == False
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
for oc_msg in old:
|
||||||
make_log(self, f"Delete old message {oc_msg.message_id} {oc_msg.type} {oc_msg.bot_id} {oc_msg.chat_id}")
|
make_log(self, f"Delete old message {oc_msg.message_id} {oc_msg.type} {oc_msg.bot_id} {oc_msg.chat_id}")
|
||||||
await self.delete_message(oc_msg.message_id)
|
await self.delete_message(oc_msg.message_id)
|
||||||
ci += 1
|
ci += 1
|
||||||
|
|
||||||
make_log(self, f"Deleted {ci} old messages", level='debug')
|
make_log(self, f"Deleted {ci} old messages", level='debug')
|
||||||
|
|
||||||
if isinstance(result, types.Message):
|
if isinstance(result, types.Message):
|
||||||
message_id = getattr(result, 'message_id', None)
|
message_id = getattr(result, 'message_id', None)
|
||||||
assert message_id, "No message_id"
|
assert message_id, "No message_id"
|
||||||
self.db_session.add(
|
if isinstance(self.db_session, AsyncSession):
|
||||||
KnownTelegramMessage(
|
self.db_session.add(KnownTelegramMessage(
|
||||||
type=message_type,
|
type=message_type,
|
||||||
bot_id=self.bot_id,
|
bot_id=self.bot_id,
|
||||||
chat_id=self._chat_id,
|
chat_id=self._chat_id,
|
||||||
|
|
@ -83,9 +96,20 @@ class Wrapped_CBotChat(T, PlayerTemplates):
|
||||||
created=datetime.now(),
|
created=datetime.now(),
|
||||||
meta=message_meta or {},
|
meta=message_meta or {},
|
||||||
content_id=content_id
|
content_id=content_id
|
||||||
)
|
))
|
||||||
)
|
await self.db_session.commit()
|
||||||
self.db_session.commit()
|
else:
|
||||||
|
self.db_session.add(KnownTelegramMessage(
|
||||||
|
type=message_type,
|
||||||
|
bot_id=self.bot_id,
|
||||||
|
chat_id=self._chat_id,
|
||||||
|
message_id=message_id,
|
||||||
|
from_user=False,
|
||||||
|
created=datetime.now(),
|
||||||
|
meta=message_meta or {},
|
||||||
|
content_id=content_id
|
||||||
|
))
|
||||||
|
self.db_session.commit()
|
||||||
else:
|
else:
|
||||||
make_log(self, f"Unknown result type: {type(result)}", level='warning')
|
make_log(self, f"Unknown result type: {type(result)}", level='warning')
|
||||||
|
|
||||||
|
|
@ -137,14 +161,28 @@ class Wrapped_CBotChat(T, PlayerTemplates):
|
||||||
message_id
|
message_id
|
||||||
)):
|
)):
|
||||||
if self.db_session:
|
if self.db_session:
|
||||||
known_message = self.db_session.query(KnownTelegramMessage).filter(
|
known_message = None
|
||||||
KnownTelegramMessage.bot_id == self.bot_id,
|
if isinstance(self.db_session, AsyncSession):
|
||||||
KnownTelegramMessage.chat_id == self._chat_id,
|
res = await self.db_session.execute(select(KnownTelegramMessage).where(
|
||||||
KnownTelegramMessage.message_id == message_id
|
and_(
|
||||||
).first()
|
KnownTelegramMessage.bot_id == self.bot_id,
|
||||||
|
KnownTelegramMessage.chat_id == self._chat_id,
|
||||||
|
KnownTelegramMessage.message_id == message_id
|
||||||
|
)
|
||||||
|
))
|
||||||
|
known_message = res.scalars().first()
|
||||||
|
else:
|
||||||
|
known_message = self.db_session.query(KnownTelegramMessage).filter(
|
||||||
|
KnownTelegramMessage.bot_id == self.bot_id,
|
||||||
|
KnownTelegramMessage.chat_id == self._chat_id,
|
||||||
|
KnownTelegramMessage.message_id == message_id
|
||||||
|
).first()
|
||||||
if known_message:
|
if known_message:
|
||||||
known_message.deleted = True
|
known_message.deleted = True
|
||||||
self.db_session.commit()
|
if isinstance(self.db_session, AsyncSession):
|
||||||
|
await self.db_session.commit()
|
||||||
|
else:
|
||||||
|
self.db_session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
make_log(self, f"Error deleting message {self._chat_id}/{message_id}. Error: {e}", level='warning')
|
make_log(self, f"Error deleting message {self._chat_id}/{message_id}. Error: {e}", level='warning')
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,13 @@ async def lifespan(app: FastAPI):
|
||||||
# Проверка готовности системы
|
# Проверка готовности системы
|
||||||
await logger.ainfo("System initialization completed successfully")
|
await logger.ainfo("System initialization completed successfully")
|
||||||
|
|
||||||
|
# Start Telegram bot polling (non-blocking) if configured
|
||||||
|
try:
|
||||||
|
from app.services.telegram_poller import start_bots_if_configured
|
||||||
|
app.state._bot_tasks = await start_bots_if_configured()
|
||||||
|
except Exception as _e:
|
||||||
|
await logger.awarning('Telegram bot polling not started', error=str(_e))
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -150,6 +157,16 @@ async def lifespan(app: FastAPI):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await logger.aerror(f"Error closing cache: {e}")
|
await logger.aerror(f"Error closing cache: {e}")
|
||||||
|
|
||||||
|
# Stop Telegram bot polling tasks if any
|
||||||
|
try:
|
||||||
|
tasks = getattr(app.state, '_bot_tasks', []) or []
|
||||||
|
for t in tasks:
|
||||||
|
t.cancel()
|
||||||
|
if tasks:
|
||||||
|
await logger.ainfo('Telegram bot polling tasks cancelled', count=len(tasks))
|
||||||
|
except Exception as _e:
|
||||||
|
await logger.awarning('Failed to cancel bot tasks', error=str(_e))
|
||||||
|
|
||||||
await logger.ainfo("Application shutdown completed")
|
await logger.ainfo("Application shutdown completed")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
"""
|
||||||
|
Project Telegram polling startup: launches both main and client bots with full routers and middleware.
|
||||||
|
Cleans webhooks before polling to ensure updates delivery.
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import Bot, Dispatcher
|
||||||
|
from aiogram.fsm.storage.memory import MemoryStorage
|
||||||
|
|
||||||
|
from app.core.config import get_settings
|
||||||
|
from app.bot.middleware import UserDataMiddleware
|
||||||
|
from app.bot.routers.index import main_router as main_bot_router
|
||||||
|
from app.client_bot.routers.index import main_router as client_bot_router
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def _run_bot(token: str, name: str, router) -> None:
|
||||||
|
bot = Bot(token)
|
||||||
|
dp = Dispatcher(storage=MemoryStorage())
|
||||||
|
dp.update.outer_middleware(UserDataMiddleware())
|
||||||
|
dp.include_router(router)
|
||||||
|
logger.info("Preparing Telegram bot for polling", extra={"name": name})
|
||||||
|
# Ensure webhook is removed so polling works
|
||||||
|
await bot.delete_webhook(drop_pending_updates=False)
|
||||||
|
logger.info("Webhook cleared", extra={"name": name})
|
||||||
|
logger.info("Starting Telegram bot polling", extra={"name": name})
|
||||||
|
await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
|
||||||
|
|
||||||
|
|
||||||
|
async def start_bots_if_configured() -> list[asyncio.Task]:
|
||||||
|
settings = get_settings()
|
||||||
|
tasks: list[asyncio.Task] = []
|
||||||
|
if bool(getattr(settings, 'TELEGRAM_WEBHOOK_ENABLED', False)):
|
||||||
|
logger.info("Telegram webhook enabled; skipping polling start")
|
||||||
|
return tasks
|
||||||
|
|
||||||
|
main_token: Optional[str] = getattr(settings, 'TELEGRAM_API_KEY', None)
|
||||||
|
if main_token:
|
||||||
|
tasks.append(asyncio.create_task(_run_bot(main_token, 'main', main_bot_router)))
|
||||||
|
|
||||||
|
client_token: Optional[str] = getattr(settings, 'CLIENT_TELEGRAM_API_KEY', None)
|
||||||
|
if client_token:
|
||||||
|
tasks.append(asyncio.create_task(_run_bot(client_token, 'client', client_bot_router)))
|
||||||
|
|
||||||
|
if tasks:
|
||||||
|
logger.info("Telegram bots polling started", extra={"count": len(tasks)})
|
||||||
|
else:
|
||||||
|
logger.info("No Telegram tokens configured; polling not started")
|
||||||
|
return tasks
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
{
|
{
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"network_id": "my-network-1755317385",
|
"network_id": "my-network-1755348796",
|
||||||
"created_at": "2025-08-16T04:09:45Z",
|
"created_at": "2025-08-16T12:53:16Z",
|
||||||
"bootstrap_nodes": [
|
"bootstrap_nodes": [
|
||||||
{
|
{
|
||||||
"id": "node-3a2c6a21e3401fce",
|
"id": "node-bea1d09bc687311b",
|
||||||
"node_id": "node-3a2c6a21e3401fce",
|
"node_id": "node-bea1d09bc687311b",
|
||||||
"address": "2a02:6b40:2000:16b1::1",
|
"address": "2a02:6b40:2000:16b1::1",
|
||||||
"port": 8000,
|
"port": 8000,
|
||||||
"public_key": "3a2c6a21e3401fceed1fb63c45d068f20e21b48159db3a961a2c43e8701071d4",
|
"public_key": "bea1d09bc687311b17b048789be8d8950b88904aa68a0d9992a83cb3851e8bd6",
|
||||||
"trusted": true,
|
"trusted": true,
|
||||||
"node_type": "bootstrap"
|
"node_type": "bootstrap"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue