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,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""B 站收藏夹写客户端(``deal`` / ``sort``)。
|
||||
|
||||
只应使用 ``bilibili.write`` 凭据构造。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import urllib.parse
|
||||
from typing import Any, Mapping
|
||||
|
||||
from .client import (
|
||||
BASE,
|
||||
BilibiliClient,
|
||||
BilibiliHTTPError,
|
||||
BilibiliProtocolError,
|
||||
)
|
||||
|
||||
|
||||
class BilibiliWriteClient(BilibiliClient):
|
||||
def post_form(self, url: str, data: Mapping[str, Any]) -> dict[str, Any]:
|
||||
self.pacer.wait()
|
||||
headers = self._headers(auth=True)
|
||||
headers["content-type"] = "application/x-www-form-urlencoded"
|
||||
body = urllib.parse.urlencode(data).encode("utf-8")
|
||||
response = self.transport.request(
|
||||
"POST", url, headers, data=body, timeout=self.timeout
|
||||
)
|
||||
if response.status >= 400:
|
||||
raise BilibiliHTTPError(
|
||||
response.status, response.body.decode("utf-8", "replace")[:200]
|
||||
)
|
||||
payload = response.json()
|
||||
if not isinstance(payload, dict):
|
||||
raise BilibiliProtocolError("写接口响应顶层不是 JSON 对象")
|
||||
if payload.get("code") != 0:
|
||||
raise self._api_error(payload)
|
||||
return payload
|
||||
|
||||
def deal(self, aid: int, folder_id: int) -> dict[str, Any]:
|
||||
return self.post_form(
|
||||
f"{BASE}/x/v3/fav/resource/deal",
|
||||
{
|
||||
"rid": int(aid),
|
||||
"type": 2,
|
||||
"add_media_ids": int(folder_id),
|
||||
"del_media_ids": "",
|
||||
"csrf": self.csrf,
|
||||
},
|
||||
)
|
||||
|
||||
def sort(self, media_id: int, sort_spec: str) -> dict[str, Any]:
|
||||
return self.post_form(
|
||||
f"{BASE}/x/v3/fav/resource/sort",
|
||||
{
|
||||
"media_id": int(media_id),
|
||||
"sort": sort_spec,
|
||||
"csrf": self.csrf,
|
||||
},
|
||||
)
|
||||
Reference in new issue
Block a user