ForcePilot/backend/package/yuxi/channel/extensions/minecraft/auth.py
Kris b3b6c95094 feat(channel): 添加 Minecraft 渠道扩展
新增 Minecraft 渠道扩展,支持在 Yuxi 平台中集成 Minecraft 游戏服务器渠道。

包含以下功能模块:
- client: Minecraft 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- auth: 认证管理
- accounts: 账户管理
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- protocol: Minecraft 协议处理
- rcon: RCON 远程控制
- keepalive: 连接保活
- version_adapter: 版本适配
- setup: 初始化设置
- types: 类型定义
2026-05-21 11:26:52 +08:00

93 lines
3.7 KiB
Python

import asyncio
import logging
import httpx
logger = logging.getLogger(__name__)
MS_DEVICE_CODE_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"
MS_TOKEN_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
XBOX_AUTH_URL = "https://user.auth.xboxlive.com/user/authenticate"
XSTS_AUTH_URL = "https://xsts.auth.xboxlive.com/xsts/authorize"
MC_AUTH_URL = "https://api.minecraftservices.com/authentication/login_with_xbox"
MC_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile"
CLIENT_ID = "00000000402b5328"
class MinecraftAuth:
def __init__(self, email: str | None = None, password: str | None = None):
self.email = email
self.password = password
self._http = httpx.AsyncClient(timeout=30)
async def close(self):
await self._http.aclose()
async def authenticate(self) -> dict:
ms_token = await self._get_ms_token()
xbl_token, uhs = await self._auth_xbox_live(ms_token)
xsts_token, _ = await self._auth_xsts(xbl_token)
mc_token = await self._auth_minecraft(uhs, xsts_token)
profile = await self._get_minecraft_profile(mc_token)
return {"access_token": mc_token, "uuid": profile["id"], "username": profile["name"]}
async def _get_ms_token(self) -> str:
device_resp = await self._http.post(
MS_DEVICE_CODE_URL, data={"client_id": CLIENT_ID, "scope": "XboxLive.signin offline_access"}
)
device_data = device_resp.json()
user_code = device_data["user_code"]
device_code = device_data["device_code"]
interval = device_data.get("interval", 5)
logger.info("Microsoft login: visit https://microsoft.com/devicelogin and enter code: %s", user_code)
while True:
await asyncio.sleep(interval)
token_resp = await self._http.post(
MS_TOKEN_URL,
data={
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"client_id": CLIENT_ID,
"device_code": device_code,
},
)
token_data = token_resp.json()
if "access_token" in token_data:
return token_data["access_token"]
if token_data.get("error") == "authorization_pending":
continue
raise RuntimeError(f"Microsoft OAuth failed: {token_data.get('error')}")
async def _auth_xbox_live(self, ms_token: str) -> tuple[str, str]:
resp = await self._http.post(
XBOX_AUTH_URL,
json={
"Properties": {"AuthMethod": "RPS", "SiteName": "user.auth.xboxlive.com", "RpsTicket": f"d={ms_token}"},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT",
},
)
data = resp.json()
return data["Token"], data["DisplayClaims"]["xui"][0]["uhs"]
async def _auth_xsts(self, xbl_token: str) -> tuple[str, str]:
resp = await self._http.post(
XSTS_AUTH_URL,
json={
"Properties": {"SandboxId": "RETAIL", "UserTokens": [xbl_token]},
"RelyingParty": "rp://api.minecraftservices.com/",
"TokenType": "JWT",
},
)
data = resp.json()
return data["Token"], data["DisplayClaims"]["xui"][0]["uhs"]
async def _auth_minecraft(self, uhs: str, xsts_token: str) -> str:
resp = await self._http.post(MC_AUTH_URL, json={"identityToken": f"XBL3.0 x={uhs};{xsts_token}"})
return resp.json()["access_token"]
async def _get_minecraft_profile(self, mc_token: str) -> dict:
resp = await self._http.get(MC_PROFILE_URL, headers={"Authorization": f"Bearer {mc_token}"})
return resp.json()