Add monitoring PF service
This commit is contained in:
182
app/bot.py
Normal file
182
app/bot.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""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"✅ Вы уже подключены как <b>{existing.name}</b>.\n"
|
||||
f"chat_id: <code>{chat_id}</code>",
|
||||
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"✅ Привет, <b>{placeholder.name}</b>! Вы успешно подключены.\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"👋 Привет, <b>{name}</b>! Вы зарегистрированы как сотрудник.\n"
|
||||
f"Откройте веб-интерфейс и создайте проекты, чтобы получать уведомления.\n"
|
||||
f"chat_id: <code>{chat_id}</code>",
|
||||
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"Вы: <b>{e.name}</b>\nchat_id: <code>{chat_id}</code>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
await update.message.reply_text(
|
||||
f"Вы пока не подключены. Отправьте /start.\nchat_id: <code>{chat_id}</code>",
|
||||
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"<b>Ваши проекты ({len(e.projects)}):</b>"]
|
||||
for p in e.projects:
|
||||
lines.append(
|
||||
f"• #{p.id} {p.title} — <code>{p.dld_permit}</code> "
|
||||
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()
|
||||
Reference in New Issue
Block a user