feat(telegram): candidate list/delete/clear commands
私聊新增 /candidate [页码](待决策列表)、/candi_del(标题按钮逐条删除,删后原地重建键盘、可翻页连续删)、/candi_clear(全部/待决策/取消)。重构 handle_callback 为前缀分派(cdel/cpage/cclr,保留 add/ign/blk/fav);repository 加 list_pending_candidates/delete_all_candidates;LibraryService 加 pending_candidates/delete_candidate/clear_candidates 并刷看板缓存。
This commit is contained in:
1 parent
c6790f0ba1
commit
4a9c37f2f3
9 files changed
+473
-23
No files matched your search
@@ -89,10 +89,85 @@ def format_manual_reply(manual_result: Any) -> str:
|
||||
|
||||
HELP_TEXT = (
|
||||
"发送 B 站视频链接、BV 号或 av 号即可手动入库并开始追踪。\n"
|
||||
"例如:https://www.bilibili.com/video/BV1x8x166EzY 或 BV1x8x166EzY"
|
||||
"例如:https://www.bilibili.com/video/BV1x8x166EzY 或 BV1x8x166EzY\n"
|
||||
"\n"
|
||||
"候选管理命令:\n"
|
||||
"/candidate [页码] 查看待决策候选列表\n"
|
||||
"/candi_del 用按钮删除指定候选\n"
|
||||
"/candi_clear 清空候选库"
|
||||
)
|
||||
|
||||
|
||||
CANDIDATE_PAGE_SIZE = 10
|
||||
TITLE_BUTTON_LIMIT = 28
|
||||
CANDIDATE_DELETE_HEADER = "待决策候选:点击标题删除对应候选。"
|
||||
|
||||
|
||||
def truncate_title(title: object, limit: int = TITLE_BUTTON_LIMIT) -> str:
|
||||
"""把标题截断到适合作为按钮文案的长度(超出补 …)。"""
|
||||
text = str(title or "").strip() or "(无标题)"
|
||||
if len(text) <= limit:
|
||||
return text
|
||||
return text[: max(1, limit - 1)] + "…"
|
||||
|
||||
|
||||
def _page_bounds(total: int, page: int, page_size: int) -> tuple[int, int, int]:
|
||||
total_pages = max(1, (total + page_size - 1) // page_size)
|
||||
try:
|
||||
page = int(page)
|
||||
except (TypeError, ValueError):
|
||||
page = 1
|
||||
page = min(max(1, page), total_pages)
|
||||
return page, total_pages, (page - 1) * page_size
|
||||
|
||||
|
||||
def format_candidate_list(
|
||||
items: Sequence[Mapping[str, Any]],
|
||||
page: int = 1,
|
||||
page_size: int = CANDIDATE_PAGE_SIZE,
|
||||
) -> str:
|
||||
"""待决策候选的纯文本列表(每页 ``page_size`` 条)。"""
|
||||
total = len(items)
|
||||
if total == 0:
|
||||
return "暂无待决策候选。"
|
||||
page, total_pages, start = _page_bounds(total, page, page_size)
|
||||
lines = [f"待决策候选 共 {total} 条 · 第 {page}/{total_pages} 页"]
|
||||
for item in items[start:start + page_size]:
|
||||
title = html.escape(str(item.get("title") or "(无标题)"))
|
||||
lines.append(f"· {title}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def candidate_delete_keyboard(
|
||||
items: Sequence[Mapping[str, Any]],
|
||||
page: int = 1,
|
||||
page_size: int = CANDIDATE_PAGE_SIZE,
|
||||
) -> dict[str, Any]:
|
||||
"""每页每条标题一个按钮(``cdel:<page>:<aid>``)+ 翻页按钮。"""
|
||||
total = len(items)
|
||||
page, total_pages, start = _page_bounds(total, page, page_size)
|
||||
rows: list[list[Mapping[str, Any]]] = []
|
||||
for item in items[start:start + page_size]:
|
||||
rows.append([{
|
||||
"text": truncate_title(item.get("title")),
|
||||
"callback_data": f"cdel:{page}:{item.get('aid')}",
|
||||
}])
|
||||
if total_pages > 1:
|
||||
rows.append([
|
||||
{"text": "上一页", "callback_data": f"cpage:del:{page - 1}"},
|
||||
{"text": "下一页", "callback_data": f"cpage:del:{page + 1}"},
|
||||
])
|
||||
return {"inline_keyboard": rows}
|
||||
|
||||
|
||||
def clear_candidates_keyboard() -> dict[str, Any]:
|
||||
return {"inline_keyboard": [[
|
||||
{"text": "全部", "callback_data": "cclr:all"},
|
||||
{"text": "待决策", "callback_data": "cclr:pending"},
|
||||
{"text": "取消", "callback_data": "cclr:cancel"},
|
||||
]]}
|
||||
|
||||
|
||||
def format_candidate(candidate: Mapping[str, Any]) -> str:
|
||||
title = html.escape(str(candidate.get("title") or "(无标题)"))
|
||||
up_name = html.escape(str(candidate.get("up_name") or "未知UP"))
|
||||
|
||||
Reference in new issue
Block a user