Files
BOINC-report-bot/bot/main.py
dan 55cfd73c24
All checks were successful
publish-images / build (push) Successful in 1m3s
Add BOINC Telegram bot, CI, and deploy compose
2026-01-06 09:44:48 +03:00

107 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import asyncio
import logging
from functools import partial
from typing import Literal
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ParseMode
from telegram.ext import (
ApplicationBuilder,
CallbackQueryHandler,
CommandHandler,
ContextTypes,
)
from .boinc import BoincClient
from .config import Settings
from .ui import format_dashboard, format_tasks_section
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
logger = logging.getLogger(__name__)
View = Literal["dashboard", "active", "queued", "completed"]
def build_keyboard() -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(
[
[
InlineKeyboardButton("Обновить", callback_data="dashboard"),
InlineKeyboardButton("Активные", callback_data="active"),
],
[
InlineKeyboardButton("Очередь", callback_data="queued"),
InlineKeyboardButton("Готовы к отправке", callback_data="completed"),
],
]
)
async def render_view(view: View, client: BoincClient) -> str:
snapshot = await client.fetch_snapshot()
if view == "dashboard":
return format_dashboard(snapshot)
if view == "active":
return format_tasks_section("Активные задачи", snapshot.active)
if view == "queued":
return format_tasks_section("Ожидают запуска", snapshot.queued)
return format_tasks_section("Готовы к отправке", snapshot.completed)
async def start_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
client: BoincClient = context.bot_data["boinc_client"]
text = await render_view("dashboard", client)
await update.message.reply_text(
text, reply_markup=build_keyboard(), parse_mode=ParseMode.HTML
)
async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
query = update.callback_query
await query.answer()
client: BoincClient = context.bot_data["boinc_client"]
view: View = query.data if query.data in {"dashboard", "active", "queued", "completed"} else "dashboard"
try:
text = await render_view(view, client)
except Exception as exc: # broad catch to show meaningful message in chat
logger.exception("Error rendering view %s", view)
text = f"Не удалось получить данные: {exc}"
await query.edit_message_text(
text=text, reply_markup=build_keyboard(), parse_mode=ParseMode.HTML
)
async def main() -> None:
settings = Settings.from_env()
client = BoincClient(
host=settings.boinc_host,
port=settings.boinc_port,
password=settings.boinc_password,
boinccmd_path=settings.boinccmd_path,
sample_output=settings.sample_output,
)
application = (
ApplicationBuilder()
.token(settings.telegram_token)
.parse_mode(ParseMode.HTML)
.build()
)
application.bot_data["boinc_client"] = client
application.add_handler(CommandHandler("start", start_handler))
application.add_handler(CommandHandler("status", start_handler))
application.add_handler(CallbackQueryHandler(callback_handler))
logger.info("Starting Telegram bot...")
await application.run_polling(close_loop=False)
if __name__ == "__main__":
asyncio.run(main())