@@ -198,9 +198,6 @@ export default class Utils {
198
198
}
199
199
this . apiLimited = true ;
200
200
console . log ( '[INFO] API REQUEST:' , apiPath ) ;
201
- setTimeout ( ( ) => {
202
- this . apiLimited = false ;
203
- } , this . config . api . ratelimit * 1000 ) ;
204
201
205
202
let body ;
206
203
try {
@@ -216,46 +213,60 @@ export default class Utils {
216
213
throw new Error ( err ) ;
217
214
}
218
215
216
+ setTimeout ( ( ) => {
217
+ this . apiLimited = false ;
218
+ } , this . config . api . ratelimit * 1000 ) ;
219
+
220
+
219
221
return JSON . parse ( body ) ;
220
222
}
221
223
222
- async getPlayerAssets ( uuid , playerpath ) {
224
+ static defaultSkin ( uuid ) {
225
+ // great thanks to Minecrell for research into Minecraft and Java's UUID hashing!
226
+ // https://git.io/xJpV
227
+ // MC uses `uuid.hashCode() & 1` for alex
228
+ // that can be compacted to counting the LSBs of every 4th byte in the UUID
229
+ // an odd sum means alex, an even sum means steve
230
+ // XOR-ing all the LSBs gives us 1 for alex and 0 for steve
231
+ const isEven = ( c ) => {
232
+ if ( c >= '0' && c <= '9' ) {
233
+ return ( c & 1 ) === 0 ; // eslint-disable-line
234
+ } else if ( c >= 'a' && c <= 'f' ) {
235
+ return ( c & 1 ) === 1 ; // eslint-disable-line
236
+ }
237
+ console . log ( 'Invalid digit' , c ) ;
238
+ return null ;
239
+ } ;
240
+ const lsbsEven =
241
+ ( isEven ( uuid [ 7 ] ) !== isEven ( uuid [ 23 ] ) ) !== ( isEven ( uuid [ 15 ] ) !== isEven ( uuid [ 31 ] ) ) ;
242
+ return lsbsEven ? 'Alex' : 'Steve' ;
243
+ }
244
+
245
+ static async getPlayerAssets ( uuid , playerpath ) {
223
246
try {
224
247
fs . ensureDirSync ( playerpath ) ;
225
248
} catch ( error ) {
226
249
throw new Error ( error ) ;
227
250
}
228
- const skinapipath = `https://sessionserver.mojang.com/session/minecraft/profile/${ uuid } ` ;
229
251
230
- let res ;
231
- try {
232
- res = await this . getMojangAPI ( skinapipath ) ;
233
- } catch ( err ) {
234
- console . error ( '[ERROR] SKIN API' , skinapipath , err ) ;
235
- throw new Error ( err ) ;
236
- }
237
- const apiprefixAvatar = 'https://crafatar.com/avatars/' ;
238
- const apiprefixBody = 'https://crafatar.com/renders/body/' ;
239
- let slim = '' ;
240
- res . properties . forEach ( ( t ) => {
241
- if ( t . name === 'textures' ) {
242
- const texture = JSON . parse ( Buffer . from ( t . value , 'base64' ) . toString ( 'ascii' ) ) ;
243
- if ( texture . textures . SKIN ) {
244
- if ( texture . textures . SKIN . metadata && texture . textures . SKIN . metadata . model === 'slim' ) {
245
- // Alex model
246
- slim = '&default=MHF_Alex' ;
247
- }
248
- }
249
- }
250
- } ) ;
252
+ const apiPrefixAvatar = 'https://crafatar.com/avatars/' ;
253
+ const apiPrefixBody = 'https://crafatar.com/renders/body/' ;
254
+ const apiPrefixSkin = 'https://crafatar.com/skins/' ;
255
+
256
+ const slim = `&default=MHF_${ Utils . defaultSkin ( uuid ) } ` ;
257
+
251
258
Utils . download (
252
- `${ apiprefixAvatar } ${ uuid } ?size=64&overlay${ slim } ` ,
259
+ `${ apiPrefixAvatar } ${ uuid } ?size=64&overlay${ slim } ` ,
253
260
path . join ( playerpath , 'avatar.png' ) ,
254
261
) ;
255
262
Utils . download (
256
- `${ apiprefixBody } ${ uuid } ?size=128&overlay${ slim } ` ,
263
+ `${ apiPrefixBody } ${ uuid } ?size=128&overlay${ slim } ` ,
257
264
path . join ( playerpath , 'body.png' ) ,
258
265
) ;
266
+ Utils . download (
267
+ `${ apiPrefixSkin } ${ uuid } ?${ slim } ` ,
268
+ path . join ( playerpath , 'skin.png' ) ,
269
+ ) ;
259
270
}
260
271
261
272
static download ( apiPath , dest ) {
@@ -295,7 +306,7 @@ export default class Utils {
295
306
return resolve ( data ) ;
296
307
} else if ( data && data . stats && data . data ) {
297
308
try {
298
- await this . getPlayerAssets ( uuid . replace ( / - / g, '' ) , playerpath ) ;
309
+ await Utils . getPlayerAssets ( uuid . replace ( / - / g, '' ) , playerpath ) ;
299
310
} catch ( error ) {
300
311
console . log ( error ) ;
301
312
}
0 commit comments