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
@@ -18,6 +18,7 @@ from typing import Any, Mapping
|
||||
|
||||
DEFAULT_CLASSIFY: dict[str, Any] = {
|
||||
"require_title": "咻咻满",
|
||||
"self_mid": 37754047,
|
||||
"positive": [
|
||||
"reaction", "re", "反应", "看", "听", "初听", "首次",
|
||||
"点评", "锐评", "路人", "震惊", "双人",
|
||||
@@ -64,6 +65,11 @@ DEFAULT_CONFIG: dict[str, Any] = {
|
||||
"snapshot_at": "00:01",
|
||||
"candidate_ttl_days": 7,
|
||||
},
|
||||
"discover": {
|
||||
"keywords": ["咻咻满 reaction", "咻咻满 充电"],
|
||||
"pages": 2,
|
||||
"window_days": 14,
|
||||
},
|
||||
"notify": {"min_score": 2, "max_items_per_message": 10},
|
||||
"classify": DEFAULT_CLASSIFY,
|
||||
"heat_weights": DEFAULT_HEAT_WEIGHTS,
|
||||
@@ -148,6 +154,13 @@ class NotifyConfig:
|
||||
max_items_per_message: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DiscoverConfig:
|
||||
keywords: tuple[str, ...]
|
||||
pages: int
|
||||
window_days: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AppConfig:
|
||||
path: Path
|
||||
@@ -160,6 +173,7 @@ class AppConfig:
|
||||
up_blacklist_seed: tuple[int, ...]
|
||||
schedule: ScheduleConfig
|
||||
notify: NotifyConfig
|
||||
discover: DiscoverConfig
|
||||
classify: Mapping[str, Any]
|
||||
heat_weights: Mapping[str, float]
|
||||
raw: Mapping[str, Any] = field(repr=False, compare=False)
|
||||
@@ -367,6 +381,24 @@ def _build_config(data: Mapping[str, Any], path: Path) -> AppConfig:
|
||||
),
|
||||
)
|
||||
|
||||
discover_raw = data["discover"]
|
||||
if not isinstance(discover_raw, Mapping):
|
||||
raise ConfigError("discover 必须是对象")
|
||||
keywords = discover_raw.get("keywords", [])
|
||||
if not isinstance(keywords, list) or not keywords:
|
||||
raise ConfigError("discover.keywords 必须是非空数组")
|
||||
discover = DiscoverConfig(
|
||||
keywords=tuple(
|
||||
_as_str(item, "discover.keywords[]") for item in keywords
|
||||
),
|
||||
pages=_as_int(
|
||||
discover_raw.get("pages", 2), "discover.pages", minimum=1, maximum=10
|
||||
),
|
||||
window_days=_as_int(
|
||||
discover_raw.get("window_days", 14), "discover.window_days", minimum=1
|
||||
),
|
||||
)
|
||||
|
||||
classify_raw = data["classify"]
|
||||
if not isinstance(classify_raw, Mapping):
|
||||
raise ConfigError("classify 必须是对象")
|
||||
@@ -376,6 +408,9 @@ def _build_config(data: Mapping[str, Any], path: Path) -> AppConfig:
|
||||
)
|
||||
if not classify["require_title"]:
|
||||
raise ConfigError("classify.require_title 不能为空")
|
||||
classify["self_mid"] = _as_int(
|
||||
classify.get("self_mid", 37754047), "classify.self_mid", minimum=1
|
||||
)
|
||||
for key in ("positive", "charge", "negative"):
|
||||
classify[key] = _as_str_list(classify.get(key, []), f"classify.{key}")
|
||||
boost = classify.get("duration_boost", {})
|
||||
@@ -407,6 +442,7 @@ def _build_config(data: Mapping[str, Any], path: Path) -> AppConfig:
|
||||
up_blacklist_seed=_as_int_list(data["up_blacklist_seed"], "up_blacklist_seed"),
|
||||
schedule=schedule,
|
||||
notify=notify,
|
||||
discover=discover,
|
||||
classify=classify,
|
||||
heat_weights=heat,
|
||||
raw=data,
|
||||
|
||||
Reference in new issue
Block a user