diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 3320bc9..f85ab8f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -489,7 +489,7 @@ P3 实现说明: - `callback_data` 使用 `add:` / `ign:` / `blk:`(`fav:` 兼容旧消息); - 入库只写本地库;`video` 的 `removed` / `dropped` / `unfaved` 状态会被重新激活; - 引用解析是纯函数 `video_ref.extract_video_refs`,`b23.tv` 短链由 `bilibili.client.resolve_short_link` 跟随跳转; -- 手动入库不做「标题含咻咻满」等规则校验;单条消息的引用数量有上限; +- 手动入库不做「标题含咻咻满」等规则校验;单条消息最多处理 50 个引用,超出会在回执中提示分批发送;无效 BV/av 号(`-400`)逐条报错、不中断其余引用; - Telegram 只接受 `chat_id` 与 `from.id` 都等于配置私聊用户的回调与消息。 --- diff --git a/reaction_board/notify/telegram.py b/reaction_board/notify/telegram.py index 222054e..9c03d0e 100644 --- a/reaction_board/notify/telegram.py +++ b/reaction_board/notify/telegram.py @@ -80,6 +80,9 @@ def format_manual_reply(manual_result: Any) -> str: return "未识别到 B 站视频引用,请发送视频链接或 BV 号,例如 BV1x8x166EzY。" results = list(getattr(manual_result, "results", []) or []) lines = [_result_line(item) for item in results] + truncated = int(getattr(manual_result, "truncated", 0) or 0) + if truncated > 0: + lines.append(f"⚠️ 本消息还有 {truncated} 个引用未处理,请分批再发一次。") header = f"共识别 {refs} 个引用,入库 {int(getattr(manual_result, 'added', 0) or 0)} 条:" return "\n".join([header, *lines]) diff --git a/reaction_board/services/library.py b/reaction_board/services/library.py index 22ac868..2d5de4b 100644 --- a/reaction_board/services/library.py +++ b/reaction_board/services/library.py @@ -11,7 +11,11 @@ import time from dataclasses import dataclass, field from typing import Any -from ..bilibili.client import BilibiliError, BilibiliProtocolError +from ..bilibili.client import ( + BilibiliAPIError, + BilibiliError, + BilibiliProtocolError, +) from ..config import ConfigStore from ..repository import Repository from ..timeutil import shanghai_date @@ -42,6 +46,7 @@ class IngestResult: class ManualIngestResult: refs: int = 0 added: int = 0 + truncated: int = 0 results: list[IngestResult] = field(default_factory=list) @property @@ -52,6 +57,7 @@ class ManualIngestResult: return { "refs": self.refs, "added": self.added, + "truncated": self.truncated, "ok": self.ok, "results": [result.as_dict() for result in self.results], } @@ -180,16 +186,30 @@ class LibraryService: # ------------------------------------------------------------ manual def ingest_text(self, text: object) -> ManualIngestResult: - refs = extract_video_refs(text, limit=self.max_refs) - result = ManualIngestResult(refs=len(refs)) - if not refs: + all_refs = extract_video_refs(text, limit=None) + result = ManualIngestResult(refs=len(all_refs)) + if not all_refs: return result + if len(all_refs) > self.max_refs: + result.truncated = len(all_refs) - self.max_refs + all_refs = all_refs[: self.max_refs] + result.refs = len(all_refs) client = self.read_provider.get() ups: list[tuple[int | None, str]] = [] - for ref in refs: + for ref in all_refs: try: detail = self._resolve_ref(client, ref) + except BilibiliAPIError as exc: + message = ( + "无法解析该视频引用:BV 号/av 号无效或视频不存在(code=-400)" + if exc.code == -400 else str(exc) + ) + result.results.append(IngestResult( + ok=False, aid=None, state="error", message=message, + )) + self._log("warning", "ingest.manual_failed", error=str(exc)) + continue except BilibiliError as exc: result.results.append(IngestResult( ok=False, aid=None, state="error", message=str(exc), diff --git a/reaction_board/video_ref.py b/reaction_board/video_ref.py index e972da8..380191a 100644 --- a/reaction_board/video_ref.py +++ b/reaction_board/video_ref.py @@ -25,7 +25,8 @@ _AID_RE = re.compile(r"(?= 15 + text = " ".join(f"BV1x8x166E{i:02d}" for i in range(15)) + assert len(extract_video_refs(text)) == 15 + assert len(extract_video_refs(" ".join( + f"BV1x8x166E{i:02d}" for i in range(DEFAULT_MAX_REFS + 5) + ))) == DEFAULT_MAX_REFS + + def test_invalid_and_boundaries(): assert extract_video_refs("") == [] assert extract_video_refs(None) == [] @@ -85,6 +96,7 @@ def main(): test_short_link() test_multiple_and_order_and_dedup() test_limit() + test_default_cap_allows_bulk_paste() test_invalid_and_boundaries() print("OK: 视频引用(BV/av/URL/短链)解析测试全部通过")