b1_find_server.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. """B1: 按名称查区服 — 调 list-box 拉全部 serverlist, 匹配后存进 MySQL servers 表。
  2. 数据流:
  3. .env / --name "玖扬复古176"
  4. ↓
  5. fetch_srvlist(206923) → GET list-box.dhsf.xqhuyu.com/box_serverlist/206923.txt
  6. ↓
  7. 遍历 serverlist, 找 list_name 包含 --name 的 group
  8. ↓
  9. 取 group 的 main server (serverid + engin_id)
  10. ↓
  11. ServerStore.upsert(server_name, server_id, game_id)
  12. ↓
  13. DebugLogger 留痕
  14. 跑法:
  15. uv run python scripts/business/b1_find_server.py
  16. uv run python scripts/business/b1_find_server.py --name "玖扬复古176"
  17. uv run python scripts/business/b1_find_server.py --game-id 999999
  18. uv run python scripts/business/b1_find_server.py --check
  19. """
  20. from __future__ import annotations
  21. import argparse
  22. import asyncio
  23. import os
  24. import sys
  25. from pathlib import Path
  26. from typing import Any
  27. _HERE = Path(__file__).resolve().parent
  28. _PROJECT = _HERE.parent.parent
  29. sys.path.insert(0, str(_PROJECT / "src"))
  30. from cq2_sdk.env_loader import load_env # noqa: E402
  31. load_env(_PROJECT / ".env")
  32. from cq2_sdk.debug_log import DebugLogger # noqa: E402
  33. from cq2_sdk.launcher.navigate import ServerNotFound, fetch_srvlist # noqa: E402
  34. from cq2_sdk.store import ServerStore, connection # noqa: E402
  35. DEFAULT_GAME_ID = 206923
  36. DEFAULT_SERVER_NAME = "玖扬复古176" # 当前的"主"区服,未来可改
  37. def pick_server_from_srvlist(srvlist: dict, name: str) -> dict[str, Any]:
  38. """Find the first server group whose list_name contains ``name``.
  39. Returns a dict with: server_id, group_id, group_name, engin_id.
  40. Raises ServerNotFound if no match.
  41. """
  42. for gid_str, group in srvlist.items():
  43. group_name = group.get("list_name", "") or ""
  44. if name in group_name or group_name == name:
  45. tree = group.get("tree") or []
  46. if not tree:
  47. continue
  48. main_id = group.get("main_serverid")
  49. target = next(
  50. (s for s in tree if s.get("serverid") == main_id),
  51. tree[0],
  52. )
  53. return {
  54. "server_id": int(target.get("serverid", 0)),
  55. "group_id": int(gid_str),
  56. "group_name": group_name,
  57. "engin_id": target.get("engin_id"),
  58. }
  59. raise ServerNotFound(f"server not found: {name!r}")
  60. async def find_and_save(
  61. name: str,
  62. game_id: int,
  63. log: DebugLogger,
  64. ) -> dict[str, Any]:
  65. """Run B1: fetch srvlist, match by name, persist to MySQL.
  66. Returns the picked server info dict.
  67. """
  68. log.step("b1_find_server_start", target=name, game_id=game_id)
  69. # 1. fetch (no auth needed for browsing the public server list)
  70. log.info("fetching_srvlist", game_id=game_id)
  71. srvlist = await fetch_srvlist(str(game_id))
  72. log.info("srvlist_fetched", group_count=len(srvlist))
  73. # 2. match
  74. picked = pick_server_from_srvlist(srvlist, name)
  75. log.info("server_matched", **picked)
  76. # 3. persist
  77. log.step("b1_persist_to_mysql", server_name=name, server_id=picked["server_id"])
  78. with connection() as conn:
  79. servers = ServerStore(conn)
  80. server = servers.upsert(
  81. server_name=name,
  82. server_id=picked["server_id"],
  83. game_id=game_id,
  84. )
  85. log.info("server_saved_to_mysql",
  86. db_id=server.id, server_name=server.server_name,
  87. server_id=server.server_id)
  88. return {
  89. **picked,
  90. "db_id": server.id,
  91. }
  92. def check_config(name: str | None, game_id: int) -> bool:
  93. print("[check] 配置检查:")
  94. print(f" target server name = {name!r} [OK]" if name else
  95. " target server name = (未设置) [FAIL]")
  96. print(f" game_id = {game_id} [OK]")
  97. return bool(name)
  98. def parse_args() -> argparse.Namespace:
  99. p = argparse.ArgumentParser(
  100. description="B1: 按名称查 996box 区服, 存进 MySQL servers 表",
  101. formatter_class=argparse.RawDescriptionHelpFormatter,
  102. epilog=(
  103. "示例:\n"
  104. " uv run python scripts/business/b1_find_server.py --check\n"
  105. " uv run python scripts/business/b1_find_server.py\n"
  106. " uv run python scripts/business/b1_find_server.py --name 复古180 --game-id 999\n"
  107. ),
  108. )
  109. p.add_argument(
  110. "--name",
  111. default=os.environ.get("CQ2_DEFAULT_SERVER", DEFAULT_SERVER_NAME),
  112. help=f"区服名 (默认 {DEFAULT_SERVER_NAME},可设 .env CQ2_DEFAULT_SERVER)",
  113. )
  114. p.add_argument(
  115. "--game-id",
  116. type=int,
  117. default=int(os.environ.get("CQ2_GAME_ID", DEFAULT_GAME_ID)),
  118. help=f"game id (默认 {DEFAULT_GAME_ID})",
  119. )
  120. p.add_argument(
  121. "--check",
  122. action="store_true",
  123. help="只检查 .env / CLI 配置和 import 依赖, 不实际调 list-box",
  124. )
  125. return p.parse_args()
  126. def main() -> int:
  127. args = parse_args()
  128. if args.check:
  129. ok = check_config(args.name, args.game_id)
  130. print(f"\n result: {'READY' if ok else 'NOT READY'}")
  131. return 0 if ok else 1
  132. if not args.name:
  133. print("[FAIL] 缺少 server name")
  134. return 1
  135. with DebugLogger(
  136. extra={"task": "b1", "server_name": args.name, "game_id": args.game_id}
  137. ) as log:
  138. log.info("b1_start", name=args.name, game_id=args.game_id)
  139. try:
  140. picked = asyncio.run(find_and_save(args.name, args.game_id, log))
  141. except ServerNotFound as e:
  142. log.error(e, step="b1_match_failed")
  143. print(f"\n[FAIL] {e}")
  144. print(f" 提示: --name 不在 list-box 返回的区服列表里")
  145. print(f" log file: {log.path}")
  146. return 2
  147. except Exception as e:
  148. log.error(e, step="b1_unexpected")
  149. print(f"\n[FAIL] {type(e).__name__}: {e}")
  150. print(f" log file: {log.path}")
  151. return 1
  152. print("\n[OK] B1 查区服 + 落库成功:")
  153. print(f" server_name = {picked['group_name']!r} (996box 列表里的全名)")
  154. print(f" server_id = {picked['server_id']} (996box 数字 ID)")
  155. print(f" group_id = {picked['group_id']}")
  156. print(f" engin_id = {picked.get('engin_id')}")
  157. print(f" db_id = {picked['db_id']} (MySQL servers.id)")
  158. print(f" log file = {log.path}")
  159. return 0
  160. if __name__ == "__main__":
  161. sys.exit(main())