新增 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: 类型定义
93 lines
3.7 KiB
Python
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()
|