| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- # -*- coding: utf-8 -*-
- """每日 00:00 调度: 遍历所有绑定主账号的区服, 用其 token 调 getTodayOpen, 写入 zones 表。
- 调度方式 (任选其一):
- - Windows 任务计划程序: 每天 00:00:30 触发
- schtasks /Create /SC DAILY /TN "996cq_zone_sync" /TR "python D:\\传奇\\tools\\daily_zone_sync.py" /ST 00:00:30
- - cron: 30 0 * * * python /path/to/daily_zone_sync.py
- - 手动: python tools/daily_zone_sync.py
- 依赖:
- - B1 已跑过 (servers 在库)
- - server_users 已有 is_main=1 的绑定
- - 主账号 token 在 users.token (新跑 A1 后不超过 30 天)
- """
- import argparse
- import asyncio
- import sys
- from datetime import datetime
- from pathlib import Path
- _HERE = Path(__file__).resolve().parent
- sys.path.insert(0, str(_HERE.parent / "cq2_sdk" / "src"))
- sys.path.insert(0, str(_HERE / "已经开发的部分代码" / "chuanqi-main" / "code" / "GeekedTest"))
- # 加载 cq2_sdk/.env (CQ2_DB_* 等) —— 必须先 import env_loader
- from cq2_sdk.env_loader import load_env # noqa: E402
- load_env(_HERE.parent / "cq2_sdk" / ".env")
- from cq2_sdk.db_models import ServerUser, Zone
- from cq2_sdk.login import Box996Login, ManualGeetestSolver
- from cq2_sdk.login.box_996 import TodayOpenServer
- from cq2_sdk.store import (
- ServerStore, ServerUserStore, UserStore, ZoneStore, connection,
- )
- DEFAULT_DEVICE_ID = "00:15:5d:0a:04:00"
- def api_zone_to_db_zone(api: TodayOpenServer) -> Zone:
- """996box API → Zone, 拆分 date + time-of-day, 按 state 区分 status。"""
- open_time = datetime.fromtimestamp(api.open_time) if api.open_time else None
- if api.state == 1:
- status = "open"
- elif api.state == 5:
- status = "scheduled"
- else:
- status = "closed"
- return Zone(
- zone_id=str(api.serverid),
- zone_name=(f"{api.server_name} / {api.section.strip()}"
- if api.section else api.server_name),
- host=None,
- port=None,
- open_time=open_time,
- open_date=open_time.date() if open_time else None,
- open_time_of_day=open_time.time() if open_time else None,
- api_state=api.state,
- api_section=(api.section or "").strip() or None,
- status=status,
- )
- async def sync_one(su: ServerUser) -> dict:
- """用主账号 token 调 getTodayOpen 并入库。返回结果摘要。"""
- with connection() as conn:
- user = UserStore(conn).get_by_username(su.username)
- if not user or not user.token:
- return {"server": su.server_name, "user": su.username,
- "status": "FAIL", "msg": "no token in MySQL"}
- server = ServerStore(conn).get_by_name(su.server_name)
- if not server:
- return {"server": su.server_name, "user": su.username,
- "status": "FAIL", "msg": "server not in MySQL"}
- # 调 API
- try:
- box = Box996Login(geetest=ManualGeetestSolver(),
- device_id=DEFAULT_DEVICE_ID)
- box.token = user.token
- api_zones = await box.get_today_open(game_id=server.game_id)
- except Exception as e:
- return {"server": su.server_name, "user": su.username,
- "status": "FAIL", "msg": f"API error: {type(e).__name__}: {e}"}
- # 入库
- db_zones = [api_zone_to_db_zone(z) for z in api_zones]
- zones = ZoneStore(conn)
- n_upserted = zones.bulk_upsert(server.id, db_zones)
- fresh_ids = {z.zone_id for z in db_zones}
- n_closed = zones.mark_missing_closed(server.id, fresh_ids)
- return {
- "server": su.server_name, "user": su.username,
- "status": "OK",
- "api_total": len(api_zones),
- "upserted": n_upserted,
- "closed": n_closed,
- "open_count": sum(1 for z in db_zones if z.status == "open"),
- "scheduled_count": sum(1 for z in db_zones if z.status == "scheduled"),
- }
- async def main() -> int:
- ap = argparse.ArgumentParser()
- ap.add_argument("--server", help="只同步指定区服名 (默认: 所有绑定主账号的区服)")
- ap.add_argument("--dry-run", action="store_true",
- help="列出将同步的区服, 不实际调 API")
- args = ap.parse_args()
- with connection() as conn:
- su_store = ServerUserStore(conn)
- all_sus = su_store.list_servers_with_main()
- if args.server:
- all_sus = [su for su in all_sus if su.server_name == args.server]
- if not all_sus:
- print(f"FAIL: 没有为 '{args.server}' 绑定的主账号。先跑 bind_server_main.py")
- return 1
- print(f"将同步 {len(all_sus)} 个区服:")
- for su in all_sus:
- print(f" - {su.server_name} (主账号: {su.username})")
- if args.dry_run:
- print("\n[DRY-RUN] 不实际执行。")
- return 0
- results = []
- for su in all_sus:
- print(f"\n>>> {su.server_name} (主账号 {su.username})")
- r = await sync_one(su)
- results.append(r)
- for k, v in r.items():
- print(f" {k}: {v}")
- print("\n========== 汇总 ==========")
- for r in results:
- print(f" {r.get('server','?'):<24} {r.get('status','?'):<6} "
- f"api={r.get('api_total','-')} upsert={r.get('upserted','-')} "
- f"open={r.get('open_count','-')} sched={r.get('scheduled_count','-')}")
- return 0 if all(r["status"] == "OK" for r in results) else 1
- if __name__ == "__main__":
- sys.exit(asyncio.run(main()))
|