Bläddra i källkod

add support for helmets (?helm), closes #2. changed '/:uuid/123' to '/:uuid?size=123', closes #10. Bugfixes

jomo 10 år sedan
förälder
incheckning
1606fac89b
8 ändrade filer med 92 tillägg och 34 borttagningar
  1. 7 6
      modules/config.js
  2. 11 6
      modules/helpers.js
  3. 17 3
      modules/networking.js
  4. 43 4
      modules/skins.js
  5. 9 10
      routes/avatars.js
  6. 0 0
      skins/faces/.gitkeep
  7. 0 0
      skins/helms/.gitkeep
  8. 5 5
      views/index.jade

+ 7 - 6
modules/config.js

@@ -1,10 +1,11 @@
 var config = {
-  min_size: 0,             // < 0 will (obviously) cause crash
-  max_size: 512,           // too big values might lead to slow response time or DoS
-  default_size: 180,       // size to be used when no size given
-  browser_cache_time: 3600,// seconds until browser will request image again
-  http_timeout: 1000,      // ms until connection to mojang is dropped
-  skins_dir: 'skins/'      // directory where skins are kept. should have trailing '/'
+  min_size: 0,               // < 0 will (obviously) cause crash
+  max_size: 512,             // too big values might lead to slow response time or DoS
+  default_size: 180,         // size to be used when no size given
+  browser_cache_time: 3600,  // seconds until browser will request image again
+  http_timeout: 1000,        // ms until connection to mojang is dropped
+  faces_dir: 'skins/faces/', // directory where faces are kept. should have trailing '/'
+  helms_dir: 'skins/helms/'  // directory where helms are kept. should have trailing '/'
 };
 
 module.exports = config;

+ 11 - 6
modules/helpers.js

@@ -8,7 +8,7 @@ var valid_uuid = /^[0-9a-f]{32}$/;
 var exp = {};
 
 // exracts the skin url of a +profile+ object
-// returns null when no url found
+// returns null when no url found (user has no skin)
 exp.skin_url = function(profile) {
   var url = null;
   if (profile && profile.properties) {
@@ -16,7 +16,7 @@ exp.skin_url = function(profile) {
       if (prop.name == 'textures') {
         var json = Buffer(prop.value, 'base64').toString();
         var props = JSON.parse(json);
-        url = props && props.textures && props.textures.SKIN && props.textures.SKIN.url;
+        url = props && props.textures && props.textures.SKIN && props.textures.SKIN.url || null;
       }
     });
   }
@@ -31,22 +31,27 @@ exp.uuid_valid = function(uuid) {
 };
 
 // handles requests for +uuid+ images with +size+
-//
 // callback is a function with 3 parameters:
 //   error, status, image buffer
+//   image is the user's face+helm when helm is true, or the face otherwise
 //
 // the status gives information about how the image was received
 //  -1: error
 //   1: found on disk
 //   2: profile requested/found, skin downloaded from mojang servers
 //   3: profile requested/found, but it has no skin
-exp.get_avatar = function(uuid, size, callback) {
-  var filepath = config.skins_dir + uuid + ".png";
+exp.get_avatar = function(uuid, helm, size, callback) {
+  var facepath = config.faces_dir + uuid + ".png";
+  var helmpath = config.helms_dir + uuid + ".png";
+  var filepath = helm ? helmpath : facepath;
+
   if (fs.existsSync(filepath)) {
+    // file found on disk
     skins.resize_img(filepath, size, function(err, result) {
       callback(err, 1, result);
     });
   } else {
+    // download skin
     networking.get_profile(uuid, function(err, profile) {
       if (err) {
         callback(err, -1, profile);
@@ -55,7 +60,7 @@ exp.get_avatar = function(uuid, size, callback) {
       var skinurl = exp.skin_url(profile);
 
       if (skinurl) {
-        networking.skin_file(skinurl, filepath, function(err) {
+        networking.skin_file(skinurl, facepath, helmpath, function(err) {
           if (err) {
             callback(err, -1, null);
           } else {

+ 17 - 3
modules/networking.js

@@ -6,12 +6,15 @@ var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
 
 var exp = {};
 
+// download the Mojang profile for +uuid+
+// callback contains error, profile object
 exp.get_profile = function(uuid, callback) {
   request.get({
     url: session_url + uuid,
     timeout: config.http_timeout // ms
   }, function (error, response, body) {
     if (!error && response.statusCode == 200) {
+      // profile downloaded successfully
       callback(null, JSON.parse(body));
     } else {
       if (error) {
@@ -33,15 +36,26 @@ exp.get_profile = function(uuid, callback) {
   });
 };
 
-exp.skin_file = function(url, outname, callback) {
+// downloads skin file from +url+
+// stores face image as +facename+
+// stores helm image as +helmname+
+// callback is forwarded from skins/extract_face or skins/extract_helm
+exp.skin_file = function(url, facename, helmname, callback) {
   request.get({
     url: url,
     encoding: null, // encoding must be null so we get a buffer
     timeout: config.http_timeout // ms
   }, function (error, response, body) {
     if (!error && response.statusCode == 200) {
-      skins.extract_face(body, outname, function(err) {
-        callback(err);
+      // skin downloaded successfully
+      skins.extract_face(body, facename, function(err) {
+        if (err) {
+          callback(err);
+        } else {
+          skins.extract_helm(facename, body, helmname, function(err) {
+            callback(err);
+          });
+        }
       });
     } else {
       if (error) {

+ 43 - 4
modules/skins.js

@@ -3,15 +3,15 @@ var lwip = require('lwip');
 var exp = {};
 
 // extracts the face from an image +buffer+
-// save it to a file called +outname+
-// callback has an error parameter which can be null
+// result is saved to a file called +outname+
+// +callback+ contains error
 exp.extract_face = function(buffer, outname, callback) {
   lwip.open(buffer, "png", function(err, image) {
     if (err) {
       callback(err);
     } else {
       image.batch()
-      .crop(8, 8, 15, 15)
+      .crop(8, 8, 15, 15) // face
       .writeFile(outname, function(err) {
         if (err) {
           callback(err);
@@ -23,8 +23,47 @@ exp.extract_face = function(buffer, outname, callback) {
   });
 };
 
+// extracts the helm from an image +buffer+ and lays it over a +facefile+
+// +facefile+ is the filename of an image produced by extract_face
+// result is saved to a file called +outname+
+// +callback+ contains error
+exp.extract_helm = function(facefile, buffer, outname, callback) {
+  lwip.open(buffer, "png", function(err, skin) {
+    if (err) {
+      callback(err);
+    } else {
+      lwip.open(facefile, function(err, face_img) {
+        if (err) {
+          callback(err);
+        } else {
+          skin.crop(42, 8, 49, 15, function(err, helm_img) {
+            if (err) {
+              callback(err);
+            } else {
+              face_img.paste(0, 0, helm_img, function(err, face_helm_img) {
+                if (err) {
+                  callback(err);
+                } else {
+                  face_helm_img.writeFile(outname, function(err) {
+                    if (err) {
+                      callback(err);
+                    } else {
+                      callback(null);
+                      // JavaScript callback hell <3
+                    }
+                  });
+                }
+              });
+            }
+          });
+        }
+      });
+    }
+  });
+};
+
 // resizes the image file +inname+ to +size+ by +size+ pixels
-// +callback+ is a buffer of the resized image
+// +callback+ contains error, image buffer
 exp.resize_img = function(inname, size, callback) {
   lwip.open(inname, function(err, image) {
     if (err) {

+ 9 - 10
routes/avatars.js

@@ -6,10 +6,11 @@ var skins = require('../modules/skins');
 var fs = require('fs');
 
 /* GET avatar request. */
-router.get('/:uuid/:size?', function(req, res) {
-  var uuid = req.param('uuid');
-  var size = req.param('size') || config.default_size;
+router.get('/:uuid', function(req, res) {
+  var uuid = req.params.uuid;
+  var size = req.query.size || config.default_size;
   var def = req.query.default;
+  var helm = req.query.hasOwnProperty('helm');
   var start = new Date();
 
   // Prevent app from crashing/freezing
@@ -24,13 +25,12 @@ router.get('/:uuid/:size?', function(req, res) {
   }
 
   try {
-    helpers.get_avatar(uuid, size, function(err, status, image) {
+    helpers.get_avatar(uuid, helm, size, function(err, status, image) {
       if (err) {
         console.error(err);
         handle_404(def);
       } else if (status == 1 || status == 2) {
-        var time = new Date() - start;
-        sendimage(200, time, image);
+        sendimage(200, image);
       } else if (status == 3) {
         handle_404(def);
       }
@@ -44,19 +44,18 @@ router.get('/:uuid/:size?', function(req, res) {
   function handle_404(def) {
     if (def == "alex" || def == "steve") {
       skins.resize_img("public/images/" + def + ".png", size, function(image) {
-        var time = new Date() - start;
-        sendimage(404, time, image);
+        sendimage(404, image);
       });
     } else {
       res.status(404).send('404 Not found');
     }
   }
 
-  function sendimage(status, time, image) {
+  function sendimage(status, image) {
     res.writeHead(status, {
       'Content-Type': 'image/png',
       'Cache-Control': 'max-age=' + config.browser_cache_time + ', public',
-      'Response-Time': time,
+      'Response-Time': new Date() - start,
       'X-Storage-Type': 'local'
     });
     res.end(image);

+ 0 - 0
skins/.gitkeep → skins/faces/.gitkeep


+ 0 - 0
skins/helms/.gitkeep


+ 5 - 5
views/index.jade

@@ -32,8 +32,8 @@ block content
         p(style="margin-top: 10px;") By default, a 404 text is returned when the avatar was not found. You can change that to the avatar of steve or alex:
         .well &lt;img src="#{domain}/avatars/ae795aa86327408e92ab25c8a59f3ba1/250?default=alex"&gt;
       .col-md-2
-        img(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?default=steve", title="Jake0oo0")
-        img(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?default=steve", title="redstone_sheep")
-        img(src="/avatars/069a79f444e94726a5befca90e38aaf5?default=steve", title="Notch")
-        img(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?default=steve", title="sk89q")
-        img(src="/avatars/af74a02d19cb445bb07f6866a861f783?default=steve", title="md_5")
+        img(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?default=alex&helm", title="Jake0oo0")
+        img(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?default=alex&helm", title="redstone_sheep")
+        img(src="/avatars/069a79f444e94726a5befca90e38aaf5?default=alex&helm", title="Notch")
+        img(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?default=alex&helm", title="sk89q")
+        img(src="/avatars/af74a02d19cb445bb07f6866a861f783?default=alex&helm", title="md_5")