"""Telegram bot — registers employees by chat_id and lets them trigger checks. Run as a separate process: `python -m app.bot`. Bot commands (set via @BotFather → /setcommands): start - Подключить себя как сотрудника list - Список своих проектов check - Проверить все мои проекты сейчас whoami - Показать свой chat_id """ from __future__ import annotations import asyncio import logging from sqlalchemy.orm import joinedload from telegram import Update from telegram.ext import ( Application, CommandHandler, ContextTypes, ) from app.config import settings from app.db import SessionLocal, init_db from app.models import Employee, Project from app.services.monitor import run_check_for_project logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") logger = logging.getLogger(__name__) async def cmd_start(update: Update, _: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_user or not update.effective_chat: return user = update.effective_user chat_id = str(update.effective_chat.id) username = user.username db = SessionLocal() try: existing = ( db.query(Employee).filter(Employee.tg_chat_id == chat_id).first() ) if existing: await update.message.reply_text( f"✅ Вы уже подключены как {existing.name}.\n" f"chat_id: {chat_id}", parse_mode="HTML", ) return # Try to find by username (admin pre-created employee w/o chat_id) if username: placeholder = ( db.query(Employee) .filter(Employee.tg_username == username, Employee.tg_chat_id.is_(None)) .first() ) if placeholder: placeholder.tg_chat_id = chat_id db.commit() await update.message.reply_text( f"✅ Привет, {placeholder.name}! Вы успешно подключены.\n" f"Уведомления будут приходить сюда.", parse_mode="HTML", ) return # Create a new employee record from this user name = (user.full_name or username or f"user_{chat_id}").strip() e = Employee(name=name, tg_chat_id=chat_id, tg_username=username) db.add(e) db.commit() await update.message.reply_text( f"👋 Привет, {name}! Вы зарегистрированы как сотрудник.\n" f"Откройте веб-интерфейс и создайте проекты, чтобы получать уведомления.\n" f"chat_id: {chat_id}", parse_mode="HTML", ) finally: db.close() async def cmd_whoami(update: Update, _: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_chat: return chat_id = str(update.effective_chat.id) db = SessionLocal() try: e = db.query(Employee).filter(Employee.tg_chat_id == chat_id).first() if e: await update.message.reply_text( f"Вы: {e.name}\nchat_id: {chat_id}", parse_mode="HTML", ) else: await update.message.reply_text( f"Вы пока не подключены. Отправьте /start.\nchat_id: {chat_id}", parse_mode="HTML", ) finally: db.close() async def cmd_list(update: Update, _: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_chat: return chat_id = str(update.effective_chat.id) db = SessionLocal() try: e = ( db.query(Employee) .options(joinedload(Employee.projects)) .filter(Employee.tg_chat_id == chat_id) .first() ) if not e: await update.message.reply_text("Сначала /start.") return if not e.projects: await update.message.reply_text("У вас пока нет проектов.") return lines = [f"Ваши проекты ({len(e.projects)}):"] for p in e.projects: lines.append( f"• #{p.id} {p.title} — {p.dld_permit} " f"({p.deal_type.value})" ) await update.message.reply_text("\n".join(lines), parse_mode="HTML") finally: db.close() async def cmd_check(update: Update, _: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_chat: return chat_id = str(update.effective_chat.id) db = SessionLocal() try: e = ( db.query(Employee) .options(joinedload(Employee.projects)) .filter(Employee.tg_chat_id == chat_id) .first() ) if not e: await update.message.reply_text("Сначала /start.") return if not e.projects: await update.message.reply_text("У вас нет проектов.") return ids = [p.id for p in e.projects] finally: db.close() await update.message.reply_text(f"⏳ Запускаю проверку {len(ids)} проектов…") total_changes = 0 for pid in ids: try: total_changes += await asyncio.to_thread(run_check_for_project, pid) except Exception as ex: logger.exception("check failed for %s: %s", pid, ex) await update.message.reply_text(f"✅ Готово. Изменений: {total_changes}") def main() -> None: if not settings.tg_bot_token: raise SystemExit("TG_BOT_TOKEN не задан в .env") init_db() app = Application.builder().token(settings.tg_bot_token).build() app.add_handler(CommandHandler("start", cmd_start)) app.add_handler(CommandHandler("whoami", cmd_whoami)) app.add_handler(CommandHandler("list", cmd_list)) app.add_handler(CommandHandler("check", cmd_check)) logger.info("Bot polling…") app.run_polling(allowed_updates=Update.ALL_TYPES) if __name__ == "__main__": main()