feat(web): up avatars, follower count, and dedupe views/avg secondary
This commit is contained in:
1 parent
1ed3746f4b
commit
2ff3e7952b
23 files changed
+209
-58
No files matched your search
+1
-1
@@ -182,7 +182,7 @@ VIEW_PAYLOAD: dict[str, Any] = {
|
||||
{"cid": 43184885058, "page": 1, "from": "vupload", "part": "第一部分"},
|
||||
{"cid": 43184885059, "page": 2, "from": "vupload", "part": "第二部分"},
|
||||
],
|
||||
"owner": {"mid": 434442072, "name": "_-抱抱-_", "face": ""},
|
||||
"owner": {"mid": 434442072, "name": "_-抱抱-_", "face": "http://i1.hdslb.com/bfs/face/example.jpg"},
|
||||
"stat": {
|
||||
"aid": 117530286889638,
|
||||
"view": 819,
|
||||
|
||||
@@ -353,6 +353,7 @@ def test_view_detail_by_aid_and_bvid():
|
||||
assert detail.pages == 2
|
||||
assert detail.cover and detail.cover.startswith("http")
|
||||
assert detail.metrics()["view"] == 819
|
||||
assert detail.up_face and detail.up_face.startswith("http")
|
||||
assert "aid=117530286889638" in transport.calls[0]["url"]
|
||||
|
||||
transport = QueueTransport([http_json(VIEW_PAYLOAD)])
|
||||
@@ -373,6 +374,7 @@ def test_view_detail_fallbacks_and_required_arg():
|
||||
assert detail.duration == 0
|
||||
assert detail.pages is None
|
||||
assert detail.cover is None
|
||||
assert detail.up_face is None
|
||||
|
||||
client, _ = make_client(QueueTransport([]))
|
||||
for kwargs in ({}, {"aid": 1, "bvid": "BV1xvxi6LEZD"}):
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ def test_init_and_status():
|
||||
path = write_config(tmp)
|
||||
code, output = run_cli(["init", "--config", str(path)])
|
||||
assert code == 0
|
||||
assert '"schema_version": 5' in output
|
||||
assert '"schema_version": 6' in output
|
||||
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
|
||||
@@ -24,7 +24,7 @@ def test_write_read_transaction():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db = make_db(tmp)
|
||||
try:
|
||||
assert db.schema_version == 5
|
||||
assert db.schema_version == 6
|
||||
db.write(lambda c: c.execute("INSERT INTO kv(k, v) VALUES ('a', '1')"))
|
||||
assert db.read(lambda c: c.execute("SELECT v FROM kv WHERE k='a'").fetchone()[0]) == "1"
|
||||
|
||||
@@ -94,8 +94,8 @@ def test_close_and_reinitialize():
|
||||
|
||||
db2 = make_db(tmp)
|
||||
try:
|
||||
assert db2.schema_version == 5
|
||||
assert db2.read(lambda c: c.execute("SELECT MAX(version) FROM schema_version").fetchone()[0]) == 5
|
||||
assert db2.schema_version == 6
|
||||
assert db2.read(lambda c: c.execute("SELECT MAX(version) FROM schema_version").fetchone()[0]) == 6
|
||||
finally:
|
||||
db2.close()
|
||||
|
||||
@@ -123,7 +123,7 @@ def test_migration_backup_on_existing_db():
|
||||
db = Database(path, backup_dir=Path(tmp) / "backup")
|
||||
try:
|
||||
applied = db.initialize()
|
||||
assert applied == [1, 2, 3, 4, 5]
|
||||
assert applied == [1, 2, 3, 4, 5, 6]
|
||||
assert len(list((Path(tmp) / "backup").glob("app-pre-v0-*.db"))) == 1
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -45,7 +45,7 @@ def detail(
|
||||
data["pubdate"] = pubdate
|
||||
data["ctime"] = pubdate
|
||||
data["duration"] = duration
|
||||
data["owner"] = {"mid": up_mid, "name": up_name, "face": ""}
|
||||
data["owner"] = {"mid": up_mid, "name": up_name, "face": "http://i1.hdslb.com/bfs/face/u.jpg"}
|
||||
data["pages"] = [{"cid": 1, "page": i + 1} for i in range(pages)]
|
||||
return VideoDetail.from_api(data)
|
||||
|
||||
@@ -150,6 +150,7 @@ def test_ingest_candidate_added_and_snapshot():
|
||||
assert video["source"] == "candidate:telegram"
|
||||
assert video["folder_id"] is None
|
||||
assert video["up_mid"] == 100
|
||||
assert video["up_face"] == "http://i1.hdslb.com/bfs/face/u.jpg"
|
||||
snap = repo.get_snapshot(DATE, 9)
|
||||
assert snap is not None and snap["ok"] == 1 and snap["view"] == 819
|
||||
assert ups.calls == [([100], {100: "UP100"})]
|
||||
|
||||
@@ -37,8 +37,8 @@ def test_fresh_and_idempotent():
|
||||
conn = sqlite3.connect(Path(tmp) / "app.db")
|
||||
try:
|
||||
applied = run_migrations(conn)
|
||||
assert applied == [1, 2, 3, 4, 5]
|
||||
assert current_version(conn) == 5
|
||||
assert applied == [1, 2, 3, 4, 5, 6]
|
||||
assert current_version(conn) == 6
|
||||
assert EXPECTED_TABLES <= table_names(conn)
|
||||
assert "schema_version" in table_names(conn)
|
||||
columns = {
|
||||
@@ -54,7 +54,7 @@ def test_fresh_and_idempotent():
|
||||
}
|
||||
assert {
|
||||
"pages", "duration", "up_follower", "up_follower_at",
|
||||
"up_follower_backfilled",
|
||||
"up_follower_backfilled", "up_face",
|
||||
} <= video_columns
|
||||
profile_columns = {
|
||||
row[1] for row in conn.execute("PRAGMA table_info(up_profile)")
|
||||
@@ -66,7 +66,7 @@ def test_fresh_and_idempotent():
|
||||
# 再跑一次不应重复应用
|
||||
assert run_migrations(conn) == []
|
||||
row = conn.execute("SELECT COUNT(*) FROM schema_version").fetchone()
|
||||
assert row[0] == 5
|
||||
assert row[0] == 6
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -81,15 +81,15 @@ def test_backup_before_upgrade():
|
||||
conn.execute("INSERT INTO kv(k, v) VALUES ('hello', 'world')")
|
||||
conn.commit()
|
||||
|
||||
v6 = Migration(6, "add probe", ("CREATE TABLE probe (id INTEGER PRIMARY KEY)",))
|
||||
v7 = Migration(7, "add probe", ("CREATE TABLE probe (id INTEGER PRIMARY KEY)",))
|
||||
applied = run_migrations(
|
||||
conn,
|
||||
backup_dir=backup_dir,
|
||||
now=1_800_000_000,
|
||||
migrations=MIGRATIONS + (v6,),
|
||||
migrations=MIGRATIONS + (v7,),
|
||||
)
|
||||
assert applied == [6]
|
||||
backups = list(backup_dir.glob("app-pre-v5-*.db"))
|
||||
assert applied == [7]
|
||||
backups = list(backup_dir.glob("app-pre-v6-*.db"))
|
||||
assert len(backups) == 1
|
||||
check = sqlite3.connect(backups[0])
|
||||
try:
|
||||
@@ -174,7 +174,7 @@ def test_v5_normalizes_unfaved():
|
||||
)
|
||||
conn.commit()
|
||||
applied = run_migrations(conn)
|
||||
assert applied == [5]
|
||||
assert applied == [5, 6]
|
||||
rows = dict(conn.execute("SELECT aid, status FROM video").fetchall())
|
||||
assert rows == {1: "active", 2: "dropped"}
|
||||
finally:
|
||||
|
||||
@@ -153,6 +153,9 @@ def test_snapshot_success_resume_and_force():
|
||||
assert (row["view"], row["like"], row["favorite"], row["coin"],
|
||||
row["share"], row["reply"], row["danmaku"]) == (100, 10, 3, 2, 1, 4, 5)
|
||||
assert row["ok"] == 1 and row["src"] == "view"
|
||||
assert repo.get_video(1)["up_face"] == (
|
||||
"http://i1.hdslb.com/bfs/face/example.jpg"
|
||||
)
|
||||
fan = repo.get_up_stat(DATE, 37754047)
|
||||
assert fan["follower"] == 2767268 and fan["ok"] == 1
|
||||
assert repo.kv_get(service.completed_key(DATE)) == "1"
|
||||
|
||||
@@ -182,6 +182,8 @@ def test_up_queries():
|
||||
assert ups["items"][0]["video_count"] == 2
|
||||
assert ups["items"][0]["total_views"] == 6400
|
||||
assert ups["items"][0]["avg_views"] == 3200.0
|
||||
assert ups["items"][0]["up_face"] == "https://i0.hdslb.com/bfs/face/a.jpg"
|
||||
assert ups["items"][0]["follower_latest"] == 12345
|
||||
|
||||
one = q.list_ups(min_videos=2)
|
||||
assert one["total"] == 1
|
||||
@@ -190,6 +192,7 @@ def test_up_queries():
|
||||
assert detail["video_count"] == 2
|
||||
assert detail["total_views"] == 6400
|
||||
assert detail["blacklisted"] is False
|
||||
assert detail["up_face"] == "https://i0.hdslb.com/bfs/face/a.jpg"
|
||||
assert detail["series"]["dates"] == list(DATES)
|
||||
assert detail["series"]["views"] == [6000, 6200, 6400]
|
||||
assert detail["space_url"].endswith("/1")
|
||||
@@ -454,7 +457,7 @@ def test_about_payload():
|
||||
assert about["snapshot_days"] == 3
|
||||
assert about["snapshot_first_date"] == DATES[0]
|
||||
assert about["fans_start_date"] == DATES[0]
|
||||
assert about["schema_version"] == 5
|
||||
assert about["schema_version"] == 6
|
||||
assert about["analytics"] == {
|
||||
"min_duration_seconds": 600,
|
||||
"maturity_days": 2,
|
||||
@@ -464,8 +467,8 @@ def test_about_payload():
|
||||
}
|
||||
assert about["heat_weights"]["view"] == 0.30
|
||||
assert about["followers"]["videos_total"] == 3
|
||||
assert about["followers"]["videos_covered"] == 0
|
||||
assert about["followers"]["videos_missing"] == 3
|
||||
assert about["followers"]["videos_covered"] == 3
|
||||
assert about["followers"]["videos_missing"] == 0
|
||||
assert about["folders"][0]["media_id"] == 3700023351
|
||||
assert about["preview"] is False
|
||||
finally:
|
||||
|
||||
@@ -75,7 +75,7 @@ def test_readonly_routes_and_static():
|
||||
assert status == 200 and payload["ok"] is True
|
||||
status, _, payload = running.json("/api/about")
|
||||
assert status == 200
|
||||
assert payload["data"]["schema_version"] == 5
|
||||
assert payload["data"]["schema_version"] == 6
|
||||
assert payload["data"]["analytics"]["min_duration_seconds"] == 600
|
||||
|
||||
status, _, overview = running.json("/api/overview")
|
||||
|
||||
@@ -106,6 +106,16 @@ def build_db(tmp: str | Path) -> Path:
|
||||
)
|
||||
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},
|
||||
|
||||
Reference in new issue
Block a user