UNPKG

minecraft-core-master

Version:

Núcleo avanzado para launchers de Minecraft. Descarga, instala y ejecuta versiones de Minecraft, assets, librerías, Java y loaders de forma automática y eficiente.

301 lines (300 loc) 12.1 kB
import { Buffer } from 'node:buffer'; import crypto from 'crypto'; /** * @description * Convierte una imagen URL en base64. * ---- * Converts an image URL to base64. */ async function getBase64(url) { const response = await fetch(url); if (response.ok) { const arrayBuffer = await response.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); return buffer.toString('base64'); } else { return ''; } } /** * @description * Clase principal para realizar la autenticación de Microsoft y Minecraft. * Se encarga de toda la cadena: OAuth2 → Xbox Live → XSTS → Minecraft Login → Perfil. * ---- * Main class for authenticating Microsoft + Minecraft accounts. * Handles full chain: OAuth2 → Xbox Live → XSTS → Minecraft Login → Profile. */ export default class Microsoft { client_id; type; constructor(client_id) { if (!client_id) { client_id = '00000000402b5328'; } this.client_id = client_id; if (typeof process !== 'undefined' && process.versions && process.versions.electron) { this.type = 'electron'; } else if (typeof process !== 'undefined' && process.versions && process.versions.nw) { this.type = 'nwjs'; } else { this.type = 'terminal'; } } /** * @description * Inicia el proceso de login mostrando la UI correcta según el entorno. * Devuelve el código de autorización que luego será intercambiado por un token. * ---- * Starts login process using the correct UI (Electron/NW/Terminal). * Returns the authorization code to exchange for token later. */ async getAuth(type, url) { const finalType = type || this.type; const finalUrl = url || `https://login.live.com/oauth20_authorize.srf?client_id=${this.client_id}&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf&scope=XboxLive.signin%20offline_access&cobrandid=8058f65d-ce06-4c30-9559-473c9275a65d&prompt=select_account`; let userCode; switch (finalType) { case 'electron': userCode = await import('./UI/Electron.js').then(m => m.default(finalUrl)); break; case 'nwjs': userCode = await import('./UI/NW.js').then(m => m.default(finalUrl)); break; case 'terminal': userCode = await import('./UI/Terminal.js').then(m => m.default(finalUrl)); break; default: return false; } if (userCode === 'cancel') return false; return this.exchangeCodeForToken(userCode); } /** * @description * Intercambia el código OAuth2 por tokens de inicio de sesión. * ---- * Exchanges authorization code for OAuth2 login tokens. */ async exchangeCodeForToken(code) { try { const response = await fetch('https://login.live.com/oauth20_token.srf', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `client_id=${this.client_id}&code=${code}&grant_type=authorization_code&redirect_uri=https://login.live.com/oauth20_desktop.srf` }); const oauth2 = await response.json(); if (oauth2.error) { return { error: oauth2.error, errorType: 'oauth2', ...oauth2 }; } return this.getAccount(oauth2); } catch (err) { return { error: err.message, errorType: 'network' }; } } /** * @description * Refresca el token si expiró, o actualiza el perfil si aún es válido. * ---- * Refreshes the token if expired, or updates profile if still valid. */ async refresh(acc) { const timeStamp = Math.floor(Date.now()); if (timeStamp < (acc?.meta?.access_token_expires_in - 7200)) { // Actualiza el perfil sin refrescar token / Update profile without refreshing token const updatedProfile = await this.getProfile({ access_token: acc.access_token }); if ('error' in updatedProfile) { return updatedProfile; } acc.profile = { skins: updatedProfile.skins, capes: updatedProfile.capes }; return acc; } // Refresca el token / Refresh token try { const response = await fetch('https://login.live.com/oauth20_token.srf', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `grant_type=refresh_token&client_id=${this.client_id}&refresh_token=${acc.refresh_token}` }); const oauth2 = await response.json(); if (oauth2.error) { return { error: oauth2.error, errorType: 'oauth2', ...oauth2 }; } return this.getAccount(oauth2); } catch (err) { return { error: err.message, errorType: 'network' }; } } /** * @description * Realiza toda la cadena de login: XBL → XSTS → Minecraft → Perfil. * ---- * Performs full login chain: XBL → XSTS → Minecraft → Profile. */ async getAccount(oauth2) { // Paso 1: Autenticar con Xbox Live const authenticateResponse = await fetch('https://user.auth.xboxlive.com/user/authenticate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ Properties: { AuthMethod: 'RPS', SiteName: 'user.auth.xboxlive.com', RpsTicket: `d=${oauth2.access_token}`, }, RelyingParty: 'http://auth.xboxlive.com', TokenType: 'JWT', }), }); const xbl = await authenticateResponse.json(); if (xbl.error) { return { error: xbl.error, errorType: 'xbl', ...xbl, refresh_token: oauth2.refresh_token }; } // Paso 2: Obtener token XSTS const authorizeResponse = await fetch('https://xsts.auth.xboxlive.com/xsts/authorize', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ Properties: { SandboxId: 'RETAIL', UserTokens: [xbl.Token], }, RelyingParty: 'rp://api.minecraftservices.com/', TokenType: 'JWT', }), }); const xsts = await authorizeResponse.json(); if (xsts.error) { return { error: xsts.error, errorType: 'xsts', ...xsts, refresh_token: oauth2.refresh_token }; } // Paso 3: Login con Minecraft const mcLoginResponse = await fetch('https://api.minecraftservices.com/authentication/login_with_xbox', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ identityToken: `XBL3.0 x=${xbl.DisplayClaims.xui[0].uhs};${xsts.Token}` }), }); const mcLogin = await mcLoginResponse.json(); if (mcLogin.error) { return { error: mcLogin.error, errorType: 'mcLogin', ...mcLogin, refresh_token: oauth2.refresh_token }; } if (!mcLogin.username) { return { error: 'NO_MINECRAFT_ACCOUNT', errorType: 'mcLogin', ...mcLogin, refresh_token: oauth2.refresh_token }; } // Paso 4: Verificar licencias de Minecraft const mcstoreResponse = await fetch('https://api.minecraftservices.com/entitlements/mcstore', { method: 'GET', headers: { 'Authorization': `Bearer ${mcLogin.access_token}` }, }); const mcstore = await mcstoreResponse.json(); if (mcstore.error) { return { error: mcstore.error, errorType: 'mcStore', ...mcstore, refresh_token: oauth2.refresh_token }; } if (!mcstore.items.some((item) => item.name === "game_minecraft" || item.name === "product_minecraft")) { return { error: 'NO_MINECRAFT_ENTITLEMENTS', errorType: 'mcStore', ...mcstore, refresh_token: oauth2.refresh_token }; } // Paso 5: Obtener perfil del jugador const profile = await this.getProfile(mcLogin); if ('error' in profile) { return { ...profile, error: profile.error, errorType: 'mcProfile', refresh_token: oauth2.refresh_token }; } // Paso 6: Obtener datos extra de Xbox (Gamertag, XUID, edad) const xboxAccountResponse = await fetch('https://xsts.auth.xboxlive.com/xsts/authorize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ Properties: { SandboxId: 'RETAIL', UserTokens: [xbl.Token] }, RelyingParty: 'http://xboxlive.com', TokenType: 'JWT' }) }); const xboxAccount = await xboxAccountResponse.json(); if (xboxAccount.error) { return { error: xboxAccount.error, errorType: 'xboxAccount', ...xboxAccount, refresh_token: oauth2.refresh_token }; } // Respuesta final correcta return { access_token: mcLogin.access_token, client_token: crypto.randomUUID(), uuid: profile.id, name: profile.name, refresh_token: oauth2.refresh_token, user_properties: "{}", meta: { type: 'Xbox', access_token_expires_in: Date.now() + (mcLogin.expires_in * 1000), demo: false }, xboxAccount: { xuid: xboxAccount.DisplayClaims.xui[0].xid, gamertag: xboxAccount.DisplayClaims.xui[0].gtg, ageGroup: xboxAccount.DisplayClaims.xui[0].agg }, profile: { skins: profile.skins ?? [], capes: profile.capes ?? [] } }; } /** * Obtiene el perfil de Minecraft del usuario, incluyendo skins y capas en base64. * ---- * Fetches player's Minecraft profile including skins and capes converted to base64. */ async getProfile(mcLogin) { try { const response = await fetch('https://api.minecraftservices.com/minecraft/profile', { method: 'GET', headers: { Authorization: `Bearer ${mcLogin.access_token}` } }); const profile = await response.json(); if (profile.error) { return { error: profile.error }; } // Convierte skins a base64 / Convert skins to base64 if (Array.isArray(profile.skins)) { for (const skin of profile.skins) { if (skin.url) { skin.base64 = `data:image/png;base64,${await getBase64(skin.url)}`; } } } // Convierte capas a base64 / Convert capes to base64 if (Array.isArray(profile.capes)) { for (const cape of profile.capes) { if (cape.url) { cape.base64 = `data:image/png;base64,${await getBase64(cape.url)}`; } } } return { id: profile.id, name: profile.name, skins: profile.skins || [], capes: profile.capes || [] }; } catch (err) { return { error: err.message }; } } }