Files
AeroMc-Launcher/src/skins.js
T

122 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/*
* skins.js – Skin und Umhang über die Minecraft-Dienste ändern
* -------------------------------------------------------------
* Der Launcher hält bereits gültige Zugangsdaten des angemeldeten Kontos.
* Damit lässt sich der Skin direkt setzen, ohne Umweg über den Browser.
*
* Verwendete Wege (api.minecraftservices.com):
* POST /minecraft/profile/skins Skin hochladen (Datei oder Adresse)
* DELETE /minecraft/profile/skins/active Skin zurücksetzen (Standard-Steve/Alex)
* GET /minecraft/profile aktuelles Profil samt Skin-Adresse
*/
const fs = require('fs');
const path = require('path');
const API = 'https://api.minecraftservices.com';
const UA = 'AeroMC-Launcher';
// Minecraft nimmt nur PNG, und die Vorlagen sind 64x64 bzw. 64x32
const MAX_BYTES = 512 * 1024;
function pruefeDatei(dateiPfad) {
if (!dateiPfad || !fs.existsSync(dateiPfad)) return { ok: false, message: 'Datei nicht gefunden.' };
if (!/\.png$/i.test(dateiPfad)) return { ok: false, message: 'Minecraft nimmt nur PNG-Dateien.' };
const st = fs.statSync(dateiPfad);
if (!st.isFile()) return { ok: false, message: 'Das ist keine Datei.' };
if (st.size > MAX_BYTES) return { ok: false, message: 'Die Datei ist zu groß (über 512 KB).' };
// PNG-Kennung und Maße aus dem Kopf lesen, bevor wir sie hochschicken
const kopf = Buffer.alloc(33);
const fd = fs.openSync(dateiPfad, 'r');
try { fs.readSync(fd, kopf, 0, 33, 0); } finally { fs.closeSync(fd); }
const PNG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
if (!kopf.subarray(0, 8).equals(PNG)) return { ok: false, message: 'Die Datei ist kein gültiges PNG.' };
const breite = kopf.readUInt32BE(16);
const hoehe = kopf.readUInt32BE(20);
// 64x64 ist heute üblich, 64x32 sind die alten Skins
if (!(breite === 64 && (hoehe === 64 || hoehe === 32))) {
return { ok: false, message: `Ein Skin muss 64×64 (oder 64×32) Pixel groß sein – diese Datei ist ${breite}×${hoehe}.` };
}
return { ok: true, breite, hoehe };
}
function fehlerText(status) {
if (status === 401) return 'Die Anmeldung ist abgelaufen. Bitte das Konto neu verbinden.';
if (status === 403) return 'Minecraft lehnt die Änderung ab. Hat das Konto Minecraft Java Edition?';
if (status === 429) return 'Zu viele Änderungen in kurzer Zeit. Bitte einen Moment warten.';
return 'Minecraft-Dienste antworten mit HTTP ' + status + '.';
}
/*
* Skin setzen.
* variante: 'classic' (breite Arme) oder 'slim' (schmale Arme, Alex)
*/
async function setzeSkin(accessToken, dateiPfad, variante) {
const geprueft = pruefeDatei(dateiPfad);
if (!geprueft.ok) return geprueft;
const form = new FormData();
form.append('variant', variante === 'slim' ? 'slim' : 'classic');
const daten = fs.readFileSync(dateiPfad);
form.append('file', new Blob([daten], { type: 'image/png' }), path.basename(dateiPfad));
const res = await fetch(API + '/minecraft/profile/skins', {
method: 'POST',
headers: { Authorization: 'Bearer ' + accessToken, 'User-Agent': UA },
body: form,
});
if (!res.ok) return { ok: false, message: fehlerText(res.status) };
let profil = null;
try { profil = await res.json(); } catch { /* Antwort ohne Rumpf ist auch in Ordnung */ }
return { ok: true, variante: variante === 'slim' ? 'slim' : 'classic', profil };
}
// Skin auf den Standard zurücksetzen
async function setzeSkinZurueck(accessToken) {
const res = await fetch(API + '/minecraft/profile/skins/active', {
method: 'DELETE',
headers: { Authorization: 'Bearer ' + accessToken, 'User-Agent': UA },
});
if (!res.ok) return { ok: false, message: fehlerText(res.status) };
return { ok: true };
}
/*
* Aktuelles Profil abrufen – liefert die Adresse des aktiven Skins,
* damit die Oberfläche vorher/nachher zeigen kann.
*/
/*
* Mojang liefert Skin-Adressen bis heute als "http://textures.minecraft.net/…".
* Die Oberfläche lässt nach ihrer Sicherheitsrichtlinie nur https zu, das Bild
* würde also blockiert. textures.minecraft.net beantwortet dieselbe Adresse
* auch über https – daher heben wir sie an.
*/
function aufHttps(url) {
const s = String(url || '');
return s.startsWith('http://') ? 'https://' + s.slice('http://'.length) : s;
}
async function holeProfil(accessToken) {
const res = await fetch(API + '/minecraft/profile', {
headers: { Authorization: 'Bearer ' + accessToken, 'User-Agent': UA, Accept: 'application/json' },
});
if (!res.ok) return { ok: false, message: fehlerText(res.status) };
const daten = await res.json();
const aktiv = (daten.skins || []).find((s) => s.state === 'ACTIVE') || null;
return {
ok: true,
name: daten.name,
id: daten.id,
skinUrl: aktiv ? aufHttps(aktiv.url) : '',
variante: aktiv ? String(aktiv.variant || '').toLowerCase() : '',
};
}
module.exports = { setzeSkin, setzeSkinZurueck, holeProfil, pruefeDatei, aufHttps };