feat: split history interaction event types

This commit is contained in:
2026-09-07 19:52:53 +08:00
parent 9145508665
commit d5f9c4043d
5 changed files with 112 additions and 28 deletions
@@ -0,0 +1,26 @@
# 历史事件类型拆分
## 修改
历史事件预览现在按独立事件类型展示:
- 点赞
- 评论
- 收藏
- 分享
- 关注
- 其他作品互动
后端新增 `favorite` 和 `share` 类型;平台返回的 `collect` 归一为 `favorite`。历史缓存读取时会根据原始业务数据重新归一化,旧缓存也能正确显示新类型。
已有只勾选“其他作品互动”的旧规则继续兼容匹配收藏和分享;新规则可单独勾选收藏或分享。
## 验证与部署
- 本地测试:57 个通过。
- Windows 原生测试:56 个通过。
- Windows 原生构建成功,耗时约 328 秒。
- 已静默安装到 `C:\Users\Rogee\AppData\Local\Programs\DouyinAccounts`。
- 最新安装包已复制到 `C:\Users\Rogee\Desktop\DouyinAccounts-0.1.8-Windows-x64-Setup.exe`。
- 已安装可执行文件与 dist 文件 SHA256 一致:`EB2C6067091596E2E0217E3F4029227D191DE...`
- 安装后离线冒烟测试通过,未执行真实业务写操作;应用当前未运行。
+53 -16
View File
@@ -12,6 +12,23 @@ from account_log import UI_LIMIT, visible
from follow_user import validate_uid
NOTICE_KIND_LABELS = {
"digg": "点赞",
"follow": "关注",
"comment": "评论",
"favorite": "收藏",
"share": "分享",
"general_notice": "其他作品互动",
}
NOTICE_KINDS = tuple(NOTICE_KIND_LABELS)
def rule_matches_kind(kinds, kind):
return kind in kinds or (
kind in {"favorite", "share"} and "general_notice" in kinds
)
def validate_rule(rule):
rule = dict(rule)
rule["require_follow"] = False
@@ -20,9 +37,7 @@ def validate_rule(rule):
rule.setdefault("work_ids", [])
rule.setdefault("works_refresh_interval", 3600)
kinds = rule.get("kinds", [])
if not isinstance(kinds, list) or any(
k not in ("digg", "follow", "comment", "general_notice") for k in kinds
):
if not isinstance(kinds, list) or any(k not in NOTICE_KINDS for k in kinds):
raise ValueError("请选择有效通知类型")
for flag in ("enabled", "follow", "dm", "require_follow"):
if type(rule.get(flag)) is not bool:
@@ -83,7 +98,14 @@ def notice_work_id(notice):
values = [notice]
values.extend(
notice.get(key)
for key in ("comment", "digg", "favorite", "collect", "general_notice")
for key in (
"comment",
"digg",
"favorite",
"collect",
"share",
"general_notice",
)
)
for value in values:
if not isinstance(value, dict):
@@ -106,12 +128,23 @@ def notice_kind(notice):
return "follow"
if notice.get("digg"):
return "digg"
if notice.get("favorite") or notice.get("collect"):
return "favorite"
if notice.get("share"):
return "share"
return "general_notice"
def notice_preview(notice):
kind = notice_kind(notice)
detail = notice.get(kind) or notice.get("favorite") or notice.get("collect") or {}
detail = (
notice.get(kind)
or notice.get("favorite")
or notice.get("collect")
or notice.get("share")
or notice.get("general_notice")
or {}
)
users = detail.get("from_user") or []
if isinstance(users, dict):
users = [users]
@@ -568,9 +601,10 @@ class Store:
selected = set(rule["work_ids"])
result = []
for row in rows:
kind = notice_kind(decode(row["business"]))
if (
rule["work_mode"] == "selected"
and row["kind"] != "follow"
and kind != "follow"
and row["work_id"] not in selected
):
continue
@@ -578,7 +612,7 @@ class Store:
{
"nid": row["nid"],
"create_time": row["create_time"],
"kind": row["kind"],
"kind": kind,
"work_id": row["work_id"],
"work_desc": row["work_desc"],
"actor_uids": decode(row["actor_uids"]),
@@ -734,7 +768,12 @@ class Store:
self.cache_notices(source, [notice], origin)
kind = notice_kind(notice)
detail = (
notice.get(kind) or notice.get("favorite") or notice.get("collect") or {}
notice.get(kind)
or notice.get("favorite")
or notice.get("collect")
or notice.get("share")
or notice.get("general_notice")
or {}
)
users = detail.get("from_user") or []
if isinstance(users, dict):
@@ -750,7 +789,10 @@ class Store:
or work_id in rule["work_ids"]
)
enabled = (
rule["enabled"] and kind in rule["kinds"] and bool(targets) and work_allowed
rule["enabled"]
and rule_matches_kind(rule["kinds"], kind)
and bool(targets)
and work_allowed
)
created = notice.get("create_time") or notice.get("createTime") or time.time()
if type(created) not in (int, float) or created <= 0:
@@ -796,7 +838,7 @@ class Store:
else "规则未启用,本通知不生成任务"
if not rule["enabled"]
else "通知类型未勾选,本通知不生成任务"
if kind not in rule["kinds"]
if not rule_matches_kind(rule["kinds"], kind)
else "未提取到有效通知来源用户,不生成任务"
if not targets
else "通知不属于当前选择的作品,不生成任务"
@@ -808,12 +850,7 @@ class Store:
nid=nid,
event=event["id"],
来源="实时通知" if origin == "live" else "历史列表",
通知类型={
"digg": "点赞",
"comment": "评论",
"follow": "关注",
"general_notice": "其他作品互动",
}[kind],
通知类型=NOTICE_KIND_LABELS[kind],
作品ID=work_id,
目标数量=len(targets),
)
+5 -8
View File
@@ -48,7 +48,7 @@ from PySide6.QtWidgets import (
from account_browser import AppLock, browser_executable, default_root, protect_root
from account_engine import Engine
from account_log import UI_LIMIT, DailyLog
from account_store import decode, validate_rule
from account_store import NOTICE_KIND_LABELS, decode, validate_rule
IMAGE_HOST_SUFFIXES = (
@@ -595,12 +595,7 @@ class Window(QMainWindow):
def history_dialog(self, payload):
items = payload["items"]
labels = {
"digg": "点赞/作品互动",
"comment": "评论",
"follow": "关注",
"general_notice": "其他作品互动",
}
labels = NOTICE_KIND_LABELS
win = QDialog(self)
win.setWindowTitle(f"历史事件预览 — 当前可选 {len(items)} 条(尚未创建任务)")
win.resize(1500, 800)
@@ -1045,7 +1040,9 @@ class Window(QMainWindow):
("digg", "点赞"),
("follow", "关注"),
("comment", "评论"),
("general_notice", "收藏/其他作品互动(仅有明确来源 UID 时)"),
("favorite", "收藏"),
("share", "分享"),
("general_notice", "其他作品互动(仅有明确来源 UID 时)"),
]:
check = QCheckBox(label)
check.setChecked(kind in rule["kinds"])
+8 -3
View File
@@ -10,6 +10,7 @@ from datetime import datetime, timedelta, timezone
from pathlib import Path
from urllib.parse import quote, urlencode
from account_store import notice_kind
from cdp_explicit import add_cdp_args, configure, evaluate_script
from get_current_user import get_user_from_browser
@@ -54,10 +55,14 @@ def get_notifications(max_time: int = 0) -> dict:
def summarize(notice: dict) -> dict:
kind = next(
(k for k in ("digg", "follow", "comment") if notice.get(k)), "general_notice"
kind = notice_kind(notice)
detail = (
notice.get(kind)
or notice.get("favorite")
or notice.get("collect")
or notice.get("share")
or {}
)
detail = notice.get(kind) or {}
users = detail.get("from_user") or []
if isinstance(users, dict):
users = [users]
+20 -1
View File
@@ -19,7 +19,13 @@ from account_browser import (
)
from account_engine import Engine
from account_session import Session, SessionError, validate_endpoint
from account_store import DEFAULT_RULE, Store, validate_rule
from account_store import (
DEFAULT_RULE,
Store,
notice_kind,
notice_preview,
validate_rule,
)
from build_windows import extract_browser
@@ -63,6 +69,19 @@ class StoreTest(unittest.TestCase):
self.store.close()
self.temp.cleanup()
def test_notification_kinds_split_favorite_and_share(self):
for raw_kind, expected in (
("digg", "digg"),
("comment", "comment"),
("favorite", "favorite"),
("collect", "favorite"),
("share", "share"),
):
raw = work_notice("101", raw_kind, "902", "700", raw_kind)
self.assertEqual(notice_kind(raw), expected)
self.assertEqual(notice_preview(raw)["kind"], expected)
validate_rule(rule(kinds=["favorite", "share"]))
def test_defaults_validation_unique_identity(self):
with self.assertRaises(sqlite3.IntegrityError):
self.store.add("worker", "重复", "101")