<commit_before>(function() {
  var crownTypes = ["Bronze", "Silver", "Gold", "Platinum", "Diamond"];

  var crownValues = {
    // default, min, max
    Bronze: [1, 1, 9],
    Silver: [90, 10, 99],
    Gold: [400, 100, 499],
    Platinum: [750, 500, 999],
    Diamond: [2000, 1000, 2499]
  };

  function buildUI() {
    if (!window.jQuery) {
      alert("Error: jQuery is not enabled on this page");
      return;
    }

    // Delete existing elements
    document.querySelectorAll(".mht-crown-solver").forEach(function(el) {
      el.remove();
    });

    var crownData = {
      bronze: [],
      silver: [],
      gold: [],
      platinum: [],
      diamond: []
    };

    // Populate 'crownData' catch stats object
    var statsRaw = localStorage.getItem("mh-catch-stats");
    if (statsRaw) {
      var allStats = JSON.parse(statsRaw);
      for (var mouse in allStats) {
        // Search for index of ' Mouse' and trim up to it
        var mouseName = mouse;
        var index = mouse.indexOf(" Mouse");
        if (index > 0) mouseName = mouse.slice(0, index);

        // Mouse name edge cases
        if (mouseName === "Dread Pirate") mouseName = "Dread Pirate Mousert";
        if (mouseName.indexOf("Thunderlord") >= 0) mouseName = "Thunderlord";

        var count = allStats[mouse];
        if (count < 10) {
          crownData["bronze"].push([mouseName, count]);
        } else if (count < 100) {
          crownData["silver"].push([mouseName, count]);
        } else if (count < 500) {
          crownData["gold"].push([mouseName, count]);
        } else if (count < 1000) {
          crownData["platinum"].push([mouseName, count]);
        } else if (count < 2500) {
          crownData["diamond"].push([mouseName, count]);
        }
      }
    }

    var mainDiv = document.createElement("div");
    mainDiv.className = "mht-crown-solver";

    var closeButton = document.createElement("button");
    closeButton.innerText = "x";
    closeButton.onclick = function() {
      document.body.removeChild(mainDiv);
    };

    var titleSpan = document.createElement("span");
    titleSpan.style.fontSize = "15px";
    titleSpan.style.fontWeight = "bold";
    titleSpan.appendChild(document.createTextNode("Crown Solver Bookmarklet"));

    var descriptionSpan = document.createElement("span");
    descriptionSpan.innerHTML = "Select desired crown types and cutoff values";

    var checkboxTable = document.createElement("table");
    checkboxTable.style.textAlign = "left";

    var cacheData;
    var cacheRaw = localStorage.getItem("tsitu-bm-crown");
    if (cacheRaw) cacheData = JSON.parse(cacheRaw);

    for (var i = 0; i < crownTypes.length; i++) {
      var crownName = crownTypes[i];
      var crownLower = crownName.toLowerCase();
      var crownTypeSpan = document.createElement("span");
      crownTypeSpan.className = "mht-crown-solver span-" + crownLower;

      var crownBox = document.createElement("input");
      crownBox.type = "checkbox";
      crownBox.className = "mht-crown-solver box-" + crownLower;
      crownBox.name = "mht-crown-solver box-" + crownLower;
      if (cacheData) crownBox.checked = cacheData[crownLower][0];

      var crownBoxLabel = document.createElement("label");
      crownBoxLabel.className = "mht-crown-solver";
      crownBoxLabel.htmlFor = "mht-crown-solver box-" + crownLower;
      crownBoxLabel.innerText = crownName;

      crownTypeSpan.appendChild(crownBox);
      crownTypeSpan.appendChild(crownBoxLabel);

      var crownCutoff = document.createElement("input");
      crownCutoff.type = "number";
      crownCutoff.style.width = "50px";
      crownCutoff.defaultValue = crownValues[crownName][0];
      crownCutoff.min = crownValues[crownName][1];
      crownCutoff.max = crownValues[crownName][2];
      crownCutoff.className = "mht-crown-solver cutoff-" + crownLower;
      crownCutoff.name = "mht-crown-solver cutoff-" + crownLower;
      if (cacheData) crownCutoff.value = cacheData[crownLower][1];

      var tableRow = document.createElement("tr");
      var boxCol = document.createElement("td");
      boxCol.style.paddingLeft = "35px";
      boxCol.style.paddingRight = "20px";
      var cutoffCol = document.createElement("td");
      boxCol.appendChild(crownTypeSpan);
      cutoffCol.appendChild(crownCutoff);
      tableRow.appendChild(boxCol);
      tableRow.appendChild(cutoffCol);
      checkboxTable.appendChild(tableRow);
    }

    // Bottom row operations
    var allCheckbox = document.createElement("input");
    allCheckbox.type = "checkbox";
    allCheckbox.className = "mht-crown-solver box-all";
    allCheckbox.name = "mht-crown-solver box-all";
    allCheckbox.onclick = function() {
      if (document.querySelector(".mht-crown-solver.box-all").checked) {
        document
          .querySelectorAll(
            "input.mht-crown-solver[type='checkbox']:not(.box-all)"
          )
          .forEach(function(el) {
            if (!el.checked) el.checked = true;
          });
      } else {
        document
          .querySelectorAll(
            "input.mht-crown-solver[type='checkbox']:not(.box-all)"
          )
          .forEach(function(el) {
            if (el.checked) el.checked = false;
          });
      }
    };

    var allCheckboxLabel = document.createElement("label");
    allCheckboxLabel.className = "mht-crown-solver";
    allCheckboxLabel.htmlFor = "mht-crown-solver box-all";
    allCheckboxLabel.innerText = "All";

    var revertCutoffButton = document.createElement("button");
    revertCutoffButton.innerText = "Revert";
    revertCutoffButton.onclick = function() {
      document
        .querySelectorAll("input.mht-crown-solver[type='number']")
        .forEach(function(el) {
          var crownNameRaw = el.className.split("cutoff-")[1];
          var crownName =
            crownNameRaw.charAt(0).toUpperCase() + crownNameRaw.slice(1);
          el.value = crownValues[crownName][0];
        });
    };

    var allRow = document.createElement("tr");
    var allCheckCol = document.createElement("td");
    allCheckCol.style.paddingLeft = "35px";
    allCheckCol.style.paddingRight = "20px";
    allCheckCol.appendChild(allCheckbox);
    allCheckCol.appendChild(allCheckboxLabel);
    var revertCutoffCol = document.createElement("td");
    revertCutoffCol.appendChild(revertCutoffButton);
    allRow.appendChild(allCheckCol);
    allRow.appendChild(revertCutoffCol);
    checkboxTable.appendChild(allRow);

    // Last updated timestamp
    var updatedTimestamp = document.createElement("span");
    updatedTimestamp.style.fontSize = "12px";
    var updateText = "Catches last updated: N/A";
    var tsRaw = localStorage.getItem("mh-catch-stats-timestamp");
    if (tsRaw) {
      updateText =
        "Catches last updated:\n" + new Date(parseInt(tsRaw)).toLocaleString();
    }
    updatedTimestamp.innerText = updateText;

    var goButton = document.createElement("button");
    goButton.style.fontWeight = "bold";
    goButton.style.fontSize = "16px";
    goButton.innerText = "Go!";
    goButton.onclick = function() {
      if (statsRaw) {
        var outputObj = {};
        document
          .querySelectorAll(
            "input.mht-crown-solver[type='checkbox']:not(.box-all)"
          )
          .forEach(function(el) {
            if (el.checked) {
              var elName = el.className.split("box-")[1];
              var elNameCap = elName.charAt(0).toUpperCase() + elName.slice(1);
              var cutoffValue = parseInt(
                document.querySelector(".mht-crown-solver.cutoff-" + elName)
                  .value
              );

              if (
                cutoffValue >= crownValues[elNameCap][1] &&
                cutoffValue <= crownValues[elNameCap][2]
              ) {
                for (var i = 0; i < crownData[elName].length; i++) {
                  var mouseData = crownData[elName][i];
                  if (mouseData[1] >= cutoffValue) {
                    outputObj[mouseData[0]] = mouseData[1];
                  }
                }
              }
            }
          });

        var outputLength = Object.keys(outputObj).length;
        if (outputLength === 0) {
          alert("No valid mice to send");
        } else {
          var shouldProceed = true;
          if (outputLength >= 100) {
            shouldProceed = false;
            if (confirm("100 or more mice are about to be sent. Proceed?")) {
              shouldProceed = true;
            }
          }
          if (shouldProceed) {
            // console.log(outputLength);
            // console.log(outputObj);
            var newWindow = window.open("");
            newWindow.name = JSON.stringify(outputObj);
            newWindow.location = "https://tsitu.github.io/MH-Tools/crown.html";
            // newWindow.location = "http://localhost:8000/crown.html";
          }
        }
      } else {
        alert("No catch data found. Please click the 'Fetch' button");
      }
    };

    var fetchButton = document.createElement("button");
    fetchButton.innerText = "Fetch";
    fetchButton.onclick = function() {
      $.post(
        "https://www.mousehuntgame.com/managers/ajax/users/profiletabs.php?action=badges&snuid=" +
          user.sn_user_id,
        {},
        null,
        "json"
      ).done(function(response) {
        if (response) {
          var badgeData = response["mouse_data"];
          var remainData = response["remaining_mice"];
          var catchData = {};

          Object.keys(badgeData).forEach(function(key) {
            catchData[badgeData[key]["name"]] = badgeData[key]["num_catches"];
          });

          remainData.forEach(function(el) {
            var split = el["name"].split(" (");
            catchData[split[0]] = parseInt(split[1][0]);
          });

          localStorage.setItem("mh-catch-stats", JSON.stringify(catchData));
          localStorage.setItem("mh-catch-stats-timestamp", Date.now());
          buildUI();
        }
      });
    };

    var saveButton = document.createElement("button");
    saveButton.innerText = "Save";
    saveButton.onclick = function() {
      var saveObj = {
        bronze: [undefined, undefined],
        silver: [undefined, undefined],
        gold: [undefined, undefined],
        platinum: [undefined, undefined],
        diamond: [undefined, undefined]
      };

      document
        .querySelectorAll(
          "input.mht-crown-solver[type='checkbox']:not(.box-all)"
        )
        .forEach(function(el) {
          var name = el.className.split("box-")[1];
          saveObj[name][0] = el.checked;
        });

      document
        .querySelectorAll("input.mht-crown-solver[type='number']")
        .forEach(function(el) {
          var name = el.className.split("cutoff-")[1];
          saveObj[name][1] = el.value;
        });

      localStorage.setItem("tsitu-bm-crown", JSON.stringify(saveObj));
    };

    mainDiv.appendChild(closeButton);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(titleSpan);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(descriptionSpan);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(checkboxTable);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(updatedTimestamp);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(goButton);
    mainDiv.appendChild(document.createTextNode("\u00A0"));
    mainDiv.appendChild(fetchButton);
    mainDiv.appendChild(document.createTextNode("\u00A0"));
    mainDiv.appendChild(saveButton);

    mainDiv.style.backgroundColor = "#F5F5F5";
    mainDiv.style.position = "fixed";
    mainDiv.style.zIndex = "9999";
    mainDiv.style.left = "57%";
    mainDiv.style.top = "88px";
    mainDiv.style.border = "solid 3px #696969";
    mainDiv.style.borderRadius = "20px";
    mainDiv.style.padding = "10px";
    mainDiv.style.textAlign = "center";
    document.body.appendChild(mainDiv);
  }

  buildUI();
})();
<commit_msg>Added Leprechaun/Mobster check to bm-crown<commit_after>(function() {
  var crownTypes = ["Bronze", "Silver", "Gold", "Platinum", "Diamond"];

  var crownValues = {
    // default, min, max
    Bronze: [1, 1, 9],
    Silver: [90, 10, 99],
    Gold: [400, 100, 499],
    Platinum: [750, 500, 999],
    Diamond: [2000, 1000, 2499]
  };

  function buildUI() {
    if (!window.jQuery) {
      alert("Error: jQuery is not enabled on this page");
      return;
    }

    // Delete existing elements
    document.querySelectorAll(".mht-crown-solver").forEach(function(el) {
      el.remove();
    });

    var crownData = {
      bronze: [],
      silver: [],
      gold: [],
      platinum: [],
      diamond: []
    };

    // Populate 'crownData' catch stats object
    var statsRaw = localStorage.getItem("mh-catch-stats");
    if (statsRaw) {
      var allStats = JSON.parse(statsRaw);
      for (var mouse in allStats) {
        // Search for index of ' Mouse' and trim up to it
        var mouseName = mouse;
        var index = mouse.indexOf(" Mouse");
        if (index > 0) mouseName = mouse.slice(0, index);

        // Skip uber-rare prize mice
        if (mouseName === "Leprechaun" || mouseName === "Mobster") continue;

        // Mouse name edge cases
        if (mouseName === "Dread Pirate") mouseName = "Dread Pirate Mousert";
        if (mouseName.indexOf("Thunderlord") >= 0) mouseName = "Thunderlord";

        var count = allStats[mouse];
        if (count < 10) {
          crownData["bronze"].push([mouseName, count]);
        } else if (count < 100) {
          crownData["silver"].push([mouseName, count]);
        } else if (count < 500) {
          crownData["gold"].push([mouseName, count]);
        } else if (count < 1000) {
          crownData["platinum"].push([mouseName, count]);
        } else if (count < 2500) {
          crownData["diamond"].push([mouseName, count]);
        }
      }
    }

    var mainDiv = document.createElement("div");
    mainDiv.className = "mht-crown-solver";

    var closeButton = document.createElement("button");
    closeButton.innerText = "x";
    closeButton.onclick = function() {
      document.body.removeChild(mainDiv);
    };

    var titleSpan = document.createElement("span");
    titleSpan.style.fontSize = "15px";
    titleSpan.style.fontWeight = "bold";
    titleSpan.appendChild(document.createTextNode("Crown Solver Bookmarklet"));

    var descriptionSpan = document.createElement("span");
    descriptionSpan.innerHTML = "Select desired crown types and cutoff values";

    var checkboxTable = document.createElement("table");
    checkboxTable.style.textAlign = "left";

    var cacheData;
    var cacheRaw = localStorage.getItem("tsitu-bm-crown");
    if (cacheRaw) cacheData = JSON.parse(cacheRaw);

    for (var i = 0; i < crownTypes.length; i++) {
      var crownName = crownTypes[i];
      var crownLower = crownName.toLowerCase();
      var crownTypeSpan = document.createElement("span");
      crownTypeSpan.className = "mht-crown-solver span-" + crownLower;

      var crownBox = document.createElement("input");
      crownBox.type = "checkbox";
      crownBox.className = "mht-crown-solver box-" + crownLower;
      crownBox.name = "mht-crown-solver box-" + crownLower;
      if (cacheData) crownBox.checked = cacheData[crownLower][0];

      var crownBoxLabel = document.createElement("label");
      crownBoxLabel.className = "mht-crown-solver";
      crownBoxLabel.htmlFor = "mht-crown-solver box-" + crownLower;
      crownBoxLabel.innerText = crownName;

      crownTypeSpan.appendChild(crownBox);
      crownTypeSpan.appendChild(crownBoxLabel);

      var crownCutoff = document.createElement("input");
      crownCutoff.type = "number";
      crownCutoff.style.width = "50px";
      crownCutoff.defaultValue = crownValues[crownName][0];
      crownCutoff.min = crownValues[crownName][1];
      crownCutoff.max = crownValues[crownName][2];
      crownCutoff.className = "mht-crown-solver cutoff-" + crownLower;
      crownCutoff.name = "mht-crown-solver cutoff-" + crownLower;
      if (cacheData) crownCutoff.value = cacheData[crownLower][1];

      var tableRow = document.createElement("tr");
      var boxCol = document.createElement("td");
      boxCol.style.paddingLeft = "35px";
      boxCol.style.paddingRight = "20px";
      var cutoffCol = document.createElement("td");
      boxCol.appendChild(crownTypeSpan);
      cutoffCol.appendChild(crownCutoff);
      tableRow.appendChild(boxCol);
      tableRow.appendChild(cutoffCol);
      checkboxTable.appendChild(tableRow);
    }

    // Bottom row operations
    var allCheckbox = document.createElement("input");
    allCheckbox.type = "checkbox";
    allCheckbox.className = "mht-crown-solver box-all";
    allCheckbox.name = "mht-crown-solver box-all";
    allCheckbox.onclick = function() {
      if (document.querySelector(".mht-crown-solver.box-all").checked) {
        document
          .querySelectorAll(
            "input.mht-crown-solver[type='checkbox']:not(.box-all)"
          )
          .forEach(function(el) {
            if (!el.checked) el.checked = true;
          });
      } else {
        document
          .querySelectorAll(
            "input.mht-crown-solver[type='checkbox']:not(.box-all)"
          )
          .forEach(function(el) {
            if (el.checked) el.checked = false;
          });
      }
    };

    var allCheckboxLabel = document.createElement("label");
    allCheckboxLabel.className = "mht-crown-solver";
    allCheckboxLabel.htmlFor = "mht-crown-solver box-all";
    allCheckboxLabel.innerText = "All";

    var revertCutoffButton = document.createElement("button");
    revertCutoffButton.innerText = "Revert";
    revertCutoffButton.onclick = function() {
      document
        .querySelectorAll("input.mht-crown-solver[type='number']")
        .forEach(function(el) {
          var crownNameRaw = el.className.split("cutoff-")[1];
          var crownName =
            crownNameRaw.charAt(0).toUpperCase() + crownNameRaw.slice(1);
          el.value = crownValues[crownName][0];
        });
    };

    var allRow = document.createElement("tr");
    var allCheckCol = document.createElement("td");
    allCheckCol.style.paddingLeft = "35px";
    allCheckCol.style.paddingRight = "20px";
    allCheckCol.appendChild(allCheckbox);
    allCheckCol.appendChild(allCheckboxLabel);
    var revertCutoffCol = document.createElement("td");
    revertCutoffCol.appendChild(revertCutoffButton);
    allRow.appendChild(allCheckCol);
    allRow.appendChild(revertCutoffCol);
    checkboxTable.appendChild(allRow);

    // Last updated timestamp
    var updatedTimestamp = document.createElement("span");
    updatedTimestamp.style.fontSize = "12px";
    var updateText = "Catches last updated: N/A";
    var tsRaw = localStorage.getItem("mh-catch-stats-timestamp");
    if (tsRaw) {
      updateText =
        "Catches last updated:\n" + new Date(parseInt(tsRaw)).toLocaleString();
    }
    updatedTimestamp.innerText = updateText;

    var goButton = document.createElement("button");
    goButton.style.fontWeight = "bold";
    goButton.style.fontSize = "16px";
    goButton.innerText = "Go!";
    goButton.onclick = function() {
      if (statsRaw) {
        var outputObj = {};
        document
          .querySelectorAll(
            "input.mht-crown-solver[type='checkbox']:not(.box-all)"
          )
          .forEach(function(el) {
            if (el.checked) {
              var elName = el.className.split("box-")[1];
              var elNameCap = elName.charAt(0).toUpperCase() + elName.slice(1);
              var cutoffValue = parseInt(
                document.querySelector(".mht-crown-solver.cutoff-" + elName)
                  .value
              );

              if (
                cutoffValue >= crownValues[elNameCap][1] &&
                cutoffValue <= crownValues[elNameCap][2]
              ) {
                for (var i = 0; i < crownData[elName].length; i++) {
                  var mouseData = crownData[elName][i];
                  if (mouseData[1] >= cutoffValue) {
                    outputObj[mouseData[0]] = mouseData[1];
                  }
                }
              }
            }
          });

        var outputLength = Object.keys(outputObj).length;
        if (outputLength === 0) {
          alert("No valid mice to send");
        } else {
          var shouldProceed = true;
          if (outputLength >= 100) {
            shouldProceed = false;
            if (confirm("100 or more mice are about to be sent. Proceed?")) {
              shouldProceed = true;
            }
          }
          if (shouldProceed) {
            // console.log(outputLength);
            // console.log(outputObj);
            var newWindow = window.open("");
            newWindow.name = JSON.stringify(outputObj);
            newWindow.location = "https://tsitu.github.io/MH-Tools/crown.html";
            // newWindow.location = "http://localhost:8000/crown.html";
          }
        }
      } else {
        alert("No catch data found. Please click the 'Fetch' button");
      }
    };

    var fetchButton = document.createElement("button");
    fetchButton.innerText = "Fetch";
    fetchButton.onclick = function() {
      $.post(
        "https://www.mousehuntgame.com/managers/ajax/users/profiletabs.php?action=badges&snuid=" +
          user.sn_user_id,
        {},
        null,
        "json"
      ).done(function(response) {
        if (response) {
          var badgeData = response["mouse_data"];
          var remainData = response["remaining_mice"];
          var catchData = {};

          Object.keys(badgeData).forEach(function(key) {
            catchData[badgeData[key]["name"]] = badgeData[key]["num_catches"];
          });

          remainData.forEach(function(el) {
            var split = el["name"].split(" (");
            catchData[split[0]] = parseInt(split[1][0]);
          });

          localStorage.setItem("mh-catch-stats", JSON.stringify(catchData));
          localStorage.setItem("mh-catch-stats-timestamp", Date.now());
          buildUI();
        }
      });
    };

    var saveButton = document.createElement("button");
    saveButton.innerText = "Save";
    saveButton.onclick = function() {
      var saveObj = {
        bronze: [undefined, undefined],
        silver: [undefined, undefined],
        gold: [undefined, undefined],
        platinum: [undefined, undefined],
        diamond: [undefined, undefined]
      };

      document
        .querySelectorAll(
          "input.mht-crown-solver[type='checkbox']:not(.box-all)"
        )
        .forEach(function(el) {
          var name = el.className.split("box-")[1];
          saveObj[name][0] = el.checked;
        });

      document
        .querySelectorAll("input.mht-crown-solver[type='number']")
        .forEach(function(el) {
          var name = el.className.split("cutoff-")[1];
          saveObj[name][1] = el.value;
        });

      localStorage.setItem("tsitu-bm-crown", JSON.stringify(saveObj));
    };

    mainDiv.appendChild(closeButton);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(titleSpan);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(descriptionSpan);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(checkboxTable);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(updatedTimestamp);
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(document.createElement("br"));
    mainDiv.appendChild(goButton);
    mainDiv.appendChild(document.createTextNode("\u00A0"));
    mainDiv.appendChild(fetchButton);
    mainDiv.appendChild(document.createTextNode("\u00A0"));
    mainDiv.appendChild(saveButton);

    mainDiv.style.backgroundColor = "#F5F5F5";
    mainDiv.style.position = "fixed";
    mainDiv.style.zIndex = "9999";
    mainDiv.style.left = "57%";
    mainDiv.style.top = "88px";
    mainDiv.style.border = "solid 3px #696969";
    mainDiv.style.borderRadius = "20px";
    mainDiv.style.padding = "10px";
    mainDiv.style.textAlign = "center";
    document.body.appendChild(mainDiv);
  }

  buildUI();
})();
<|endoftext|>


<commit_before>#!/usr/bin/env node
//
//   Copyright 2015 Ilkka Oksanen <iao@iki.fi>
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing,
//   software distributed under the License is distributed on an "AS
//   IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//   express or implied.  See the License for the specific language
//   governing permissions and limitations under the License.
//

'use strict';

const readlineSync = require('readline-sync'),
      assert = require('assert'),
      co = require('co'),
      redisModule = require('../server/lib/redis'),
      redis = redisModule.createClient();

const tests = [
    desktopTest,
    activeDesktopTest,
    conversationIndexTest,
    conversationIndexAccessTest,
    conversationMembersTest,
    conversationListTest,
    oneOnOneHistoryTest,
    windowIndexTest,
    windowTest,
    windowlistTest,
    friendsExistTest
];

console.log(' ************************************************************************');
console.log(' *** This is a experimental MAS Redis database consistency checking tool');
console.log(' ************************************************************************');

let response = readlineSync.question('Are you sure you want to continue? [yes/no]: ');

if (response !== 'yes') {
    process.exit(1);
}

co(function*() {
    for (let test of tests) {
        yield test();
    }

    yield redis.quit();
    console.log('DONE');
})();

function *conversationIndexTest() {
    let conversationKeys = (yield redis.keys('conversation:*'));
    let conversationIndexFieldsLength = yield redis.hlen('index:conversation');

    let passed = conversationKeys.length === conversationIndexFieldsLength;

    printVerdict('index:conversation', passed);

    if (!passed) {
        console.log('Rebuilding index:conversation...');
        yield redis.del('index:conversation');

        for (let conversationKey of conversationKeys) {
            let conversation = yield redis.hgetall(conversationKey);
            let conversationId = conversationKey.split(':')[1];
            let key;

            let members = yield redis.hgetall(`conversationmembers:${conversationId}`);

            if (conversation.type === 'group') {
                key = `group:${conversation.network}:${conversation.name}`;
            } else {
                let users = Object.keys(members).sort();
                key = `1on1:${conversation.network}:${users[0]}:${users[1]}`;
            }

            yield redis.hset('index:conversation', key, conversationId);
        }
    }
}

function *conversationIndexAccessTest() {
    let index = yield redis.hgetall('index:conversation');
    let indexKeys = Object.keys(index);

    for (let key of indexKeys) {
        let keyParts = key.split(':');
        let type = keyParts[0];
        let network = keyParts[1];

        assert(type === 'group' || type === '1on1');
        assert(network === 'IRCNet' || network === 'MAS' ||  network === 'FreeNode' ||
            network === 'W3C' || network === 'Flowdock');

        if (type === '1on1') {
            let user1 = keyParts[2];
            let user2 = keyParts[3];

            assert(user1.charAt(0) === 'm' || user2.charAt(0) === 'm');
        }

        let conversation = yield redis.hgetall(`conversation:${index[key]}`);

        assert(conversation);
    }

    printVerdict('conversation:index', true);
}

function *conversationMembersTest() {
    let conversationKeys = yield redis.keys('conversation:*');
    let conversationMembersKeys = yield redis.keys('conversationmembers:*');

    assert(conversationKeys.length === conversationMembersKeys.length);

    for (let conversationKey of conversationKeys) {
        let conversationId = conversationKey.split(':')[1];
        let conversation = yield redis.hgetall(conversationKey);
        let members = yield redis.hgetall(`conversationmembers:${conversationId}`);

        assert(members);

        for (let userId of Object.keys(members)) {
            if (userId.charAt(0) === 'm') {
                assert((yield redis.exists(`user:${userId}`)));
            }
        }

        if (conversation.type === '1on1' && Object.keys(members).length !== 2) {
            console.log('Removing invalid 1on1, conversationId: ' + conversationId + '...');
            yield removeConversation(conversationId);
        }
    }

    printVerdict('conversationmembers', true);
}

function *conversationListTest() {
    let conversationListKeys = yield redis.keys('conversationlist:*');

    for (let conversationListKey of conversationListKeys) {
        let list = yield redis.smembers(conversationListKey);

        for (let conversationId of list) {
            let conversation = yield redis.hgetall(`conversation:${conversationId}`);

            assert(conversation);
        }
    }

    printVerdict('conversationlists', true);
}

function *oneOnOneHistoryTest() {
    let conversationHistoryKeys = yield redis.keys('1on1conversationhistory:*');

    for (let conversationHistoryKey of conversationHistoryKeys) {
        let list = yield redis.smembers(conversationHistoryKey);

        for (let conversationId of list) {
            let conversation = yield redis.hgetall(`conversation:${conversationId}`);

            if (!conversation) {
                console.log('Removing invalid 1on1conversationhistory entry...');
                yield redis.srem(conversationHistoryKey, conversationId);
            }
        }
    }

    printVerdict('1on1conversationhistory', true);
}

function *windowIndexTest() {
    let windowKeys = yield redis.keys('window:*');
    let windowIndexEntries = yield redis.hgetall('index:windowIds');

    let passed = windowKeys.length === Object.keys(windowIndexEntries).length;

    for (let entry of Object.keys(windowIndexEntries)) {
        let windowId = windowIndexEntries[entry];
        let userId = entry.split(':')[0];
        let conversationId = entry.split(':')[1];

        assert((yield redis.exists(`user:${userId}`)));

        let conversationExists = yield redis.exists(`conversation:${conversationId}`);

        if (!conversationExists) {
            console.log(`Removing orphan windowId: ${windowId}, userId: ${userId}`);

            yield redis.del(`window:${userId}:${windowId}`);
            yield redis.hdel('index:windowIds', `${userId}:${conversationId}`);
            yield redis.srem(`windowlist:${userId}`, windowId);
        }

        let windowExists = yield redis.exists(`window:${userId}:${windowId}`);

        if (!windowExists) {
            console.log(`Invalid windowlist entry, userId: ${userId}`);
        }

    }

    printVerdict('index:windowIds', passed);
}

function *windowTest() {
    let windowKeys = yield redis.keys('window:*');

    for (let windowKey of windowKeys) {
        let windowItem = yield redis.hgetall(windowKey);

        let userId = windowKey.split(':')[1];
        assert((yield redis.exists(`user:${userId}`)));

        assert((yield redis.exists(`conversation:${windowItem.conversationId}`)));
    }

    printVerdict('windows', true);
}

function *windowlistTest() {
    let windowListKeys = yield redis.keys('windowlist:*');

    for (let windowListKey of windowListKeys) {
        let userId = windowListKey.split(':')[1];
        assert((yield redis.exists(`user:${userId}`)));

        let windowIds = yield redis.smembers(windowListKey);

        for (let windowId of windowIds) {
            assert((yield redis.exists(`window:${userId}:${windowId}`)));
        }
    }

    printVerdict('windowlist', true);
}

function *friendsExistTest() {
    let friendsKeys = yield redis.keys('friends:*');

    for (let friendsKey of friendsKeys) {
        let friends = yield redis.smembers(friendsKey);

        for (let userId of friends) {
            let exists = yield redis.exists(`user:${userId}`);

            if (!exists) {
                console.log(`${friendsKeys} has non-existing friend ${userId}`);
            }
        }
    }

    printVerdict('friends', true);
}

function *desktopTest() {
    let windowKeys = yield redis.keys('window:*');

    for (let windowKey of windowKeys) {
        let masWindow = yield redis.hgetall(windowKey);

        if (masWindow.desktop !== null && isNaN(masWindow.desktop)) {
            console.log(`Fixing invalid window.desktop for ${windowKey}`);
            yield redis.hset(windowKey, 'desktop', 0);
        }
    }

    printVerdict('window.desktop', true);
}

function *activeDesktopTest() {
    let settingsKeys = yield redis.keys('settings:*');

    for (let settingsKey of settingsKeys) {
        let activeDesktop = yield redis.hget(settingsKey, 'activeDesktop');

        if (activeDesktop !== null) {
            let userId = settingsKey.split(':')[1];
            let windowKeys = yield redis.keys(`window:${userId}:*`);
            let found = false;

            for (let windowKey of windowKeys) {
                let desktop = yield redis.hget(windowKey, 'desktop');

                if (desktop === activeDesktop) {
                    found = true;
                    break;
                }
            }

            if (!found) {
                console.log('ERROR: found invalid activeDesktop setting');
            }
        }
    }

    printVerdict('settings.activeDesktop', true);
}

function printVerdict(desc, passed) {
    console.log('Checking ' + desc + ': ' + (passed ? '[PASS]' : '[FAIL]'));
}

function *removeConversation(conversationId) {
    let conversation = yield redis.hgetall(`conversation:${conversationId}`);
    let members = yield redis.hgetall(`conversationmembers:${conversationId}`);

    yield redis.del(`conversation:${conversationId}`);
    yield redis.del(`conversationmsgs:${conversationId}`);
    yield redis.del(`conversationmembers:${conversationId}`);

    for (let userId of Object.keys(members)) {
        yield redis.srem(`conversationlist:${userId}`, conversationId);
    }

    let key;

    if (conversation.type === 'group') {
        key = 'group:' + conversation.network + ':' + conversation.name;
    } else {
        let userIds = Object.keys(members);
        userIds = userIds.sort();
        key = '1on1:' + conversation.network + ':' + userIds[0] + ':' + userIds[1];
    }

    yield redis.hdel('index:conversation', key);
}
<commit_msg>Add conversation:index case insensitive check and fix<commit_after>#!/usr/bin/env node
//
//   Copyright 2015 Ilkka Oksanen <iao@iki.fi>
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing,
//   software distributed under the License is distributed on an "AS
//   IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//   express or implied.  See the License for the specific language
//   governing permissions and limitations under the License.
//

'use strict';

const readlineSync = require('readline-sync'),
      assert = require('assert'),
      co = require('co'),
      redisModule = require('../server/lib/redis'),
      redis = redisModule.createClient();

const tests = [
    desktopTest,
    activeDesktopTest,
    conversationIndexTest,
    conversationIndexAccessTest,
    conversationMembersTest,
    conversationListTest,
    oneOnOneHistoryTest,
    windowIndexTest,
    windowTest,
    windowlistTest,
    friendsExistTest,
    conversationIndexCaseTest
];

console.log(' ************************************************************************');
console.log(' *** This is a experimental MAS Redis database consistency checking tool');
console.log(' ************************************************************************');

let response = readlineSync.question('Are you sure you want to continue? [yes/no]: ');

if (response !== 'yes') {
    process.exit(1);
}

co(function*() {
    for (let test of tests) {
        yield test();
    }

    yield redis.quit();
    console.log('DONE');
})();

function *conversationIndexTest() {
    let conversationKeys = (yield redis.keys('conversation:*'));
    let conversationIndexFieldsLength = yield redis.hlen('index:conversation');

    let passed = conversationKeys.length === conversationIndexFieldsLength;

    printVerdict('index:conversation', passed);

    if (!passed) {
        console.log('Rebuilding index:conversation...');
        yield redis.del('index:conversation');

        for (let conversationKey of conversationKeys) {
            let conversation = yield redis.hgetall(conversationKey);
            let conversationId = conversationKey.split(':')[1];
            let key;

            let members = yield redis.hgetall(`conversationmembers:${conversationId}`);

            if (conversation.type === 'group') {
                key = `group:${conversation.network}:${conversation.name}`;
            } else {
                let users = Object.keys(members).sort();
                key = `1on1:${conversation.network}:${users[0]}:${users[1]}`;
            }

            yield redis.hset('index:conversation', key, conversationId);
        }
    }
}

function *conversationIndexAccessTest() {
    let index = yield redis.hgetall('index:conversation');
    let indexKeys = Object.keys(index);

    for (let key of indexKeys) {
        let keyParts = key.split(':');
        let type = keyParts[0];
        let network = keyParts[1];

        assert(type === 'group' || type === '1on1');
        assert(network === 'IRCNet' || network === 'MAS' ||  network === 'FreeNode' ||
            network === 'W3C' || network === 'Flowdock');

        if (type === '1on1') {
            let user1 = keyParts[2];
            let user2 = keyParts[3];

            assert(user1.charAt(0) === 'm' || user2.charAt(0) === 'm');
        }

        let conversation = yield redis.hgetall(`conversation:${index[key]}`);

        assert(conversation);
    }

    printVerdict('conversation:index', true);
}

function *conversationMembersTest() {
    let conversationKeys = yield redis.keys('conversation:*');
    let conversationMembersKeys = yield redis.keys('conversationmembers:*');

    assert(conversationKeys.length === conversationMembersKeys.length);

    for (let conversationKey of conversationKeys) {
        let conversationId = conversationKey.split(':')[1];
        let conversation = yield redis.hgetall(conversationKey);
        let members = yield redis.hgetall(`conversationmembers:${conversationId}`);

        assert(members);

        for (let userId of Object.keys(members)) {
            if (userId.charAt(0) === 'm') {
                assert((yield redis.exists(`user:${userId}`)));
            }
        }

        if (conversation.type === '1on1' && Object.keys(members).length !== 2) {
            console.log('Removing invalid 1on1, conversationId: ' + conversationId + '...');
            yield removeConversation(conversationId);
        }
    }

    printVerdict('conversationmembers', true);
}

function *conversationListTest() {
    let conversationListKeys = yield redis.keys('conversationlist:*');

    for (let conversationListKey of conversationListKeys) {
        let list = yield redis.smembers(conversationListKey);

        for (let conversationId of list) {
            let conversation = yield redis.hgetall(`conversation:${conversationId}`);

            assert(conversation);
        }
    }

    printVerdict('conversationlists', true);
}

function *oneOnOneHistoryTest() {
    let conversationHistoryKeys = yield redis.keys('1on1conversationhistory:*');

    for (let conversationHistoryKey of conversationHistoryKeys) {
        let list = yield redis.smembers(conversationHistoryKey);

        for (let conversationId of list) {
            let conversation = yield redis.hgetall(`conversation:${conversationId}`);

            if (!conversation) {
                console.log('Removing invalid 1on1conversationhistory entry...');
                yield redis.srem(conversationHistoryKey, conversationId);
            }
        }
    }

    printVerdict('1on1conversationhistory', true);
}

function *windowIndexTest() {
    let windowKeys = yield redis.keys('window:*');
    let windowIndexEntries = yield redis.hgetall('index:windowIds');

    let passed = windowKeys.length === Object.keys(windowIndexEntries).length;

    for (let entry of Object.keys(windowIndexEntries)) {
        let windowId = windowIndexEntries[entry];
        let userId = entry.split(':')[0];
        let conversationId = entry.split(':')[1];

        assert((yield redis.exists(`user:${userId}`)));

        let conversationExists = yield redis.exists(`conversation:${conversationId}`);

        if (!conversationExists) {
            console.log(`Removing orphan windowId: ${windowId}, userId: ${userId}`);

            yield redis.del(`window:${userId}:${windowId}`);
            yield redis.hdel('index:windowIds', `${userId}:${conversationId}`);
            yield redis.srem(`windowlist:${userId}`, windowId);
        }

        let windowExists = yield redis.exists(`window:${userId}:${windowId}`);

        if (!windowExists) {
            console.log(`Invalid windowlist entry, userId: ${userId}`);
        }

    }

    printVerdict('index:windowIds', passed);
}

function *windowTest() {
    let windowKeys = yield redis.keys('window:*');

    for (let windowKey of windowKeys) {
        let windowItem = yield redis.hgetall(windowKey);

        let userId = windowKey.split(':')[1];
        assert((yield redis.exists(`user:${userId}`)));

        assert((yield redis.exists(`conversation:${windowItem.conversationId}`)));
    }

    printVerdict('windows', true);
}

function *windowlistTest() {
    let windowListKeys = yield redis.keys('windowlist:*');

    for (let windowListKey of windowListKeys) {
        let userId = windowListKey.split(':')[1];
        assert((yield redis.exists(`user:${userId}`)));

        let windowIds = yield redis.smembers(windowListKey);

        for (let windowId of windowIds) {
            assert((yield redis.exists(`window:${userId}:${windowId}`)));
        }
    }

    printVerdict('windowlist', true);
}

function *friendsExistTest() {
    let friendsKeys = yield redis.keys('friends:*');

    for (let friendsKey of friendsKeys) {
        let friends = yield redis.smembers(friendsKey);

        for (let userId of friends) {
            let exists = yield redis.exists(`user:${userId}`);

            if (!exists) {
                console.log(`${friendsKeys} has non-existing friend ${userId}`);
            }
        }
    }

    printVerdict('friends', true);
}

function *desktopTest() {
    let windowKeys = yield redis.keys('window:*');

    for (let windowKey of windowKeys) {
        let masWindow = yield redis.hgetall(windowKey);

        if (masWindow.desktop !== null && isNaN(masWindow.desktop)) {
            console.log(`Fixing invalid window.desktop for ${windowKey}`);
            yield redis.hset(windowKey, 'desktop', 0);
        }
    }

    printVerdict('window.desktop', true);
}

function *activeDesktopTest() {
    let settingsKeys = yield redis.keys('settings:*');

    for (let settingsKey of settingsKeys) {
        let activeDesktop = yield redis.hget(settingsKey, 'activeDesktop');

        if (activeDesktop !== null) {
            let userId = settingsKey.split(':')[1];
            let windowKeys = yield redis.keys(`window:${userId}:*`);
            let found = false;

            for (let windowKey of windowKeys) {
                let desktop = yield redis.hget(windowKey, 'desktop');

                if (desktop === activeDesktop) {
                    found = true;
                    break;
                }
            }

            if (!found) {
                console.log('ERROR: found invalid activeDesktop setting');
            }
        }
    }

    printVerdict('settings.activeDesktop', true);
}

function *conversationIndexCaseTest() {
    let index = yield redis.hgetall('index:conversation');

    assert(index);

    let keyArray = Object.keys(index);

    for (let entry of keyArray) {
        if (!(/^group:/.test(entry))) {
            continue;
        }

        let parts = /^group:([a-zA-Z0-9]+):(.+)$/.exec(entry);

        assert(parts.length === 3);

        let channel = parts[2];
        let channelLowerCase = parts[2].toLowerCase();

        if (channel !== channelLowerCase) {
            let value = yield redis.hget('index:conversation', `group:${parts[1]}:${channel}`);
            assert(value);

            let existingValue =  yield redis.hget(
                'index:conversation', `group:${parts[1]}:${channelLowerCase}`);

            if (existingValue && value !== existingValue) {
                console.log('BAD CONVERSATION, can\'t fix:' + parts[1] + ':' + channel);
                process.exit(1);
            }

            yield redis.hdel('index:conversation', `group:${parts[1]}:${channel}`);
            yield redis.hset('index:conversation', `group:${parts[1]}:${channelLowerCase}`, value);

            console.log(
                `Fixing group:${parts[1]}:${channel} to group:${parts[1]}:${channelLowerCase}`);
        }
    }

    console.log(keyArray.length);
}

function printVerdict(desc, passed) {
    console.log('Checking ' + desc + ': ' + (passed ? '[PASS]' : '[FAIL]'));
}

function *removeConversation(conversationId) {
    let conversation = yield redis.hgetall(`conversation:${conversationId}`);
    let members = yield redis.hgetall(`conversationmembers:${conversationId}`);

    yield redis.del(`conversation:${conversationId}`);
    yield redis.del(`conversationmsgs:${conversationId}`);
    yield redis.del(`conversationmembers:${conversationId}`);

    for (let userId of Object.keys(members)) {
        yield redis.srem(`conversationlist:${userId}`, conversationId);
    }

    let key;

    if (conversation.type === 'group') {
        key = 'group:' + conversation.network + ':' + conversation.name;
    } else {
        let userIds = Object.keys(members);
        userIds = userIds.sort();
        key = '1on1:' + conversation.network + ':' + userIds[0] + ':' + userIds[1];
    }

    yield redis.hdel('index:conversation', key);
}
<|endoftext|>


<commit_before>var util = require("util");
var dgram = require('dgram');
var extend = require('gextend');
var events = require("events");

var DEFAULTS = {
    autoinitialize: true,
    port: 8899,
    host: '192.168.1.100',
    activeZone: 1
};

var ZONES = [
    {on: 0x35, off: 0x39, label: 'All'},
    {on: 0x38, off: 0x3B, label: 'Zone1'},
    {on: 0x3D, off: 0x33, label: 'Zone2'},
    {on: 0x37, off: 0x3A, label: 'Zone3'},
    {on: 0x32, off: 0x36, label: 'Zone4'},
];

var VALUE              = 0x00;
var EXTENSION          = 0x55;
var DIM_CMD            = 0x34;
var BRIGHTEN_CMD       = 0x3C;
var WARM_COLOR_UP_CMD  = 0x3E;
var COOL_COLOR_UP_CMD  = 0x3F;
var ALL_LIGHTS_ON_CMD  = ZONES[0].on;
var ALL_LIGHTS_OFF_CMD = ZONES[0].off;

/**
 * Controller constructor
 * @param {Object} config Options object
 */
var Controller = function(config){
    config = extend({}, this.constructor.DEFAULTS, config);

    events.EventEmitter.call(this);

    if(config.autoinitialize ) this.init(config);
};

util.inherits(Controller, events.EventEmitter);

Controller.DEFAULTS = DEFAULTS;

/**
 * Initialize the `Controller` instance
 * @param  {Object} config Options object
 * @return {this}
 */
Controller.prototype.init = function(config){
    if(this.initialized) return;
    this.initialized = true;

    extend(this, this.constructor.DEFAULTS, config);

    return this;
};

/**
 * Sends a message over `UDP` to the bridge
 * @param  {Number}   command  Hex code
 * @param  {Function} callback
 * @return {void}
 */
Controller.prototype.sendMessage = function(command, callback){
    var msg = new Buffer([command, VALUE, EXTENSION]);
    var socket = dgram.createSocket('udp4');
    socket.send(msg, 0, msg.length, this.port, this.host, function(err, dat){
        socket.close();
        if(callback) callback(err, dat);
    });
};

/**
 * Sends brighten command
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.brighten = function(callback){
    this.sendMessage(BRIGHTEN_CMD, callback);
    return this;
};

/**
 * Sends dim command
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.dim = function(callback){
    this.sendMessage(DIM_CMD, callback);
    return this;
};

/**
 * Sends warmer color command
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.warmerColor = function(callback){
    this.sendMessage(WARM_COLOR_UP_CMD, callback);
    return this;
};

/**
 * Sends cooler color command
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.coolerColor = function(callback){
    this.sendMessage(COOL_COLOR_UP_CMD, callback);
    return this;
};

/**
 * Sets `activeZone` to all zones.
 * @return {this}
 */
Controller.prototype.all = function(){
    this.previousActive = this.activeZone;
    this.activeZone = 0;
    return this;
};

/**
 * Set the `activeZone` which should
 * be between 1 and 4
 * @param  {int} id Zone id
 * @return {this}
 */
Controller.prototype.zone = function(id){
    if(id < 0 || id > 4) id = 1;
    this.activeZone = id;
    return this;
};

/**
 * Turn led bulbs of a zone **on**, using `activeZone`
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.ledsOn = function(callback){
    this.sendMessage(ZONES[this.activeZone].on, this._onLedCommand.bind(this, callback));
    return this;
};

/**
 * Turn led bulbs of a zone **off**, using `activeZone`
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.ledsOff = function(callback){
    this.sendMessage(ZONES[this.activeZone].off, callback, this._onLedCommand.bind(this, callback));
};

/**
 * Led command complete handler
 * @param  {Function} callback
 * @param  {Object}   err
 * @param  {Object}   dat
 * @return {void}
 * @private
 */
Controller.prototype._onLedCommand = function(callback, err, dat){
    console.log('ON LED COMMAND', arguments)
    if(!this.previousActive) return;
    this.activeZone = this.previousActive;
    this.previousActive = null;
    callback(err, dat);
};

Controller.prototype.logger = console;

module.exports = Controller;
<commit_msg>LIB: refactoring. Added fullColor and nightMode<commit_after>var util = require("util");
var dgram = require('dgram');
var extend = require('gextend');
var events = require("events");

var DEFAULTS = {
    autoinitialize: true,
    port: 8899,
    host: '192.168.1.100',
    activeZone: 1
};

var ZONES = [
    {on: 0x35, off: 0x39, nightMode:0xB9, fullMode:0xB5, label: 'All'},

    {on: 0x38, off: 0x3B, nightMode: 0xBB, fullMode: 0xB8, label: 'Zone1'},
    {on: 0x3D, off: 0x33, nightMode: 0xB3, fullMode: 0xBD, label: 'Zone2'},
    {on: 0x37, off: 0x3A, nightMode: 0xBA, fullMode: 0xB7, label: 'Zone3'},
    {on: 0x32, off: 0x36, nightMode: 0xB6, fullMode: 0xB2, label: 'Zone4'},
];

var VALUE              = 0x00;
var EXTENSION          = 0x55;

var DIM_CMD            = 0x34;
var BRIGHTEN_CMD       = 0x3C;
var SET_BRIGHTNESS_CMD = 0x4E;
var WARM_COLOR_UP_CMD  = 0x3E;
var COOL_COLOR_UP_CMD  = 0x3F;
var ALL_LIGHTS_ON_CMD  = ZONES[0].on;
var ALL_LIGHTS_OFF_CMD = ZONES[0].off;

var ALL_LIGHTS_FULL_CMD = ZONES[0].fullMode;//0xB5;
var ALL_LIGHTS_NIGHTMODE_CMD = ZONES[0].nightMode;//0xB9;

/**
 * Controller constructor
 * @param {Object} config Options object
 */
var Controller = function(config){
    config = extend({}, this.constructor.DEFAULTS, config);

    events.EventEmitter.call(this);

    if(config.autoinitialize ) this.init(config);
};

util.inherits(Controller, events.EventEmitter);

Controller.DEFAULTS = DEFAULTS;

/**
 * Initialize the `Controller` instance
 * @param  {Object} config Options object
 * @return {this}
 */
Controller.prototype.init = function(config){
    if(this.initialized) return;
    this.initialized = true;

    extend(this, this.constructor.DEFAULTS, config);

    return this;
};

/**
 * Sends a message over `UDP` to the bridge
 * @param  {Number}   command  Hex code
 * @param  {Function} callback
 * @return {void}
 */
Controller.prototype.sendCommand = function(command, callback, value){
    value || (value = VALUE);
    var msg = new Buffer([command, value, EXTENSION]);
    // console.log('MSG', msg)
    var socket = dgram.createSocket('udp4');
    socket.send(msg, 0, msg.length, this.port, this.host, function(err, dat){
        socket.close();
        if(callback) callback(err, dat);
    });
};

/**
 * Sends brighten command
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.brighten = function(callback){
    this.sendCommand(BRIGHTEN_CMD, callback);
    return this;
};

Controller.prototype.setBrightness = function(percent, callback){
    //value is from 0-100, should be converted to 2-27.
    var value = Math.floor(2 + ((percent / 100) * 25));
    //value should be hex
    value = struct.pack('B', value);
    console.log('VALUE', value)
    //command is 4E
    this.sendCommand(SET_BRIGHTNESS_CMD, callback, value);
    return this;
};

/**
 * Sends dim command
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.dim = function(callback){
    this.sendCommand(DIM_CMD, callback);
    return this;
};

/**
 * Sends warmer color command
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.warmerColor = function(callback){
    this.sendCommand(WARM_COLOR_UP_CMD, callback);
    return this;
};

/**
 * Sends cooler color command
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.coolerColor = function(callback){
    this.sendCommand(COOL_COLOR_UP_CMD, callback);
    return this;
};

/**
 * Sets `activeZone` to all zones.
 * @return {this}
 */
Controller.prototype.all = function(){
    this.previousActive = this.activeZone;
    this.activeZone = 0;
    return this;
};

/**
 * Set the `activeZone` which should
 * be between 1 and 4
 * @param  {int} id Zone id
 * @return {this}
 */
Controller.prototype.zone = function(id){
    if(id < 0 || id > 4) id = 1;
    this.activeZone = id;
    return this;
};

/**
 * Turn led bulbs of a zone **on**, using `activeZone`
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.ledsOn = function(callback){
    this.sendCommand(ZONES[this.activeZone].on, this._onLedCommand.bind(this, callback));
    return this;
};

/**
 * Turn led bulbs of a zone **off**, using `activeZone`
 * @param  {Function} callback
 * @return {this}
 */
Controller.prototype.ledsOff = function(callback){
    this.sendCommand(ZONES[this.activeZone].off, callback, this._onLedCommand.bind(this, callback));
    return this;
};

Controller.prototype.fullColor = function(callback){
    this.sendCommand(ZONES[this.activeZone].fullMode, callback);
    return this;
};

Controller.prototype.nightMode = function(callback){
    this.sendCommand(ZONES[this.activeZone].nightMode, callback);
    return this;
};

/**
 * Led command complete handler
 * @param  {Function} callback
 * @param  {Object}   err
 * @param  {Object}   dat
 * @return {void}
 * @private
 */
Controller.prototype._onLedCommand = function(callback, err, dat){
    console.log('ON LED COMMAND', arguments)
    if(!this.previousActive) return;
    this.activeZone = this.previousActive;
    this.previousActive = null;
    callback(err, dat);
};

Controller.prototype.logger = console;

module.exports = Controller;
<|endoftext|>


<commit_before>import _ from "lodash";
import inaturalistjs from "inaturalistjs";
import moment from "moment-timezone";
import uuidv4 from "uuid/v4";
import actions from "../actions/actions";
import util from "./util";

const ObsCard = class ObsCard {
  constructor( attrs ) {
    const defaultAttrs = {
      id: new Date( ).getTime( ),
      saveState: "pending",
      geoprivacy: "open",
      files: { },
      date: null,
      taxon_id: null,
      bounds: null,
      zoom: null,
      latitude: null,
      longitude: null,
      accuracy: null,
      species_guess: null,
      tags: [],
      observation_field_values: [],
      projects: [],
      changedFields: { },
      /* global TIMEZONE */
      time_zone: TIMEZONE,
      uuid: uuidv4()
    };
    Object.assign( this, defaultAttrs, attrs );
  }

  blank( ) {
    return (
      _.isEmpty( this.files ) &&
      _.isEmpty( this.tags ) &&
      _.isEmpty( this.observation_field_values ) &&
      _.isEmpty( this.projects ) &&
      !this.description &&
      !this.date &&
      !this.taxon_id &&
      !this.latitude &&
      !this.species_guess
    );
  }

  nonUploadedFiles( ) {
    return _.filter( this.files, f =>
      f.uploadState === "uploading" || f.uploadState === "pending" );
  }

  uploadedFiles( ) {
    return _.filter( this.files, f => f.uploadState === "uploaded" );
  }

  uploadedFileIDs( ) {
    return _.map( this.uploadedFiles( ), f => f.id );
  }

  momentDate( ) {
    let m;
    if ( this.date &&
         this.date.match( /\d{2}\/\d{2}\/\d{2}(\d{2})? \d{1,2}:\d{2} [AP]M [-+]\d{1,2}:\d{2}/ ) ) {
      const d = new Date( this.date );
      m = d && moment( d );
      if ( m && m.isValid( ) ) {
        return m;
      }
    }
    return undefined;
  }

  visionParams( ) {
    const firstThumbnail = _.first( _.compact(
      _.map( _.sortBy( this.files, "sort" ), f => f.visionThumbnail ) ) );
    if ( !firstThumbnail ) { return null; }
    const params = { image: firstThumbnail };
    if ( this.latitude ) { params.lat = this.latitude; }
    if ( this.longitude ) { params.lng = this.longitude; }
    if ( this.date ) { params.observed_on = this.date; }
    return params;
  }

  // usually called when a card acquires a new photo, this will return
  // all fields with metadata attached to the photo, where the corresponding
  // field on the card is currently blank
  newMetadataFromFile( file ) {
    const newMetadata = { };
    const fileMetadata = Object.assign( { }, file.metadata, file.serverMetadata );
    _.each( fileMetadata, ( v, k ) => {
      if ( _.isEmpty( this[k] ) &&
          !_.isBoolean( this[k] ) &&
          !_.isNumber( this[k] ) &&
          !_.has( this.changedFields, k ) ) {
        newMetadata[k] = v;
      }
    } );
    return newMetadata;
  }

  save( dispatch ) {
    if ( this.blank( ) ) {
      dispatch( actions.updateObsCard( this, { saveState: "saved" } ) );
      return;
    }
    if ( this.saveState !== "pending" ) { return; }
    dispatch( actions.updateObsCard( this, { saveState: "saving" } ) );
    const params = {
      observation: {
        description: this.description,
        latitude: this.latitude,
        longitude: this.longitude,
        positional_accuracy: this.accuracy,
        geoprivacy: this.geoprivacy,
        place_guess: this.locality_notes,
        observation_field_values_attributes: _.pick(
          this.observation_field_values, ["value", "observation_field_id"]
        ),
        tag_list: this.tags.join( "," ),
        captive_flag: this.captive,
        uuid: this.uuid
      },
      project_id: _.map( this.projects, "id" ),
      uploader: true
    };
    if ( this.taxon_id ) { params.observation.taxon_id = this.taxon_id; }
    if ( this.species_guess ) { params.observation.species_guess = this.species_guess; }
    if ( this.date && !util.dateInvalid( this.date ) ) {
      params.observation.observed_on_string = this.date;
    }
    if ( this.selected_taxon && this.selected_taxon.isVisionResult ) {
      params.observation.owners_identification_from_vision_requested = true;
    }
    const photoIDs = _.compact( _.map( _.sortBy( this.files, "sort" ),
      f => ( f.photo ? f.photo.id : null ) ) );
    if ( photoIDs.length > 0 ) { params.local_photos = { 0: photoIDs }; }
    const soundIDs = _.compact( _.map( _.sortBy( this.files, "sort" ),
      f => ( f.sound ? f.sound.id : null ) ) );
    if ( soundIDs.length > 0 ) { params.local_sounds = { 0: soundIDs }; }
    inaturalistjs.observations.create( params, { same_origin: true } ).then( r => {
      dispatch( actions.updateObsCard( this, {
        saveState: "saved",
        serverResponse: r && r[0]
      } ) );
    } ).catch( e => {
      let errors;
      const err = util.errorJSON( e.message );
      if ( err && err.errors && err.errors[0] ) {
        errors = err.errors[0];
      }
      if ( errors ) {
        dispatch( actions.updateObsCard( this, { saveState: "failed", saveErrors: errors } ) );
      } else {
        e.response.json( ).then( errorJSON => {
          errors = [I18n.t( "unknown_error" )];
          if ( errorJSON && errorJSON.errors && errorJSON.errors[0] ) {
            errors = errorJSON.errors[0];
          }
          dispatch( actions.updateObsCard( this, { saveState: "failed", saveErrors: errors } ) );
        } );
      }
    } );
  }
};

export default ObsCard;
<commit_msg>Bugfix: I had broken adding obs fields in the Uploader<commit_after>import _ from "lodash";
import inaturalistjs from "inaturalistjs";
import moment from "moment-timezone";
import uuidv4 from "uuid/v4";
import actions from "../actions/actions";
import util from "./util";

const ObsCard = class ObsCard {
  constructor( attrs ) {
    const defaultAttrs = {
      id: new Date( ).getTime( ),
      saveState: "pending",
      geoprivacy: "open",
      files: { },
      date: null,
      taxon_id: null,
      bounds: null,
      zoom: null,
      latitude: null,
      longitude: null,
      accuracy: null,
      species_guess: null,
      tags: [],
      observation_field_values: [],
      projects: [],
      changedFields: { },
      /* global TIMEZONE */
      time_zone: TIMEZONE,
      uuid: uuidv4()
    };
    Object.assign( this, defaultAttrs, attrs );
  }

  blank( ) {
    return (
      _.isEmpty( this.files ) &&
      _.isEmpty( this.tags ) &&
      _.isEmpty( this.observation_field_values ) &&
      _.isEmpty( this.projects ) &&
      !this.description &&
      !this.date &&
      !this.taxon_id &&
      !this.latitude &&
      !this.species_guess
    );
  }

  nonUploadedFiles( ) {
    return _.filter( this.files, f =>
      f.uploadState === "uploading" || f.uploadState === "pending" );
  }

  uploadedFiles( ) {
    return _.filter( this.files, f => f.uploadState === "uploaded" );
  }

  uploadedFileIDs( ) {
    return _.map( this.uploadedFiles( ), f => f.id );
  }

  momentDate( ) {
    let m;
    if ( this.date &&
         this.date.match( /\d{2}\/\d{2}\/\d{2}(\d{2})? \d{1,2}:\d{2} [AP]M [-+]\d{1,2}:\d{2}/ ) ) {
      const d = new Date( this.date );
      m = d && moment( d );
      if ( m && m.isValid( ) ) {
        return m;
      }
    }
    return undefined;
  }

  visionParams( ) {
    const firstThumbnail = _.first( _.compact(
      _.map( _.sortBy( this.files, "sort" ), f => f.visionThumbnail ) ) );
    if ( !firstThumbnail ) { return null; }
    const params = { image: firstThumbnail };
    if ( this.latitude ) { params.lat = this.latitude; }
    if ( this.longitude ) { params.lng = this.longitude; }
    if ( this.date ) { params.observed_on = this.date; }
    return params;
  }

  // usually called when a card acquires a new photo, this will return
  // all fields with metadata attached to the photo, where the corresponding
  // field on the card is currently blank
  newMetadataFromFile( file ) {
    const newMetadata = { };
    const fileMetadata = Object.assign( { }, file.metadata, file.serverMetadata );
    _.each( fileMetadata, ( v, k ) => {
      if ( _.isEmpty( this[k] ) &&
          !_.isBoolean( this[k] ) &&
          !_.isNumber( this[k] ) &&
          !_.has( this.changedFields, k ) ) {
        newMetadata[k] = v;
      }
    } );
    return newMetadata;
  }

  save( dispatch ) {
    if ( this.blank( ) ) {
      dispatch( actions.updateObsCard( this, { saveState: "saved" } ) );
      return;
    }
    if ( this.saveState !== "pending" ) { return; }
    dispatch( actions.updateObsCard( this, { saveState: "saving" } ) );
    const params = {
      observation: {
        description: this.description,
        latitude: this.latitude,
        longitude: this.longitude,
        positional_accuracy: this.accuracy,
        geoprivacy: this.geoprivacy,
        place_guess: this.locality_notes,
        observation_field_values_attributes: ( this.observation_field_values || [] ).map(
          ofv => _.pick(
            ofv, ["value", "observation_field_id"]
          )
        ),
        tag_list: this.tags.join( "," ),
        captive_flag: this.captive,
        uuid: this.uuid
      },
      project_id: _.map( this.projects, "id" ),
      uploader: true
    };
    if ( this.taxon_id ) { params.observation.taxon_id = this.taxon_id; }
    if ( this.species_guess ) { params.observation.species_guess = this.species_guess; }
    if ( this.date && !util.dateInvalid( this.date ) ) {
      params.observation.observed_on_string = this.date;
    }
    if ( this.selected_taxon && this.selected_taxon.isVisionResult ) {
      params.observation.owners_identification_from_vision_requested = true;
    }
    const photoIDs = _.compact( _.map( _.sortBy( this.files, "sort" ),
      f => ( f.photo ? f.photo.id : null ) ) );
    if ( photoIDs.length > 0 ) { params.local_photos = { 0: photoIDs }; }
    const soundIDs = _.compact( _.map( _.sortBy( this.files, "sort" ),
      f => ( f.sound ? f.sound.id : null ) ) );
    if ( soundIDs.length > 0 ) { params.local_sounds = { 0: soundIDs }; }
    inaturalistjs.observations.create( params, { same_origin: true } ).then( r => {
      dispatch( actions.updateObsCard( this, {
        saveState: "saved",
        serverResponse: r && r[0]
      } ) );
    } ).catch( e => {
      let errors;
      const err = util.errorJSON( e.message );
      if ( err && err.errors && err.errors[0] ) {
        errors = err.errors[0];
      }
      if ( errors ) {
        dispatch( actions.updateObsCard( this, { saveState: "failed", saveErrors: errors } ) );
      } else {
        e.response.json( ).then( errorJSON => {
          errors = [I18n.t( "unknown_error" )];
          if ( errorJSON && errorJSON.errors && errorJSON.errors[0] ) {
            errors = errorJSON.errors[0];
          }
          dispatch( actions.updateObsCard( this, { saveState: "failed", saveErrors: errors } ) );
        } );
      }
    } );
  }
};

export default ObsCard;
<|endoftext|>


<commit_before>/**
 * PaySwarm signature tool.
 *
 * Copyright (c) 2011-2013, Digital Bazaar, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of Digital Bazaar, Inc. nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

var async = require('async');
var common = require('./common');
var payswarm = require('..');

function init(options) {
  var cmd = options.program
    .command('signature')
    .description('sign and verify JSON-LD');
  common
    .command
    .init(cmd)
    .option('    --nonce <nonce>', 'nonce to use when signing')
    .option('    --created <created>', 'W3C date to use when signing')
    .option('    --sign <data>',
      'Sign JSON-LD string, @file for file, @- for stdin')
    .option('    --verify <data>',
      'Verify JSON-LD string, @file for file, @- for stdin')
    .action(signature)
    .on('--help', function() {
      console.log();
      console.log('  Signatures are performed using the current config.');
      console.log('  Verification will fetch the public key.');
      console.log();
    });
}

function signature(cmd) {
  async.auto({
    cfg: function(callback) {
      common.config.read(cmd, callback);
    },
    signData: ['cfg', function(callback, results) {
      if(cmd.sign) {
        if(cmd.sign.length > 0 && cmd.sign[0] === '@') {
          // FIXME: handle non-JSON-LD data
          payswarm.getJsonLd(cmd.sign.slice(1), function(err, data) {
            if(err) {
              return callback(err);
            }
            callback(null, data);
          });
          return;
        }
        else {
          return callback(null, JSON.parse(cmd.sign));
        }
      }
      callback();
    }],
    sign: ['signData', function(callback, results) {
      if(results.signData) {
        var opts = {
          publicKeyId: results.cfg.publicKey.id,
          privateKeyPem: results.cfg.publicKey.privateKeyPem
        };
        if(cmd.nonce) {
          opts.nonce = cmd.nonce;
        }
        if(cmd.created) {
          opts.dateTime = cmd.created;
        }

        return payswarm.sign(results.signData, opts, function(err, signed) {
          if(err) {
            return callback(err);
          }
          common.output(cmd, signed, callback);
        });
      }
      callback();
    }],
    verifyData: ['cfg', 'sign', function(callback, results) {
      if(cmd.verify) {
        if(cmd.verify.length > 0 && cmd.verify[0] === '@') {
          // FIXME: handle non-JSON-LD data
          payswarm.getJsonLd(cmd.verify.slice(1), function(err, data) {
            if(err) {
              return callback(err);
            }
            callback(null, data);
          });
          return;
        }
        else {
          return callback(null, JSON.parse(cmd.verify));
        }
      }
      callback();
    }],
    verify: ['verifyData', function(callback, results) {
      if(results.verifyData) {
        var options = {
          checkTimestamp: false,
          request: common.requestOptions(cmd)
        };
        return payswarm.verify(results.verifyData, options, function(err) {
          if(err) {
            return callback(err);
          }
          if(cmd.verbose) {
            console.log('OK');
          }
          callback();
        });
      }
      callback();
    }],
  }, function(err) {
    common.error(err);
  });
}

module.exports = {
  init: init
};

if(require.main === module) {
  common.error('Run this tool with the payswarm application.');
}
<commit_msg>[tools] Add URL suport for sign and verify.<commit_after>/**
 * PaySwarm signature tool.
 *
 * Copyright (c) 2011-2013, Digital Bazaar, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of Digital Bazaar, Inc. nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

var async = require('async');
var common = require('./common');
var payswarm = require('..');

function init(options) {
  var cmd = options.program
    .command('signature')
    .description('sign and verify JSON-LD');
  common
    .command
    .init(cmd)
    .option('    --nonce <nonce>', 'nonce to use when signing')
    .option('    --created <created>', 'W3C date to use when signing')
    .option('    --sign <data>',
      'Sign URL, JSON-LD string, @file for file, @- for stdin')
    .option('    --verify <data>',
      'Verify URL, JSON-LD string, @file for file, @- for stdin')
    .action(signature)
    .on('--help', function() {
      console.log();
      console.log('  Signatures are performed using the current config.');
      console.log('  Verification will fetch the public key.');
      console.log();
    });
}

function signature(cmd) {
  async.auto({
    cfg: function(callback) {
      common.config.read(cmd, callback);
    },
    signData: ['cfg', function(callback, results) {
      if(cmd.sign) {
        if(cmd.sign.length === 0) {
          return callback(new Error({
            message: 'No data given.',
            type: 'payswarm.SignatureTool.InvalidData'
          }));
        }
        if(cmd.sign.indexOf('@') === 0) {
          // get data from a file
          // FIXME: handle non-JSON-LD data
          payswarm.getJsonLd(cmd.sign.slice(1), function(err, data) {
            if(err) {
              return callback(err);
            }
            callback(null, data);
          });
          return;
        }
        else if(cmd.sign.indexOf('http://') === 0 ||
          cmd.sign.indexOf('https://') === 0) {
          // get data from URL
          // FIXME: handle non-JSON-LD data
          var options = {
            request: common.requestOptions(cmd)
          };
          payswarm.getJsonLd(cmd.sign, options, function(err, data) {
            if(err) {
              return callback(err);
            }
            callback(null, data);
          });
          return;
        }
        else {
          // parse raw data
          return callback(null, JSON.parse(cmd.sign));
        }
      }
      callback();
    }],
    sign: ['signData', function(callback, results) {
      if(results.signData) {
        var opts = {
          publicKeyId: results.cfg.publicKey.id,
          privateKeyPem: results.cfg.publicKey.privateKeyPem
        };
        if(cmd.nonce) {
          opts.nonce = cmd.nonce;
        }
        if(cmd.created) {
          opts.dateTime = cmd.created;
        }

        return payswarm.sign(results.signData, opts, function(err, signed) {
          if(err) {
            return callback(err);
          }
          common.output(cmd, signed, callback);
        });
      }
      callback();
    }],
    verifyData: ['cfg', 'sign', function(callback, results) {
      if(cmd.verify) {
        if(cmd.verify.length === 0) {
          return callback(new Error({
            message: 'No data given.',
            type: 'payswarm.SignatureTool.InvalidData'
          }));
        }
        if(cmd.verify.indexOf('@') === 0) {
          // get data from a file
          // FIXME: handle non-JSON-LD data
          payswarm.getJsonLd(cmd.verify.slice(1), function(err, data) {
            if(err) {
              return callback(err);
            }
            callback(null, data);
          });
          return;
        }
        else if(cmd.verify.indexOf('http://') === 0 ||
          cmd.verify.indexOf('https://') === 0) {
          // get data from URL
          // FIXME: handle non-JSON-LD data
          var options = {
            request: common.requestOptions(cmd)
          };
          payswarm.getJsonLd(cmd.verify, options, function(err, data) {
            if(err) {
              return callback(err);
            }
            callback(null, data);
          });
          return;
        }
        else {
          // parse raw data
          return callback(null, JSON.parse(cmd.verify));
        }
      }
      callback();
    }],
    verify: ['verifyData', function(callback, results) {
      if(results.verifyData) {
        var options = {
          checkTimestamp: false,
          request: common.requestOptions(cmd)
        };
        return payswarm.verify(results.verifyData, options, function(err) {
          if(err) {
            return callback(err);
          }
          if(cmd.verbose) {
            console.log('OK');
          }
          callback();
        });
      }
      callback();
    }],
  }, function(err) {
    common.error(err);
  });
}

module.exports = {
  init: init
};

if(require.main === module) {
  common.error('Run this tool with the payswarm application.');
}
<|endoftext|>


<commit_before>var _ = require('lodash');

/**
 * Generate 'by.css' locator suggestions given the element's tag name and an
 * attribute.
 * @param {string} tagName The tag name.
 * @param {string} attrName The element's attribute name (for example:
 *     class, value, id).
 * @param {string} attrValue The element's attribute value.
 * @return {(null|string|Array.<string>)} Null when a css selector cannot be
 *     created.
 */
var buildByCssSuggestions = function(tagName, attrName, attrValue) {
  if (attrName === 'id') {
    return 'by.css(\'#' + attrValue + '\')';
  }

  if (attrName === 'class') {
    // For the 'class' attribute avoid div[class="foo bar"] and return
    // 'div.foo.bar'. Ignore ng classes.
    var classes = _.reject(attrValue.split(' '), function(className) {
      return /^ng-.*/.test(className);
    }).join('.');

    if (!classes) {
      return null;
    }

    // Return both 'div.className' and '.className'.
    return [
          'by.css(\'' + tagName + '.' + classes + '\')',
          'by.css(\'.' + classes + '\')'
    ];
  }

  // Escape colon-formatted (ng:model) attributes.
  attrName = attrName.replace(/ng\:/, 'ng\\\\:');

  return 'by.css(\'' + tagName +
      '[' + attrName + '="' + attrValue + '"]\')';
};

/**
 * Mapping between the locators coming from the chrome extension and the
 * functions that create locators for each type.
 * @type {Object.<string, Function>}
 */
var strategies = {
  byBinding: function(value) {
    var locators = [],
        addBinding = function(binding) {
          locators.push('by.binding(\'' + binding + '\')');
        };

    // Binding can take many forms. Try the following:
    // '({{nickname|uppercase}})', 'nickname|uppercase', and 'nickname'.
    addBinding(value);
    var insideCurlies = /{{(.+)}}/.exec(value);
    if (insideCurlies) {
      addBinding(insideCurlies[1]);

      // Is there a filter?
      var beforeFilter = /(.+)\|.+/.exec(insideCurlies[1]);
      if (beforeFilter) {
        addBinding(beforeFilter[1]);
      }
    }

    return locators;
  },
  byButtonText: function(buttonText) {
    return 'by.buttonText(\'' + buttonText + '\')';
  },
  /**
   * Generate css locator suggestions.
   * @param {Object.<string, string>} attributeMap The element attributes and
   *     values.
   * @return {Array.<string>} A list of 'by.css' suggestions.
   */
  byCss: function(attributeMap) {
    var tagName = attributeMap.nodeName;

    // Try every attribute. Ignore null and undefined.
    return _.chain(attributeMap).
        map(function(attrValue, attrName) {
          // Don't create suggestion for tag name.
          if (attrName !== 'nodeName') {
            return buildByCssSuggestions(tagName, attrName, attrValue);
          }
        }).
        compact().
        flatten().
        value();
  },
  byId: function(value) {
    return 'by.id(\'' + value + '\')';
  },
  byLinkText: function(value) {
    value = (value || '').trim();
    return  'by.linkText(\'' + value + '\')';
  },
  byModel: function(value) {
    return 'by.model(\'' + value + '\')';
  }
};

/**
 * Create a locator suggestions.
 * @param {string} locatorType A locator strategy: byCss, byBinding, etc.
 * @param {!Object} extensionLocators The locators coming from the
 * @return {string|Array.<string>}
 */
var buildSuggestions = function(locatorType, extensionLocators) {
  if (!strategies[locatorType]) {
    return null;
  }
  return strategies[locatorType](extensionLocators[locatorType]);
};

/**
 * Generate a list of locators based on the locators suggested by the chrome
 * extension. The extension locators have the following format:
 *
 * {
   *   byCss: {
   *     nodeName: string,
   *     ... And a key / value pair for each attribute.
   *   },
   *   byId: string,
   *   byButtonText: string,
   *   byLinkText: string,
   *   byBinding: string,
   *   byModel: string
   * }
 *
 * @param {!Object} extensionLocators The locators coming from the
 *     chrome extension.
 * @return {Array.<{name:string, locator: string, countExpression: string}>}
 */
var buildLocatorList = function(extensionLocators) {
  return _.chain(extensionLocators).
      map(function(value, locatorType) {
        var suggestions = buildSuggestions(locatorType, extensionLocators);
        if (!suggestions) {
          return;
        }

        suggestions = _.isArray(suggestions) ? suggestions : [suggestions];
        return _.map(suggestions, function(suggestion) {
          // Count the number of matches.
          var countExpression = 'element.all(' + suggestion + ').count()';

          return {
            name: locatorType,
            locator: suggestion,
            countExpression: countExpression
          };
        });
      }).
      flatten().
      compact().
      value();
};

module.exports = {
  buildLocatorList: buildLocatorList
};
<commit_msg>fix jsdoc formatting<commit_after>var _ = require('lodash');

/**
 * Generate 'by.css' locator suggestions given the element's tag name and an
 * attribute.
 * @param {string} tagName The tag name.
 * @param {string} attrName The element's attribute name (for example:
 *     class, value, id).
 * @param {string} attrValue The element's attribute value.
 * @return {(null|string|Array.<string>)} Null when a css selector cannot be
 *     created.
 */
var buildByCssSuggestions = function(tagName, attrName, attrValue) {
  if (attrName === 'id') {
    return 'by.css(\'#' + attrValue + '\')';
  }

  if (attrName === 'class') {
    // For the 'class' attribute avoid div[class="foo bar"] and return
    // 'div.foo.bar'. Ignore ng classes.
    var classes = _.reject(attrValue.split(' '), function(className) {
      return /^ng-.*/.test(className);
    }).join('.');

    if (!classes) {
      return null;
    }

    // Return both 'div.className' and '.className'.
    return [
          'by.css(\'' + tagName + '.' + classes + '\')',
          'by.css(\'.' + classes + '\')'
    ];
  }

  // Escape colon-formatted (ng:model) attributes.
  attrName = attrName.replace(/ng\:/, 'ng\\\\:');

  return 'by.css(\'' + tagName +
      '[' + attrName + '="' + attrValue + '"]\')';
};

/**
 * Mapping between the locators coming from the chrome extension and the
 * functions that create locators for each type.
 * @type {Object.<string, Function>}
 */
var strategies = {
  byBinding: function(value) {
    var locators = [],
        addBinding = function(binding) {
          locators.push('by.binding(\'' + binding + '\')');
        };

    // Binding can take many forms. Try the following:
    // '({{nickname|uppercase}})', 'nickname|uppercase', and 'nickname'.
    addBinding(value);
    var insideCurlies = /{{(.+)}}/.exec(value);
    if (insideCurlies) {
      addBinding(insideCurlies[1]);

      // Is there a filter?
      var beforeFilter = /(.+)\|.+/.exec(insideCurlies[1]);
      if (beforeFilter) {
        addBinding(beforeFilter[1]);
      }
    }

    return locators;
  },
  byButtonText: function(buttonText) {
    return 'by.buttonText(\'' + buttonText + '\')';
  },
  /**
   * Generate css locator suggestions.
   * @param {Object.<string, string>} attributeMap The element attributes and
   *     values.
   * @return {Array.<string>} A list of 'by.css' suggestions.
   */
  byCss: function(attributeMap) {
    var tagName = attributeMap.nodeName;

    // Try every attribute. Ignore null and undefined.
    return _.chain(attributeMap).
        map(function(attrValue, attrName) {
          // Don't create suggestion for tag name.
          if (attrName !== 'nodeName') {
            return buildByCssSuggestions(tagName, attrName, attrValue);
          }
        }).
        compact().
        flatten().
        value();
  },
  byId: function(value) {
    return 'by.id(\'' + value + '\')';
  },
  byLinkText: function(value) {
    value = (value || '').trim();
    return  'by.linkText(\'' + value + '\')';
  },
  byModel: function(value) {
    return 'by.model(\'' + value + '\')';
  }
};

/**
 * Create a locator suggestions.
 * @param {string} locatorType A locator strategy: byCss, byBinding, etc.
 * @param {!Object} extensionLocators The locators coming from the
 * @return {string|Array.<string>}
 */
var buildSuggestions = function(locatorType, extensionLocators) {
  if (!strategies[locatorType]) {
    return null;
  }
  return strategies[locatorType](extensionLocators[locatorType]);
};

/**
 * Generate a list of locators based on the locators suggested by the chrome
 * extension. The extension locators have the following format:
 *
 * {
 *   byCss: {
 *     nodeName: string,
 *     ... And a key / value pair for each attribute.
 *   },
 *   byId: string,
 *   byButtonText: string,
 *   byLinkText: string,
 *   byBinding: string,
 *   byModel: string
 * }
 *
 * @param {!Object} extensionLocators The locators coming from the
 *     chrome extension.
 * @return {Array.<{name:string, locator: string, countExpression: string}>}
 */
var buildLocatorList = function(extensionLocators) {
  return _.chain(extensionLocators).
      map(function(value, locatorType) {
        var suggestions = buildSuggestions(locatorType, extensionLocators);
        if (!suggestions) {
          return;
        }

        suggestions = _.isArray(suggestions) ? suggestions : [suggestions];
        return _.map(suggestions, function(suggestion) {
          // Count the number of matches.
          var countExpression = 'element.all(' + suggestion + ').count()';

          return {
            name: locatorType,
            locator: suggestion,
            countExpression: countExpression
          };
        });
      }).
      flatten().
      compact().
      value();
};

module.exports = {
  buildLocatorList: buildLocatorList
};
<|endoftext|>


<commit_before>/* Amber package loading.
  Load this script as well as require.js (works in any order;
    either defines 'require', thus passing config, if loaded prior require.js;
    or calls require.config, if loaded post require.js).
  Usage example:
    require(['amber/devel'], function(smalltalk) {
        smallralk.initialize({"transport.defaultAmdNamespace": "com_example_myproject"});

        smalltalk.globals.Browser._open(); // for legacy IDE
        smalltalk.popupHelios(); // for Helios IDE
    });
  For detailed explanation of amber loading, see:
  https://github.com/amber-smalltalk/amber/wiki/How-to-load-amber
 */

var require;

require = function (require) {
    // To be able to use its path and attrubutes
    // to map other parts of Amber, this code must find its <script> tag.
    // It first looks for id 'amber-path-mapper'.
    // When loading amber.js asynchronously, you must include this id,
    // or the code can not reliably find its <script>.
    var me = document.getElementById("amber-path-mapper");
    if (!me || me.tagName.toLowerCase() !== "script") {
        // If <script> with 'amber-path-mapper' id is not present,
        // (this id is not necessary for inline <script> tag in HTML),
        // it uses the "find the last <script> tag present in the moment" method.
        var scripts = document.getElementsByTagName("script");
        me = scripts[scripts.length - 1];
    }
    var src = me.src;
    // strip the last two elements from the URL
    // e.g. http://app.com/amber/support/amber.js -> http://app.com/amber
    var amber_home = resolveViaDOM(src).replace(/\/[^\/]+\/[^\/]+$/, "");
    // In case of nonstandard deployment, you can specify libraries placement directly ...
    var library_home = me.hasAttribute('data-libs') && me.getAttribute('data-libs');

    // ... otherwise, this heuristics is used:
    if (!library_home) {
        // At the present moment, bower tries to have flat hierarchy,
        // which leads to two possible scenarios:
        // 1. amber itself was deployed via bower,
        //    its libraries are at the same bower dir
        //    where amber itself is placed
        // 2. amber was deployed in different fashion,
        //    its libraries are included by bower locally, inside amber
        // The detection is done by looking for '/bower_components/' in amber path.
        var match = amber_home.match(/^(.*\/bower_components)\//);
        library_home = match ? match[1] : amber_home + '/bower_components';
    }

    function resolveViaDOM(url) {
        var a = document.createElement("a");
        a.href = url;
        return a.href;
    }

    var config = {
        paths: {
            'amber': amber_home + '/support',
            'amber_vm': amber_home + '/support',
            'amber_css': amber_home + '/css',
            'amber_lib': library_home,
            'amber_core': amber_home + '/src',
            'amber_helios/html': amber_home,
            'jquery': library_home + '/jquery/jquery.min',
            'jquery-ui': library_home + '/jquery-ui/ui/minified/jquery-ui.min'
        },
        map: {
            '*': {
                'css': 'amber_lib/require-css/css'
            }
        },
        shim: {
            'jquery-ui': {
                deps: [ 'jquery' ]
            },
            'amber_lib/bootstrap/js/bootstrap': {
                deps: [ 'jquery', 'css!amber_lib/bootstrap/css/bootstrap' ]
            },
            'amber_lib/codemirror/lib/codemirror': {
                deps: [ 'css!amber_lib/codemirror/lib/codemirror' ]
            },
            'amber_lib/jquery-tabby/jquery.textarea': {
                deps: [ 'jquery' ]
            },
            'amber_core/IDE': {
                deps: [ 'amber_lib/codemirror/mode/smalltalk/smalltalk' ]
            },
            'amber_lib/codemirror/mode/smalltalk/smalltalk': {
                deps: [ '../../lib/codemirror' ]
            },
            'amber_lib/codemirror/addon/hint/show-hint': {
                deps: [ '../../lib/codemirror' ]
            },
            'ensure-console': {
                exports: 'console'
            }
        }
    };

    // This is to allow both alternatives of loading:
    // before require.js as well as after require.js
    // See http://requirejs.org/docs/api.html#config for details
    // of usage of 'require' global to allow to pre-define configuration
    // before require.js is loaded.
    if (require) {
        require.config(config);
        return require;
    } else {
        return config;
    }
}(require);
<commit_msg>rename callback function parameter to `amber`<commit_after>/* Amber package loading.
  Load this script as well as require.js (works in any order;
    either defines 'require', thus passing config, if loaded prior require.js;
    or calls require.config, if loaded post require.js).
  Usage example:
    require(['amber/devel'], function(amber) {
        amber.initialize({"transport.defaultAmdNamespace": "com_example_myproject"});

        amber.globals.Browser._open(); // for legacy IDE
        amber.popupHelios(); // for Helios IDE
    });
  For detailed explanation of amber loading, see:
  https://github.com/amber-smalltalk/amber/wiki/How-to-load-amber
 */

var require;

require = function (require) {
    // To be able to use its path and attrubutes
    // to map other parts of Amber, this code must find its <script> tag.
    // It first looks for id 'amber-path-mapper'.
    // When loading amber.js asynchronously, you must include this id,
    // or the code can not reliably find its <script>.
    var me = document.getElementById("amber-path-mapper");
    if (!me || me.tagName.toLowerCase() !== "script") {
        // If <script> with 'amber-path-mapper' id is not present,
        // (this id is not necessary for inline <script> tag in HTML),
        // it uses the "find the last <script> tag present in the moment" method.
        var scripts = document.getElementsByTagName("script");
        me = scripts[scripts.length - 1];
    }
    var src = me.src;
    // strip the last two elements from the URL
    // e.g. http://app.com/amber/support/amber.js -> http://app.com/amber
    var amber_home = resolveViaDOM(src).replace(/\/[^\/]+\/[^\/]+$/, "");
    // In case of nonstandard deployment, you can specify libraries placement directly ...
    var library_home = me.hasAttribute('data-libs') && me.getAttribute('data-libs');

    // ... otherwise, this heuristics is used:
    if (!library_home) {
        // At the present moment, bower tries to have flat hierarchy,
        // which leads to two possible scenarios:
        // 1. amber itself was deployed via bower,
        //    its libraries are at the same bower dir
        //    where amber itself is placed
        // 2. amber was deployed in different fashion,
        //    its libraries are included by bower locally, inside amber
        // The detection is done by looking for '/bower_components/' in amber path.
        var match = amber_home.match(/^(.*\/bower_components)\//);
        library_home = match ? match[1] : amber_home + '/bower_components';
    }

    function resolveViaDOM(url) {
        var a = document.createElement("a");
        a.href = url;
        return a.href;
    }

    var config = {
        paths: {
            'amber': amber_home + '/support',
            'amber_vm': amber_home + '/support',
            'amber_css': amber_home + '/css',
            'amber_lib': library_home,
            'amber_core': amber_home + '/src',
            'amber_helios/html': amber_home,
            'jquery': library_home + '/jquery/jquery.min',
            'jquery-ui': library_home + '/jquery-ui/ui/minified/jquery-ui.min'
        },
        map: {
            '*': {
                'css': 'amber_lib/require-css/css'
            }
        },
        shim: {
            'jquery-ui': {
                deps: [ 'jquery' ]
            },
            'amber_lib/bootstrap/js/bootstrap': {
                deps: [ 'jquery', 'css!amber_lib/bootstrap/css/bootstrap' ]
            },
            'amber_lib/codemirror/lib/codemirror': {
                deps: [ 'css!amber_lib/codemirror/lib/codemirror' ]
            },
            'amber_lib/jquery-tabby/jquery.textarea': {
                deps: [ 'jquery' ]
            },
            'amber_core/IDE': {
                deps: [ 'amber_lib/codemirror/mode/smalltalk/smalltalk' ]
            },
            'amber_lib/codemirror/mode/smalltalk/smalltalk': {
                deps: [ '../../lib/codemirror' ]
            },
            'amber_lib/codemirror/addon/hint/show-hint': {
                deps: [ '../../lib/codemirror' ]
            },
            'ensure-console': {
                exports: 'console'
            }
        }
    };

    // This is to allow both alternatives of loading:
    // before require.js as well as after require.js
    // See http://requirejs.org/docs/api.html#config for details
    // of usage of 'require' global to allow to pre-define configuration
    // before require.js is loaded.
    if (require) {
        require.config(config);
        return require;
    } else {
        return config;
    }
}(require);
<|endoftext|>