mirror of
https://github.com/dbv111m/musicgrabber.git
synced 2026-10-19 01:31:29 -05:00
fix(bot): notify chat when a queued job completes or fails
Without a push the failure looked like a hang (user had to check /queue manually). _watch_job polls the job API (5s, ~15min cap) and sends ✅/❌ with the error text; title HTML-escaped.
This commit is contained in:
1 parent
d6cceaa744
commit
0607ee488a
2 files changed
+61
-1
No files matched your search
+29
-1
@@ -5,6 +5,8 @@ Simple functional Telegram bot for searching and downloading music.
|
||||
No classes, no over-engineering - just functions.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import html
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
@@ -799,6 +801,30 @@ async def callback_set_quality(update: Update, context: ContextTypes.DEFAULT_TYP
|
||||
log_bot_action(chat_id, "settings", f"flac: {settings['convert_to_flac']}")
|
||||
|
||||
|
||||
async def _watch_job(job_id: str, chat_id: int, message, title: str,
|
||||
interval: float = 5.0, max_polls: int = 180):
|
||||
"""Poll a queued job until it completes/fails, then notify the chat.
|
||||
Fire-and-forget: without it the user never learns the outcome (looks
|
||||
like a hang). ~15 min cap at 5s."""
|
||||
status = "unknown"
|
||||
for _ in range(max_polls):
|
||||
await asyncio.sleep(interval)
|
||||
data = await get_job_status(job_id)
|
||||
if not data:
|
||||
continue
|
||||
status = data.get("status", "unknown")
|
||||
if status in ("completed", "failed"):
|
||||
break
|
||||
safe_title = html.escape(title)
|
||||
if status == "completed":
|
||||
await message.chat.send_message(f"✅ <b>Загружено</b>\n\n🎵 {safe_title}", parse_mode="HTML")
|
||||
elif status == "failed":
|
||||
err = html.escape((data or {}).get("error") or "неизвестная ошибка")
|
||||
await message.chat.send_message(f"❌ <b>Ошибка загрузки</b>\n\n🎵 {safe_title}\nПричина: {err}", parse_mode="HTML")
|
||||
else:
|
||||
logger.info(f"job {job_id}: watcher gave up after {max_polls} polls (last: {status})")
|
||||
|
||||
|
||||
async def callback_download(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle download callback."""
|
||||
query = update.callback_query
|
||||
@@ -855,7 +881,9 @@ async def callback_download(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
🎵 {track.get('title', 'Unknown')}
|
||||
🎤 {track.get('artist') or track.get('channel', 'Unknown')}
|
||||
|
||||
Используйте /queue для отслеживания прогресса."""
|
||||
Придёт уведомление о результате (или /queue для прогресса)."""
|
||||
|
||||
asyncio.create_task(_watch_job(job_id, chat_id, query.message, track.get('title', 'Unknown')))
|
||||
|
||||
await query.edit_message_text(text, parse_mode="HTML")
|
||||
log_bot_action(chat_id, "download", f"{track.get('title')}, job: {job_id}")
|
||||
|
||||
@@ -66,6 +66,38 @@ def main():
|
||||
|
||||
assert app.concurrent_updates, "concurrent_updates выключен: медленный хендлер блокирует остальные кнопки"
|
||||
|
||||
# _watch_job уведомляет чат об итоге (иначе провал выглядит как зависание)
|
||||
import asyncio
|
||||
|
||||
class FakeChat:
|
||||
def __init__(self):
|
||||
self.sent = []
|
||||
|
||||
async def send_message(self, text, **kw):
|
||||
self.sent.append(text)
|
||||
|
||||
class FakeMessage:
|
||||
def __init__(self):
|
||||
self.chat = FakeChat()
|
||||
|
||||
async def run_case(status_json, expect):
|
||||
msg = FakeMessage()
|
||||
tb.get_job_status = lambda job_id: _async_ret(status_json)
|
||||
await tb._watch_job("j", 1, msg, "T<i>tle", interval=0, max_polls=2)
|
||||
if expect is None:
|
||||
assert not msg.chat.sent, f"не должно быть отправок, а было: {msg.chat.sent}"
|
||||
else:
|
||||
assert msg.chat.sent and expect in msg.chat.sent[0], f"ожидалось '{expect}', отправлено: {msg.chat.sent}"
|
||||
assert "<i>" not in msg.chat.sent[0], "HTML в title не экранирован"
|
||||
|
||||
async def _async_ret(v):
|
||||
return v
|
||||
|
||||
asyncio.run(run_case({"status": "completed"}, "✅"))
|
||||
asyncio.run(run_case({"status": "failed", "error": "DRM"}, "❌"))
|
||||
asyncio.run(run_case({"status": "downloading"}, None)) # не завершился — молча
|
||||
print("OK: watcher шлёт ✅/❌ по итогу задания")
|
||||
|
||||
print(f"OK: все {len(all_callback_datas())} видов callback_data покрыты хендлерами, concurrent_updates включён")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user