record_session.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. """Record a full game session to JSONL for offline replay.
  2. USAGE:
  3. # Password login (recommended)
  4. uv run python scripts/record_session.py --phone <account> --password <pwd>
  5. # SMS login
  6. uv run python scripts/record_session.py --phone <phone> --sms-code <code>
  7. # From .env
  8. uv run python scripts/record_session.py
  9. Runs the full pre-game + 13-task flow and logs every send/recv to
  10. tests/recorded/session_<timestamp>.jsonl. Use the recorded file with
  11. `MockTcpClient` to write tests that exercise task decision logic
  12. without a live game server.
  13. NOTE: This script requires a live 996box/cq2 server and valid
  14. account credentials. Use it after verifying the SDK against
  15. production at least once with `smoke.py`.
  16. """
  17. import argparse
  18. import asyncio
  19. import os
  20. import sys
  21. from datetime import datetime
  22. from pathlib import Path
  23. sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, "src"))
  24. async def main() -> int:
  25. parser = argparse.ArgumentParser(description="cq2_sdk session recorder")
  26. parser.add_argument("--phone", help="Phone number or account name (CQ2_PHONE or CQ2_ACCOUNT)")
  27. parser.add_argument("--password", help="Account password (CQ2_PASSWORD)")
  28. parser.add_argument("--sms-code", help="SMS verification code (CQ2_SMS_CODE)")
  29. parser.add_argument("--lua-path", help="Path to decrypted client Lua (CQ2_LUA_PATH)")
  30. args = parser.parse_args()
  31. from cq2_sdk.env_loader import load_env
  32. load_env()
  33. phone = args.phone or os.environ.get("CQ2_PHONE") or os.environ.get("CQ2_ACCOUNT")
  34. password = args.password or os.environ.get("CQ2_PASSWORD")
  35. sms_code = args.sms_code or os.environ.get("CQ2_SMS_CODE")
  36. lua_path = args.lua_path or os.environ.get("CQ2_LUA_PATH")
  37. if not phone:
  38. print("error: --phone (or CQ2_PHONE/CQ2_ACCOUNT env) required", file=sys.stderr)
  39. return 1
  40. if not password and not sms_code:
  41. print("error: need --password OR --sms-code", file=sys.stderr)
  42. return 1
  43. from cq2_sdk.client import Cq2Client
  44. from cq2_sdk.testing import Recorder
  45. out_dir = Path("tests/recorded")
  46. out_dir.mkdir(parents=True, exist_ok=True)
  47. out = out_dir / f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jsonl"
  48. print(f"[record] writing to {out}")
  49. async with Cq2Client(
  50. phone=phone,
  51. password=password,
  52. sms_code=sms_code,
  53. lua_path=lua_path,
  54. http_secret=os.environ.get("CQ2_SIGNKEY", ""),
  55. sign_version=2,
  56. ) as cli:
  57. # run_until_enter_game() first — it sets up TCP + task context
  58. await cli.launcher.run_until_enter_game(server_name="玖扬复古176", zone_index=0)
  59. # Then create the recorder (now cli.task.ctx.tcp exists)
  60. assert cli.task is not None
  61. recorder = Recorder(cli.task.ctx.tcp, out)
  62. recorder.install()
  63. try:
  64. result = await cli.run_newbie_flow()
  65. finally:
  66. recorder.close()
  67. print(f"[record] done: {len(result.completed_tasks)} tasks in {result.elapsed:.1f}s")
  68. return 0
  69. if __name__ == "__main__":
  70. sys.exit(asyncio.run(main()))