feat(p3): add discovery, telegram notifications, and one-click favorites
Implement WBI video search with legacy fallback, classification scoring, candidate pool, permanent seen-aid dedupe, blacklist filtering, and batched Telegram notifications. Add write client for fav deal/sort, target-folder selection, insertion position calculation, read-back verification, fav_action locking, retry on failure, ignore and blacklist actions. Wire discovery, candidate cleanup, Telegram long polling, and new discover/candidates CLI commands into the scheduler. Add migration v3 for candidate duration and index stats.
This commit is contained in:
1 parent
6a21b5d905
commit
d971e718e9
31 files changed
+2886
-55
No files matched your search
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Telegram getUpdates long polling 与按钮回调分发。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
class TelegramPoller:
|
||||
def __init__(self, bot, actions, *, logger=None, sleeper=time.sleep) -> None:
|
||||
self.bot = bot
|
||||
self.actions = actions
|
||||
self.logger = logger
|
||||
self._sleep = sleeper
|
||||
|
||||
def _log(self, level: str, event: str, **fields) -> None:
|
||||
if self.logger is not None:
|
||||
getattr(self.logger, level)(event, **fields)
|
||||
|
||||
def run_once(self, offset: int | None = None) -> int | None:
|
||||
updates = self.bot.get_updates(offset)
|
||||
for update in updates:
|
||||
update_id = update.get("update_id")
|
||||
if isinstance(update_id, int) and not isinstance(update_id, bool):
|
||||
offset = max(offset or 0, update_id + 1)
|
||||
callback = update.get("callback_query")
|
||||
if isinstance(callback, Mapping):
|
||||
self.handle_callback(callback)
|
||||
return offset
|
||||
|
||||
def handle_callback(self, callback: Mapping[str, Any]) -> None:
|
||||
callback_id = str(callback.get("id") or "")
|
||||
message = callback.get("message") or {}
|
||||
chat_id = (message.get("chat") or {}).get("id")
|
||||
from_id = (callback.get("from") or {}).get("id")
|
||||
if chat_id != self.bot.chat_id or from_id != self.bot.chat_id:
|
||||
self._log(
|
||||
"warning", "telegram.unauthorized",
|
||||
chat_id=chat_id, from_id=from_id,
|
||||
)
|
||||
self._answer(callback_id, "无权限")
|
||||
return
|
||||
|
||||
action, _, raw_aid = str(callback.get("data") or "").partition(":")
|
||||
try:
|
||||
aid = int(raw_aid)
|
||||
except (TypeError, ValueError):
|
||||
self._answer(callback_id, "未知操作")
|
||||
return
|
||||
|
||||
if action == "fav":
|
||||
result = self.actions.favorite(aid)
|
||||
elif action == "ign":
|
||||
result = self.actions.ignore(aid)
|
||||
elif action == "blk":
|
||||
result = self.actions.blacklist(aid)
|
||||
else:
|
||||
self._answer(callback_id, "未知操作")
|
||||
return
|
||||
|
||||
text = result.message
|
||||
if result.ok and result.position is not None:
|
||||
text = f"{text}(位置 #{result.position})"
|
||||
self._log(
|
||||
"info", "telegram.action",
|
||||
action=action, aid=aid, ok=result.ok, state=result.state,
|
||||
position=result.position,
|
||||
)
|
||||
self._answer(callback_id, text)
|
||||
|
||||
def _answer(self, callback_id: str, text: str) -> None:
|
||||
try:
|
||||
self.bot.answer_callback(callback_id, text)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._log("error", "telegram.answer_failed", error=str(exc))
|
||||
|
||||
def run_forever(self, stop_event=None) -> None:
|
||||
offset: int | None = None
|
||||
while stop_event is None or not stop_event.is_set():
|
||||
try:
|
||||
offset = self.run_once(offset)
|
||||
except Exception as exc: # noqa: BLE001 长轮询失败后退避重试
|
||||
self._log("error", "telegram.poll_failed", error=str(exc))
|
||||
self._sleep(3)
|
||||
Reference in new issue
Block a user