Selaa lähdekoodia

split code into more modules, fixes #8

jomo 10 vuotta sitten
vanhempi
sitoutus
cc159d3620
10 muutettua tiedostoa jossa 230 lisäystä ja 176 poistoa
  1. 1 1
      app.js
  2. 8 0
      modules/config.js
  3. 72 0
      modules/helpers.js
  4. 65 0
      modules/networking.js
  5. 32 0
      modules/skins.js
  6. 1 1
      package.json
  7. 47 65
      routes/avatars.js
  8. 1 1
      routes/index.js
  9. 3 3
      server.js
  10. 0 105
      skins.js

+ 1 - 1
app.js

@@ -56,4 +56,4 @@ app.use(function(err, req, res, next) {
   });
 });
 
-module.exports = app;
+module.exports = app;

+ 8 - 0
modules/config.js

@@ -0,0 +1,8 @@
+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
+};
+
+module.exports = config;

+ 72 - 0
modules/helpers.js

@@ -0,0 +1,72 @@
+var networking = require('./networking');
+var config = require('./config');
+var skins = require('./skins');
+var fs = require('fs');
+
+var valid_uuid = /^[0-9a-f]{32}$/;
+var skins_dir = config.skins_dir;
+
+var exp = {};
+
+// exracts the skin url of a +profile+ object
+// returns null when no url found
+exp.skin_url = function(profile) {
+  var url = null;
+  if (profile && profile.properties) {
+    profile.properties.forEach(function(prop) {
+      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;
+      }
+    });
+  }
+  return url;
+};
+
+
+// returns true if the +uuid+ is a valid uuid
+// the uuid may be not exist, however
+exp.uuid_valid = function(uuid) {
+  return valid_uuid.test(uuid);
+};
+
+// handles requests for +uuid+ images with +size+
+//
+// callback is a function with 3 parameters:
+//   error, status, image buffer
+//
+// the status gives information about how the image was received
+//  -1: profile requested, but it was not found
+//   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 = skins_dir + uuid + ".png";
+  if (fs.existsSync(filepath)) {
+    skins.resize_img(filepath, size, function(result) {
+      callback(null, 1, result);
+    });
+  } else {
+    networking.get_profile(uuid, function(err, profile) {
+      if (err) {
+        callback(err, -1, profile);
+      }
+      var skinurl = exp.skin_url(profile);
+
+      if (skinurl) {
+        networking.skin_file(skinurl, filepath, function() {
+          console.log('got skin');
+          skins.resize_img(filepath, size, function(result) {
+            callback(null, 2, result);
+          });
+        });
+      } else {
+        // profile found, but has no skin
+        callback(null, 3, null);
+      }
+    });
+  }
+};
+
+module.exports = exp;

+ 65 - 0
modules/networking.js

@@ -0,0 +1,65 @@
+var request = require('request');
+var skins = require('./skins');
+
+var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
+
+var exp = {};
+
+exp.get_profile = function(uuid, callback) {
+  request.get({
+    url: session_url + uuid,
+    timeout: 1000 // ms
+  }, function (error, response, body) {
+    if (!error && response.statusCode == 200) {
+      callback(null, JSON.parse(body));
+    } else {
+      if (error) {
+        callback(error, null);
+        return;
+      } else if (response.statusCode == 204 || response.statusCode == 404) {
+        // we get 204 No Content when UUID doesn't exist (including 404 in case they change that)
+      } else if (response.statusCode == 429) {
+        // Too Many Requests
+        console.warn("Too many requests for " + uuid);
+        console.warn(body);
+      } else {
+        console.error("Unknown error:");
+        console.error(response);
+        console.error(body);
+      }
+      callback(null, null);
+    }
+  });
+};
+
+exp.skin_file = function(url, outname, callback) {
+  request.get({
+    url: url,
+    encoding: null, // encoding must be null so we get a buffer
+    timeout: 1000 // ms
+  }, function (error, response, body) {
+    if (!error && response.statusCode == 200) {
+      skins.extract_face(body, outname, function() {
+        callback();
+      });
+    } else {
+      if (error) {
+        console.error(error);
+      } else if (response.statusCode == 404) {
+        console.warn("Texture not found: " + url);
+      } else if (response.statusCode == 429) {
+        // Too Many Requests
+        // Never got this, seems like textures aren't limited
+        console.warn("Too many requests for " + url);
+        console.warn(body);
+      } else {
+        console.error("Unknown error:");
+        console.error(response);
+        console.error(body);
+      }
+      callback(null);
+    }
+  });
+};
+
+module.exports = exp;

+ 32 - 0
modules/skins.js

@@ -0,0 +1,32 @@
+var lwip = require('lwip');
+
+var exp = {};
+
+// extracts the face from an image +buffer+
+// save it to a file called +outname+
+exp.extract_face = function(buffer, outname, callback) {
+  lwip.open(buffer, "png", function(err, image) {
+    if (err) throw err;
+    image.batch()
+    .crop(8, 8, 15, 15)
+    .writeFile(outname, function(err) {
+      if (err) throw err;
+      callback();
+    });
+  });
+};
+
+// resizes the image file +inname+ to +size+ by +size+ pixels
+// +callback+ is a buffer of the resized image
+exp.resize_img = function(inname, size, callback) {
+  lwip.open(inname, function(err, image) {
+    if (err) throw err;
+    image.batch()
+    .resize(size, size, "nearest-neighbor") // nearest-neighbor doesn't blur
+    .toBuffer('png', function(err, buffer) {
+      callback(buffer);
+    });
+  });
+};
+
+module.exports = exp;

+ 1 - 1
package.json

@@ -3,7 +3,7 @@
   "version": "0.0.0",
   "private": true,
   "scripts": {
-    "start": "node ./bin/www"
+    "start": "node server.js"
   },
   "dependencies": {
     "express": "~4.9.0",

+ 47 - 65
routes/avatars.js

@@ -1,78 +1,60 @@
-var express = require('express');
-var router = express.Router();
-var skins = require('../skins');
+var networking = require('../modules/networking');
+var helpers = require('../modules/helpers');
+var router = require('express').Router();
+var config = require('../modules/config');
+var skins = require('../modules/skins');
 var fs = require('fs');
 
-var valid_uuid = /^[0-9a-f]{32}$/;
-
-/* GET home page. */
+/* GET avatar request. */
 router.get('/:uuid/:size?', function(req, res) {
   var uuid = req.param('uuid');
-  var size = req.param('size') || 180;
+  var size = req.param('size') || config.default_size;
   var def = req.query.default;
   var start = new Date();
+
   // Prevent app from crashing/freezing
-  if (size <= 0 || size > 512) size = 180;
-  if (valid_uuid.test(uuid)) {
-    var filename = uuid + ".png";
-    if (fs.existsSync("skins/" + filename)) {
-      console.log('found ' + filename);
-      skins.resize_img("skins/" + filename, size, function(data) {
-        // tell browser to cache image locally for 10 minutes
-        var end = new Date() - start;
-        res.writeHead(200, {'Content-Type': 'image/png', 'Cache-Control': 'max-age=600, public', 'Response-Time': end, 'Storage-Type': 'local'});
-        res.end(data);
+  if (size <= config.min_size || size > config.max_size) {
+    // "Unprocessable Entity", valid request, but semantically erroneous:
+    // https://tools.ietf.org/html/rfc4918#page-78
+    res.status(422).send("422 Invalid size");
+    return;
+  } else if (!helpers.uuid_valid(uuid)) {
+    res.status(422).send("422 Invalid UUID");
+    return;
+  }
+
+  helpers.get_avatar(uuid, size, function(err, status, image) {
+    if (err) {
+      throw err;
+    } else if (status == 1 || status == 2) {
+      var time = new Date() - start;
+      sendimage(200, time, image);
+    } else if (status == 3) {
+      handle_404(def);
+    }
+  });
+
+  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);
       });
     } else {
-      console.log(filename + ' not found, downloading profile..');
-      skins.get_profile(uuid, function(profile) {
-        var skinurl = skins.skin_url(profile);
-        if (skinurl) {
-          console.log('got profile, skin url is "' + skinurl + '" downloading..');
-          skins.skin_file(skinurl, "skins/" + filename, function() {
-            console.log('got skin');
-            skins.resize_img("skins/" + filename, size, function(data) {
-              // tell browser to cache image locally for 10 minutes
-              var end = new Date() - start;
-              res.writeHead(200, {
-                'Content-Type': 'image/png',
-                'Cache-Control': 'max-age=600, public',
-                'Response-Time': end,
-                'Storage-Type': 'downloaded'
-              });
-              res.end(data);
-            });
-          });
-        } else {
-          console.log('no skin url found');
-          switch (def) {
-            case "alex":
-              skins.resize_img("public/images/alex.png", size, function(data) {
-                // tell browser to cache image locally for 10 minutes
-                var end = new Date() - start;
-                res.writeHead(404, {'Content-Type': 'image/png', 'Cache-Control': 'max-age=600, public', 'Response-Time': end, 'Storage-Type': 'local'});
-                res.end(data);
-              });
-              break;
-            case "steve":
-              skins.resize_img("public/images/steve.png", size, function(data) {
-                // tell browser to cache image locally for 10 minutes
-                var end = new Date() - start;
-                res.writeHead(404, {'Content-Type': 'image/png', 'Cache-Control': 'max-age=600, public', 'Response-Time': end, 'Storage-Type': 'local'});
-                res.end(data);
-              });
-              break;
-            default:
-              res.status(404).send('404 Not found');
-              break;
-          }
-        }
-      });
+      res.status(404).send('404 Not found');
     }
-  } else {
-    res.status(422) // "Unprocessable Entity", valid request, but semantically erroneous: https://tools.ietf.org/html/rfc4918#page-78
-    .send("422 Invalid UUID");
+  }
+
+  function sendimage(status, time, image) {
+    res.writeHead(status, {
+      'Content-Type': 'image/png',
+      'Cache-Control': 'max-age=' + config.browser_cache_time + ', public',
+      'Response-Time': time,
+      'X-Storage-Type': 'local'
+    });
+    res.end(image);
   }
 });
 
-module.exports = router;
+
+module.exports = router;

+ 1 - 1
routes/index.js

@@ -7,4 +7,4 @@ router.get('/', function(req, res) {
 });
 
 
-module.exports = router;
+module.exports = router;

+ 3 - 3
bin/www → server.js

@@ -1,9 +1,9 @@
 #!/usr/bin/env node
 var debug = require('debug')('crafatar');
-var app = require('../app');
+var app = require('./app');
 
 app.set('port', process.env.PORT || 3000);
 
 var server = app.listen(app.get('port'), function() {
-  debug('Express server listening on port ' + server.address().port);
-});
+  debug('Crafatar server listening on port ' + server.address().port);
+});

+ 0 - 105
skins.js

@@ -1,105 +0,0 @@
-var request = require('request');
-var lwip = require('lwip');
-
-/*
-* Skin retrieval methods are based on @jomo's CLI Crafatar implementation.
-* https://github.com/jomo/Crafatar
-*/
-
-function extract_face(buffer, outname, callback) {
-  lwip.open(buffer, "png", function(err, image) {
-    if (err) {
-      console.log('c ' + buffer.length);
-      throw err;
-    }
-    image.batch()
-    .crop(8, 8, 15, 15)
-    .writeFile(outname, function(err) {
-      if (err) throw err;
-      callback();
-    });
-  });
-}
-
-module.exports = {
-  get_profile: function(uuid, callback) {
-    request.get({
-      url: "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid,
-      timeout: 1000 // ms
-    }, function (error, response, body) {
-      if (!error && response.statusCode == 200) {
-        callback(JSON.parse(body));
-      } else {
-        if (error) {
-          console.error(error);
-        } else if (response.statusCode == 204 || response.statusCode == 404) {
-          // we get 204 No Content when UUID doesn't exist (including 404 in case they change that)
-        } else if (response.statusCode == 429) {
-          // Too Many Requests
-          console.warn("Too many requests for " + uuid);
-          console.warn(body);
-        } else {
-          console.error("Unknown error:");
-          console.error(response);
-          console.error(body);
-        }
-        callback(null);
-      }
-    });
-  },
-
-  skin_url: function(profile) {
-    var url = null;
-    if (profile && profile.properties) {
-      profile.properties.forEach(function(prop) {
-        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;
-        }
-      });
-    }
-    return url;
-  },
-
-  skin_file: function(url, outname, callback) {
-    request.get({
-      url: url,
-      encoding: null, // encoding must be null so we get a buffer
-      timeout: 1000 // ms
-    }, function (error, response, body) {
-      if (!error && response.statusCode == 200) {
-        extract_face(body, outname, function() {
-          callback();
-        });
-      } else {
-        if (error) {
-          console.error(error);
-        } else if (response.statusCode == 404) {
-          console.warn("Texture not found: " + url);
-        } else if (response.statusCode == 429) {
-          // Too Many Requests
-          // Never got this, seems like textures aren't limited
-          console.warn("Too many requests for " + url);
-          console.warn(body);
-        } else {
-          console.error("Unknown error:");
-          console.error(response);
-          console.error(body);
-        }
-        callback(null);
-      }
-    });
-  },
-
-  resize_img: function(inname, size, callback) {
-    lwip.open(inname, function(err, image) {
-      if (err) throw err;
-      image.batch()
-      .resize(size, size, "nearest-neighbor") // nearest-neighbor doesn't blur
-      .toBuffer('png', function(err, buffer) {
-        callback(buffer);
-      });
-    });
-  }
-};