Split /system schedule into fans / snapshot+UP / discovery next-runs. Drive data_date and snapshot_days from fully completed video snapshots only, keep prior success date on failure, show task failure records, and mark in-progress tasks as 运行中 without wall-clock next-run jumps.
204 lines
7.1 KiB
Python
204 lines
7.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Web 查询/服务测试共用的临时数据库与配置。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import date
|
|
from pathlib import Path
|
|
|
|
from reaction_board.bilibili.models import FavResource
|
|
from reaction_board.config import FolderConfig
|
|
from reaction_board.database import Database
|
|
from reaction_board.repository import Repository
|
|
from reaction_board.timeutil import day_bounds
|
|
|
|
FOLDER = FolderConfig(year=2026, media_id=3700023351, title="2026")
|
|
DATES = ("2026-10-29", "2026-10-30", "2026-10-31")
|
|
|
|
|
|
def write_config(
|
|
tmp: str | Path,
|
|
*,
|
|
token: str = "",
|
|
host: str = "127.0.0.1",
|
|
port: int = 8080,
|
|
) -> Path:
|
|
root = Path(tmp)
|
|
path = root / "config.json"
|
|
path.write_text(json.dumps({
|
|
"bilibili": {
|
|
"read": {"mid": 1, "csrf": "read-csrf", "cookie": "SESSDATA=fake-read"},
|
|
"write": {"mid": 2, "csrf": "write-csrf", "cookie": "SESSDATA=fake-write"},
|
|
},
|
|
"telegram": {"enabled": False, "bot_token": "", "chat_id": 0},
|
|
"web": {
|
|
"host": host, "port": port, "auth_token": token,
|
|
"public_base_url": "",
|
|
},
|
|
"storage": {
|
|
"database_path": str(root / "app.db"),
|
|
"backup_dir": str(root / "backup"),
|
|
"backup_keep_days": 7,
|
|
},
|
|
"folders": [
|
|
{"year": 2026, "media_id": 3700023351, "title": "2026"},
|
|
],
|
|
"track_fans": [37754047],
|
|
"up_blacklist_seed": [],
|
|
"schedule": {
|
|
"discover_minutes": 60,
|
|
"snapshot_at": "00:01",
|
|
"candidate_ttl_days": 7,
|
|
},
|
|
}, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def resource(
|
|
aid: int,
|
|
*,
|
|
up_mid: int,
|
|
up_name: str,
|
|
pubdate: int,
|
|
attr: int = 0,
|
|
title: str | None = None,
|
|
) -> FavResource:
|
|
return FavResource(
|
|
aid=aid,
|
|
bvid=f"BV{aid}",
|
|
title=title or f"咻咻满 reaction 视频 {aid}",
|
|
up_mid=up_mid,
|
|
up_name=up_name,
|
|
pubdate=pubdate,
|
|
duration=7200,
|
|
cover=f"//i0.hdslb.com/{aid}.jpg",
|
|
attr=attr,
|
|
fav_time=None,
|
|
)
|
|
|
|
|
|
def build_db(tmp: str | Path) -> Path:
|
|
root = Path(tmp)
|
|
path = root / "app.db"
|
|
db = Database(path)
|
|
db.initialize()
|
|
try:
|
|
repo = Repository(db)
|
|
repo.ensure_folder(FOLDER)
|
|
repo.seed_blacklist([2])
|
|
|
|
def ts(day: str) -> int:
|
|
return int(day_bounds(day)[0])
|
|
|
|
videos = {
|
|
101: (1, "UP A", int(day_bounds("2026-10-30")[0]), 0, "2026-10-29"),
|
|
102: (1, "UP A", int(day_bounds("2026-10-20")[0]), 0, "2026-10-29"),
|
|
103: (2, "UP B", int(day_bounds("2026-10-31")[0]), 0, "2026-10-31"),
|
|
104: (3, "UP C", int(day_bounds("2026-09-01")[0]), 9, "2026-10-29"),
|
|
}
|
|
for aid, (up_mid, up_name, pubdate, attr, first_day) in videos.items():
|
|
repo.upsert_from_fav(
|
|
resource(aid, up_mid=up_mid, up_name=up_name, pubdate=pubdate, attr=attr),
|
|
FOLDER.media_id,
|
|
was_added=True,
|
|
now=ts(first_day),
|
|
)
|
|
repo.set_video_status(103, "unfaved")
|
|
|
|
# UP 头像与最新粉丝数(UP 列表/详情展示用)
|
|
repo.apply_up_follower(
|
|
mid=1, name="UP A", follower=12345, following=5, now=ts("2026-10-30")
|
|
)
|
|
repo.apply_up_follower(
|
|
mid=2, name="UP B", follower=678, following=1, now=ts("2026-10-31")
|
|
)
|
|
repo.apply_video_up_face(101, "//i0.hdslb.com/bfs/face/a.jpg")
|
|
repo.apply_video_up_face(103, "//i0.hdslb.com/bfs/face/b.jpg")
|
|
|
|
metrics = {
|
|
101: {"view": 1000, "like": 100, "coin": 20, "favorite": 30, "share": 2, "reply": 10, "danmaku": 12},
|
|
102: {"view": 5000, "like": 200, "coin": 50, "favorite": 100, "share": 10, "reply": 30, "danmaku": 40},
|
|
104: {"view": 50, "like": 1, "coin": 0, "favorite": 0, "share": 0, "reply": 0, "danmaku": 0},
|
|
}
|
|
for index, day in enumerate(DATES):
|
|
for aid in (101, 102):
|
|
base = metrics[aid]
|
|
values = {}
|
|
for key, value in base.items():
|
|
values[key] = value + (index * 100 if key == "view" else index * 2)
|
|
repo.upsert_snapshot(
|
|
day, aid, metrics=values, ok=True, err=None,
|
|
captured_at=ts(day) + 60,
|
|
)
|
|
repo.upsert_up_stat(
|
|
day, 37754047, follower=1000 + index * 10, following=5,
|
|
ok=True, captured_at=ts(day) + 60,
|
|
)
|
|
# 103 只有最后一天快照,用于验证样本不足;104 另加一条失败快照
|
|
repo.upsert_snapshot(
|
|
DATES[-1], 103,
|
|
metrics={"view": 120, "like": 5, "coin": 1, "favorite": 2, "share": 0, "reply": 1, "danmaku": 1},
|
|
ok=True, err=None, captured_at=ts(DATES[-1]) + 60,
|
|
)
|
|
repo.upsert_snapshot(
|
|
"2026-10-30", 104,
|
|
metrics=None, ok=False, err="simulated failure",
|
|
captured_at=ts("2026-10-30") + 60,
|
|
)
|
|
|
|
repo.upsert_candidate(
|
|
aid=201, bvid="BV201", title="咻咻满 reaction 候选",
|
|
up_mid=9, up_name="候选UP", pubdate=1_790_000_000,
|
|
cover=None, score=6, channel="search:test#p1",
|
|
now=ts("2026-10-30"), play=1000, like=50,
|
|
)
|
|
repo.mark_notified(
|
|
201, channel="telegram", batch_id="b1", ok=True,
|
|
now=ts("2026-10-30") + 10,
|
|
)
|
|
repo.upsert_candidate(
|
|
aid=202, bvid="BV202", title="咻咻满 切片候选",
|
|
up_mid=10, up_name="切片UP", pubdate=1_790_000_000,
|
|
cover=None, score=1, channel="search:test#p1",
|
|
now=ts("2026-10-29"),
|
|
)
|
|
repo.mark_candidate_decision(202, "rejected", now=ts("2026-10-30"))
|
|
repo.finish_folder_sync(FOLDER, media_count=3, full_refresh=True)
|
|
repo.kv_set("snapshot:last_completed", DATES[-1])
|
|
for day in DATES:
|
|
repo.kv_set(f"snapshot:completed:{day}", "1")
|
|
repo.kv_set("auth:ok", "1")
|
|
repo.kv_set("auth:message", "OK")
|
|
repo.kv_set(
|
|
"task:last:snapshot",
|
|
json.dumps({
|
|
"name": "snapshot", "ok": True,
|
|
"started_at": ts(DATES[-1]), "finished_at": ts(DATES[-1]) + 60,
|
|
"error": None, "summary": {"completed": True},
|
|
}),
|
|
)
|
|
repo.kv_set(
|
|
"task:last:fans",
|
|
json.dumps({
|
|
"name": "fans", "ok": True,
|
|
"started_at": ts(DATES[-1]) + 60, "finished_at": ts(DATES[-1]) + 90,
|
|
"error": None, "summary": {"fans_ok": 1, "fans_failed": 0},
|
|
}),
|
|
)
|
|
repo.kv_set(
|
|
"task:last:discovery",
|
|
json.dumps({
|
|
"name": "discovery", "ok": True,
|
|
"started_at": ts(DATES[-1]) - 3600, "finished_at": ts(DATES[-1]) - 3500,
|
|
"error": None, "summary": {"candidates": 0},
|
|
}),
|
|
)
|
|
finally:
|
|
db.close()
|
|
return path
|
|
|
|
|
|
def day_date(value: str) -> date:
|
|
return date.fromisoformat(value)
|