瀏覽代碼

bug fixes, improvements, fix #13, fix #14

jomo 10 年之前
父節點
當前提交
e6481e3c73
共有 5 個文件被更改,包括 114 次插入68 次删除
  1. 2 2
      modules/config.js
  2. 19 43
      modules/helpers.js
  3. 69 9
      modules/networking.js
  4. 2 2
      routes/avatars.js
  5. 22 12
      test/test.js

+ 2 - 2
modules/config.js

@@ -4,9 +4,9 @@ var config = {
   default_size: 160,         // size to be used when no size given
   local_cache_time: 3600,    // seconds until we will check if the image changed. should be > 60 to prevent mojang 429 response
   browser_cache_time: 3600,  // seconds until browser will request image again
-  http_timeout: 1000,        // ms until connection to mojang is dropped
+  http_timeout: 3000,        // 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;
+module.exports = config;

+ 19 - 43
modules/helpers.js

@@ -5,7 +5,7 @@ var skins = require('./skins');
 
 // 0098cb60-fa8e-427c-b299-793cbd302c9a
 var valid_uuid = /^([0-9a-f-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
-var hash_pattern = /([^\/]+)(?=\.\w{0,16}$)|((?:[a-z][a-z]*[0-9]+[a-z0-9]*))/;
+var hash_pattern = /[0-9a-f]+$/;
 
 function get_hash(url) {
   return hash_pattern.exec(url)[0].toLowerCase();
@@ -14,28 +14,15 @@ function get_hash(url) {
 // requests skin for +uuid+ and extracts face/helm if image hash in +details+ changed
 // callback contains error, image hash
 function store_images(uuid, details, callback) {
-  // get profile for +uuid+
-  networking.get_profile(uuid, function(err, profile) {
-    if (err === 0) {
-      // uuid does not exist
-      cache.save_hash(uuid, null);
-      callback(null, null);
-    } else if (err) {
+  // get skin_url for +uuid+
+  networking.get_skin_url(uuid, function(err, skin_url) {
+    if (err) {
       callback(err, null);
     } else {
-      var skinurl = null;
-
-      // Username handling
-      if (uuid.length <= 16) {
-        skinurl = "https://skins.minecraft.net/MinecraftSkins/" + uuid + ".png";
-        console.log(uuid + " is a username");
-      } else {
-        skinurl = skin_url(profile);
-      }
-      if (skinurl) {
-        console.log(uuid + " " + skinurl);
+      if (skin_url) {
+        console.log(uuid + " " + skin_url);
         // set file paths
-        var hash = get_hash(skinurl);
+        var hash = get_hash(skin_url);
         if (details && details.hash == hash) {
           // hash hasn't changed
           console.log(uuid + " hash has not changed");
@@ -47,7 +34,7 @@ function store_images(uuid, details, callback) {
           var facepath = __dirname + '/../' + config.faces_dir + hash + ".png";
           var helmpath = __dirname + '/../' + config.helms_dir + hash + ".png";
           // download skin, extract face/helm
-          networking.skin_file(skinurl, facepath, helmpath, function(err) {
+          networking.skin_file(skin_url, facepath, helmpath, function(err) {
             if (err) {
               callback(err, null);
             } else {
@@ -65,30 +52,14 @@ function store_images(uuid, details, callback) {
   });
 }
 
-// exracts the skin url of a +profile+ object
-// returns null when no url found (user has no skin)
-function skin_url(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 || null;
-      }
-    });
-  }
-  return url;
-}
-
 // decides whether to get an image from disk or to download it
 // callback contains error, status, hash
 // the status gives information about how the image was received
-//  -1: error
-//   0: cached as null
-//   1: found on disk
-//   2: profile requested/found, skin downloaded from mojang servers
-//   3: profile requested/found, but it has not changed or no skin
+//  -1: "error"
+//   0: "none" - cached as null
+//   1: "cached" - found on disk
+//   2: "downloaded" - profile downloaded, skin downloaded from mojang servers
+//   3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
 function get_image_hash(uuid, callback) {
   cache.get_details(uuid, function(err, details) {
     if (err) {
@@ -100,12 +71,17 @@ function get_image_hash(uuid, callback) {
         callback(null, (details.hash ? 1 : 0), details.hash);
       } else {
         console.log(uuid + " uuid not known or too old");
+        console.log("details:");
+        console.log(details);
+        console.log("/details");
         store_images(uuid, details, function(err, hash) {
           if (err) {
             callback(err, -1, details && details.hash);
           } else {
             console.log(uuid + " hash: " + hash);
-            callback(null, (hash != (details && details.hash) ? 2 : 3), hash);
+            var oldhash = details && details.hash;
+            var status = hash !== oldhash ? 2 : 3;
+            callback(null, status, hash);
           }
         });
       }

+ 69 - 9
modules/networking.js

@@ -4,16 +4,60 @@ var skins = require('./skins');
 var fs = require("fs");
 
 var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
+var skins_url = "https://skins.minecraft.net/MinecraftSkins/";
 
-var exp = {};
-
-// download the Mojang profile for +uuid+
-// callback contains error, profile object
-exp.get_profile = function(uuid, callback) {
-  if (uuid.length <= 16) {
-    callback(null, null);
-    return;
+// exracts the skin url of a +profile+ object
+// returns null when no url found (user has no skin)
+function extract_skin_url(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 || null;
+      }
+    });
   }
+  return url;
+}
+
+// make a request to skins.miencraft.net
+// the skin url is taken from the HTTP redirect
+var get_username_url = function(name, callback) {
+  request.get({
+    url: skins_url + name + ".png",
+    timeout: config.http_timeout,
+    followRedirect: false
+  }, function(error, response, body) {
+    if (!error && response.statusCode == 301) {
+      // skin_url received successfully
+      console.log(name + " skin url received");
+      callback(null, response.headers.location);
+    } else if (error) {
+      callback(error, null);
+    } else if (response.statusCode == 404) {
+      // skin doesn't exist
+      console.log(name + " has no skin");
+      callback(0, null);
+    } else if (response.statusCode == 429) {
+      // Too Many Requests
+      // Never got this, seems like skins aren't limited
+      console.warn(name + " Too many requests");
+      console.warn(body);
+      callback(null, null);
+    } else {
+      console.error(name + " Unknown error:");
+      console.error(response);
+      console.error(body);
+      callback(null, null);
+    }
+  });
+};
+
+// make a request to sessionserver
+// the skin_url is taken from the profile
+var get_uuid_url = function(uuid, callback) {
   request.get({
     url: session_url + uuid,
     timeout: config.http_timeout // ms
@@ -21,7 +65,7 @@ exp.get_profile = function(uuid, callback) {
     if (!error && response.statusCode == 200) {
       // profile downloaded successfully
       console.log(uuid + " profile downloaded");
-      callback(null, JSON.parse(body));
+      callback(null, extract_skin_url(JSON.parse(body)));
     } else if (error) {
       callback(error, null);
     } else if (response.statusCode == 204 || response.statusCode == 404) {
@@ -42,6 +86,22 @@ exp.get_profile = function(uuid, callback) {
   });
 };
 
+var exp = {};
+
+// download skin_url for +uuid+ (name or uuid)
+// callback contains error, skin_url
+exp.get_skin_url = function(uuid, callback) {
+  if (uuid.length <= 16) {
+    get_username_url(uuid, function(err, url) {
+      callback(err, url);
+    });
+  } else {
+    get_uuid_url(uuid, function(err, url) {
+      callback(err, url);
+    });
+  }
+};
+
 // downloads skin file from +url+
 // stores face image as +facename+
 // stores helm image as +helmname+

+ 2 - 2
routes/avatars.js

@@ -6,8 +6,8 @@ var skins = require('../modules/skins');
 var human_status = {
   0: "none",
   1: "cached",
-  2: "checked",
-  3: "downloaded",
+  2: "downloaded",
+  3: "checked",
   "-1": "error"
 };
 

+ 22 - 12
test/test.js

@@ -16,12 +16,12 @@ var usernames = fs.readFileSync('test/usernames.txt').toString().split("\n");
 var uuid = uuids[Math.round(Math.random() * (uuids.length - 1))];
 var username = usernames[Math.round(Math.random() * (usernames.length - 1))];
 
-describe('UUID/username', function() {
+describe('Crafatar', function() {
   before(function() {
     cache.get_redis().flushall();
   });
 
-  describe('UUID', function() {
+  describe('UUID/username', function() {
     it("should be an invalid uuid", function(done) {
       assert.strictEqual(helpers.uuid_valid("g098cb60fa8e427cb299793cbd302c9a"), false);
       done();
@@ -58,43 +58,51 @@ describe('UUID/username', function() {
       assert.strictEqual(helpers.uuid_valid("a"), true);
       done();
     });
-    it("should not exist", function(done) {
-      networking.get_profile("00000000000000000000000000000000", function(err, profile) {
+    it("should not exist (uuid)", function(done) {
+      networking.get_skin_url("00000000000000000000000000000000", function(err, profile) {
+        assert.strictEqual(err, 0);
+        done();
+      });
+    });
+    it("should not exist (username)", function(done) {
+      networking.get_skin_url("Steve", function(err, profile) {
         assert.strictEqual(err, 0);
         done();
       });
     });
   });
 
-  describe('Avatar', function() {
+  describe('Networking: Avatar', function() {
     it("should be downloaded (uuid)", function(done) {
       helpers.get_avatar(uuid, false, 160, function(err, status, image) {
         assert.strictEqual(status, 2);
         done();
       });
     });
-    it("should be local (uuid)", function(done) {
+    it("should be cached (uuid)", function(done) {
       helpers.get_avatar(uuid, false, 160, function(err, status, image) {
         assert.strictEqual(status, 1);
         done();
       });
     });
+    /* We can't test this because of mojang's rate limits :(
     it("should be checked (uuid)", function(done) {
       var original_cache_time = config.local_cache_time;
       config.local_cache_time = 0;
       helpers.get_avatar(uuid, false, 160, function(err, status, image) {
-        assert.strictEqual(status, 2);
+        assert.strictEqual(status, 3);
         config.local_cache_time = original_cache_time;
         done();
       });
     });
+    */
     it("should be downloaded (username)", function(done) {
       helpers.get_avatar(username, false, 160, function(err, status, image) {
         assert.strictEqual(status, 2);
         done();
       });
     });
-    it("should be local (username)", function(done) {
+    it("should be cached (username)", function(done) {
       helpers.get_avatar(username, false, 160, function(err, status, image) {
         assert.strictEqual(status, 1);
         done();
@@ -104,7 +112,7 @@ describe('UUID/username', function() {
       var original_cache_time = config.local_cache_time;
       config.local_cache_time = 0;
       helpers.get_avatar(username, false, 160, function(err, status, image) {
-        assert.strictEqual(status, 2);
+        assert.strictEqual(status, 3);
         config.local_cache_time = original_cache_time;
         done();
       });
@@ -137,18 +145,20 @@ describe('UUID/username', function() {
       });
     });
     it("should time out on profile download", function(done) {
+      var original_timeout = config.http_timeout;
       config.http_timeout = 1;
-      networking.get_profile("069a79f444e94726a5befca90e38aaf5", function(err, profile) {
+      networking.get_skin_url("069a79f444e94726a5befca90e38aaf5", function(err, profile) {
         assert.strictEqual(err.code, "ETIMEDOUT");
-        config.http_timeout = 3000;
+        config.http_timeout = original_timeout;
         done();
       });
     });
     it("should time out on skin download", function(done) {
+      var original_timeout = config.http_timeout;
       config.http_timeout = 1;
       networking.skin_file("http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", "face.png", "helm.png", function(err) {
         assert.strictEqual(err.code, "ETIMEDOUT");
-        config.http_timeout = 3000;
+        config.http_timeout = original_timeout;
         done();
       });
     });