feat: split history interaction event types
This commit is contained in:
@@ -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
@@ -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
@@ -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"])
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user