feat: publish sanitized Linux cloud-game MAA adapter
This commit is contained in:
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Bootstrap a NetEase Cloud Game token without blocking the API server."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
TOKEN_FILE = Path(os.getenv("MAA_ONLINE_TOKEN_FILE", str(BASE_DIR / "token")))
|
||||
PENDING_FILE = Path(os.getenv("MAA_ONLINE_PENDING_LOGIN_FILE", str(BASE_DIR / ".pending-login")))
|
||||
API_BASE = "https://n.cg.163.com/api/v1"
|
||||
|
||||
|
||||
def write_private(path: Path, value: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
fd = os.open(path, flags, 0o600)
|
||||
try:
|
||||
os.write(fd, value.encode("utf-8"))
|
||||
finally:
|
||||
os.close(fd)
|
||||
os.chmod(path, 0o600)
|
||||
|
||||
|
||||
def normalize_phone(phone: str, country_code: str) -> tuple[str, str]:
|
||||
phone = phone.strip().replace(" ", "")
|
||||
country_code = country_code.strip().lstrip("+")
|
||||
if not phone.isdigit() or not country_code.isdigit():
|
||||
raise ValueError("phone and country code must contain digits only")
|
||||
return country_code, phone
|
||||
|
||||
|
||||
def request_code(phone: str, country_code: str) -> None:
|
||||
country_code, phone = normalize_phone(phone, country_code)
|
||||
response = requests.post(
|
||||
f"{API_BASE}/phone-captchas/{country_code}-{phone}",
|
||||
timeout=15,
|
||||
)
|
||||
response.raise_for_status()
|
||||
write_private(PENDING_FILE, json.dumps({"country_code": country_code, "phone": phone}))
|
||||
print("SMS code requested. Run: python login.py verify <code>")
|
||||
|
||||
|
||||
def verify_code(code: str) -> None:
|
||||
if not PENDING_FILE.is_file():
|
||||
raise RuntimeError("no pending login; run the request command first")
|
||||
pending = json.loads(PENDING_FILE.read_text(encoding="utf-8"))
|
||||
code = code.strip()
|
||||
if not code:
|
||||
raise ValueError("verification code must not be empty")
|
||||
|
||||
payload = {
|
||||
"auth_method": "phone-captcha",
|
||||
"ctcode": pending["country_code"],
|
||||
"phone": pending["phone"],
|
||||
"captcha": code,
|
||||
"device_info": {
|
||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/140 Safari/537.36",
|
||||
"appVersion": "5.0 (X11)",
|
||||
"codecs": ["h264", "vp8", "vp9"],
|
||||
},
|
||||
}
|
||||
response = requests.post(f"{API_BASE}/tokens", json=payload, timeout=15)
|
||||
response.raise_for_status()
|
||||
token = response.json().get("token")
|
||||
if not token:
|
||||
raise RuntimeError("login response did not contain a token")
|
||||
|
||||
write_private(TOKEN_FILE, token)
|
||||
PENDING_FILE.unlink(missing_ok=True)
|
||||
print(f"Login token saved securely to {TOKEN_FILE}")
|
||||
|
||||
|
||||
def status() -> None:
|
||||
if TOKEN_FILE.is_file() and TOKEN_FILE.stat().st_size:
|
||||
print(f"token: present ({oct(TOKEN_FILE.stat().st_mode & 0o777)})")
|
||||
else:
|
||||
print("token: missing")
|
||||
print(f"pending login: {'yes' if PENDING_FILE.is_file() else 'no'}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
request_parser = sub.add_parser("request", help="request an SMS verification code")
|
||||
request_parser.add_argument("phone")
|
||||
request_parser.add_argument("--country-code", default="86")
|
||||
verify_parser = sub.add_parser("verify", help="exchange an SMS code for a token")
|
||||
verify_parser.add_argument("code")
|
||||
sub.add_parser("status", help="show credential state without revealing secrets")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "request":
|
||||
request_code(args.phone, args.country_code)
|
||||
elif args.command == "verify":
|
||||
verify_code(args.code)
|
||||
else:
|
||||
status()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user