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
JavaScript
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 };
}
}
}