<commit_before>/*

GET /note
GET /note/:id
POST /note
PUT /note/:id
DELETE /note/:id

*/
HTTP = Package.http && Package.http.HTTP || {};

var url = Npm.require('url');

// Primary local test scope
_methodHTTP = {};

_methodHTTP.methodHandlers = {};
_methodHTTP.methodTree = {};

_methodHTTP.nameFollowsConventions = function(name) {
  // Check that name is string, not a falsy or empty
  return name && name === '' + name && name !== '';
};

_methodHTTP.getNameList = function(name) {
  // Remove leading and trailing slashes and make command array
  name = name && name.replace(/^\//g, '') || ''; // /^\/|\/$/g
  // TODO: Get the format from the url - eg.: "/list/45.json" format should be
  // set in this function by splitting the last list item by . and have format
  // as the last item. How should we toggle:
  // "/list/45/item.name.json" and "/list/45/item.name"?
  // We would either have to check all known formats or allways determin the "."
  // as an extension. Resolving in "json" and "name" as handed format - the user
  // Could simply just add the format as a parametre? or be explicit about
  // naming
  return name && name.split('/') || [];
};

// Merge two arrays one containing keys and one values
_methodHTTP.createObject = function(keys, values) {
  var result = {};
  if (keys && values) {
    for (var i = 0; i < keys.length; i++) {
      result[keys[i]] = decodeURIComponent(values[i]) || '';
    }
  }
  return result;
};

_methodHTTP.addToMethodTree = function(methodName) {
  var list = _methodHTTP.getNameList(methodName);
  var name = '/';
  // Contains the list of params names
  var params = [];
  var currentMethodTree = _methodHTTP.methodTree;

  for (var i = 0; i < list.length; i++) {
    var lastListItem = (i === list.length - 1);

    // get the key name
    var key = list[i];
    // Check if it expects a value
    if (key[0] === ':') {
      // This is a value
      params.push(key.slice(1));
      key = ':value';
    }
    name += key + '/';

    // Set the key into the method tree
    if (typeof currentMethodTree[key] === 'undefined') {
      currentMethodTree[key] = {};
    }

    // Dig deeper
    currentMethodTree = currentMethodTree[key];

  }

  if (_.isEmpty(currentMethodTree[':ref'])) {
    currentMethodTree[':ref'] = {
      name: name,
      params: params
    };
  }

  return currentMethodTree[':ref'];
};

// This method should be optimized for speed since its called on allmost every
// http call to the server so we return null as soon as we know its not a method
_methodHTTP.getMethod = function(name) {
  // Check if the
  if (!_methodHTTP.nameFollowsConventions(name)) {
    return null;
  }
  var list = _methodHTTP.getNameList(name);
  // Check if we got a correct list
  if (!list || !list.length) {
    return null;
  }
  // Set current refernce in the _methodHTTP.methodTree
  var currentMethodTree = _methodHTTP.methodTree;
  // Buffer for values to hand on later
  var values = [];
  // Iterate over the method name and check if its found in the method tree
  for (var i = 0; i < list.length; i++) {
    // get the key name
    var key = list[i];
    // We expect to find the key or :value if not we break
    if (typeof currentMethodTree[key] !== 'undefined' ||
            typeof currentMethodTree[':value'] !== 'undefined') {
      // We got a result now check if its a value
      if (typeof currentMethodTree[key] === 'undefined') {
        // Push the value
        values.push(key);
        // Set the key to :value to dig deeper
        key = ':value';
      }

    } else {
      // Break - method call not found
      return null;
    }

    // Dig deeper
    currentMethodTree = currentMethodTree[key];
  }

  // Extract reference pointer
  var reference = currentMethodTree && currentMethodTree[':ref'];
  if (typeof reference !== 'undefined') {
    return {
      name: reference.name,
      params: _methodHTTP.createObject(reference.params, values)
    };
  } else {
    // Did not get any reference to the method
    return null;
  }
};

// This method retrieves the userId from the token and makes sure that the token
// is valid and not expired
_methodHTTP.getUserId = function() {
  var self = this;

  // // Get ip, x-forwarded-for can be comma seperated ips where the first is the
  // // client ip
  // var ip = self.req.headers['x-forwarded-for'] &&
  //         // Return the first item in ip list
  //         self.req.headers['x-forwarded-for'].split(',')[0] ||
  //         // or return the remoteAddress
  //         self.req.connection.remoteAddress;

  // Check authentication
  var userToken = self.query.token;

  // Check if we are handed strings
  try {
    userToken && check(userToken, String);
  } catch(err) {
    throw new Meteor.Error(404, 'Error user token and id not of type strings, Error: ' + (err.stack || err.message));
  }

  // Set the this.userId
  if (userToken) {
    // Look up user to check if user exists and is loggedin via token
    var user = Meteor.users.findOne({ 'services.resume.loginTokens.token': userToken });
    // TODO: check 'services.resume.loginTokens.when' to have the token expire

    // Set the userId in the scope
    return user && user._id;
  }

  return null;
};

// Public interface for adding server-side http methods - if setting a method to
// 'false' it would actually remove the method (can be used to unpublish a method)
HTTP.methods = function(newMethods) {
  _.each(newMethods, function(func, name) {
    if (_methodHTTP.nameFollowsConventions(name)) {
      // Check if we got a function
      //if (typeof func === 'function') {
        var method = _methodHTTP.addToMethodTree(name);
        // The func is good
        if (typeof _methodHTTP.methodHandlers[method.name] !== 'undefined') {
          if (func === false) {
            // If the method is set to false then unpublish
            delete _methodHTTP.methodHandlers[method.name];
            // Delete the reference in the _methodHTTP.methodTree
            delete method.name;
            delete method.params;
          } else {
            // We should not allow overwriting - following Meteor.methods
            throw new Error('HTTP method "' + name + '" is already registered');
          }
        } else {
          // We could have a function or a object
          // The object could have:
          // '/test/': {
          //   auth: function() ... returning the userId using over default
          //
          //   method: function() ...
          //   or
          //   post: function() ...
          //   put:
          //   get:
          //   delete:
          // }

          /*
          We conform to the object format:
          {
            auth:
            post:
            put:
            get:
            delete:
          }
          This way we have a uniform reference
          */

          var uniObj = {};
          if (typeof func === 'function') {
            uniObj = {
              'auth': _methodHTTP.getUserId,
              'POST': func,
              'PUT': func,
              'GET': func,
              'DELETE': func
            };
          } else {
            uniObj = {
              'auth': func.auth || _methodHTTP.getUserId,
              'POST': func.post || func.method,
              'PUT': func.put || func.method,
              'GET': func.get || func.method,
              'DELETE': func.delete || func.method
            };
          }

          // Registre the method
          _methodHTTP.methodHandlers[method.name] = uniObj; // func;

        }
      // } else {
      //   // We do require a function as a function to execute later
      //   throw new Error('HTTP.methods failed: ' + name + ' is not a function');
      // }
    } else {
      // We have to follow the naming spec defined in nameFollowsConventions
      throw new Error('HTTP.method "' + name + '" invalid naming of method');
    }
  });
};

var sendError = function(res, code, message) {
  res.writeHead(code);
  res.end(message);
};

// This handler collects the header data into either an object (if json) or the
// raw data. The data is passed to the callback
var requestHandler = function(req, res, callback) {
  if (typeof callback !== 'function') {
    return null;
  }

  var body = '';

  // Extract the body
  req.on('data', function(data) {
    body += data;
    if (body.length > 1e6) {
      body = '';
      // Flood attack or faulty client
      sendError(res, 413, 'Flood attack or faulty client');
      req.connection.destroy();
    }
  });

  // When message is ready to be passed on
  req.on('end', function() {
    if (res.finished) {
      return;
    }
    // Convert the body into json and extract the data object
    var result = {};
    try {
      if (body !== '') {
        result = EJSON.parse(body);
      }
    } catch(err) {
      // Could not parse so we return the raw data
      result = body;
    }

    try {
      callback(result);
    } catch(err) {
      sendError(res, 500, 'Error in requestHandler callback, Error: ' + (err.stack || err.message) );
    }
  });

};

// Handle the actual connection
WebApp.connectHandlers.use(function(req, res, next) {

  // Check to se if this is a http method call
  var method = _methodHTTP.getMethod(req._parsedUrl.pathname);

  // If method is null then it wasn't and we pass the request along
  if (method === null) {
    return next();
  }

  requestHandler(req, res, function(data) {
    var methodReference = method.name;

    // If methodsHandler not found or somehow the methodshandler is not a
    // function then return a 404
    if (typeof _methodHTTP.methodHandlers[methodReference] === 'undefined') {
      sendError(res, 404, 'Error HTTP method handler "' + methodReference + '" is not found');
      return;
    }

    // Set fiber scope
    var fiberScope = {
      // Pointers to Request / Response
      req: req,
      res: res,
      // Request / Response helpers
      statusCode: 200,
      method: req.method,
      // Headers for response
      headers: {
        'Content-Type': 'text/html'  // Set default type
      },
      // Arguments
      data: data,
      query: req.query,
      params: method.params,
      // Method reference
      reference: methodReference,
      methodObject: _methodHTTP.methodHandlers[methodReference],
    };

    // Helper functions this scope
    Fiber = Npm.require('fibers');
    runServerMethod = Fiber(function(self) {
      // We fetch methods data from methodsHandler, the handler uses the this.addItem()
      // function to populate the methods, this way we have better check control and
      // better error handling + messages

      // The scope for the user methodObject callbacks
      var thisScope = {
        // The user whos id and token was used to run this method, if set/found
        userId: null,
        // The id of the data
        _id: null,
        // Set the query params ?token=1&id=2 -> { token: 1, id: 2 }
        query: self.query,
        // Set params /foo/:name/test/:id -> { name: '', id: '' }
        params: self.params,
        // Method GET, PUT, POST, DELETE
        method: self.method,
        // User agent
        userAgent: req.headers['user-agent'],
        // All request headers
        requestHeaders: req.headers,
        // Set the userId
        setUserId: function(id) {
          this.userId = id;
        },
        // We dont simulate / run this on the client at the moment
        isSimulation: false,
        // Run the next method in a new fiber - This is default at the moment
        unblock: function() {},
        // Set the content type in header, defaults to text/html?
        setContentType: function(type) {
          self.headers['Content-Type'] = type;
        },
        setStatusCode: function(code) {
          self.statusCode = code;
        },
        addHeader: function(key, value) {
          self.headers[key] = value;
        }
      };

      var methodCall = self.methodObject[self.method];

      // If the method call is set for the POST/PUT/GET or DELETE then run the
      // respective methodCall if its a function
      if (typeof methodCall === 'function') {

        // Get the userId - This is either set as a method specific handler and
        // will allways default back to the builtin getUserId handler
        try {
          // Try to set the userId
          thisScope.userId = self.methodObject.auth.apply(self);
        } catch(err) {
          sendError(res, err.error, (err.message || err.stack));
          return;
        }

        // Get the result of the methodCall
        var result;
        // Get a result back to send to the client
        try {
          result = methodCall.apply(thisScope, [self.data]) || '';
        } catch(err) {
          if (err instanceof Meteor.Error) {
            // Return controlled error
            sendError(res, err.error, err.message);
          } else {
            // Return error trace - this is not intented
            sendError(res, 503, 'Error in method "' + self.reference + '", Error: ' + (err.stack || err.message) );
          }
          return;
        }

        // If OK / 200 then Return the result
        if (self.statusCode === 200) {
          var resultBuffer = new Buffer(result);
          // Check if user wants to overwrite content length for some reason?
          if (typeof self.headers['Content-Length'] === 'undefined') {
            self.headers['Content-Length'] = resultBuffer.length;
          }
          // Set headers
          _.each(self.headers, function(value, key) {
            self.res.setHeader(key, value);
          });
          // End response
          self.res.end(resultBuffer);
        } else {
          // Set headers
          _.each(self.headers, function(value, key) {
            // If value is defined then set the header, this allows for unsetting
            // the default content-type
            if (typeof value !== 'undefined')
              self.res.setHeader(key, value);
          });
          // Allow user to alter the status code and send a message
          sendError(res, self.statusCode, result);
        }

      } else {
        sendError(res, 404, 'Service not found');
      }

    });
    // Run http methods handler
    try {
      runServerMethod.run(fiberScope);
    } catch(err) {
      sendError(res, 500, 'Error running the server http method handler, Error: ' + (err.stack || err.message));
    }

  }); // EO Request handler

});
<commit_msg>support binary data from the request<commit_after>/*

GET /note
GET /note/:id
POST /note
PUT /note/:id
DELETE /note/:id

*/
HTTP = Package.http && Package.http.HTTP || {};

var url = Npm.require('url');

// Primary local test scope
_methodHTTP = {};

_methodHTTP.methodHandlers = {};
_methodHTTP.methodTree = {};

_methodHTTP.nameFollowsConventions = function(name) {
  // Check that name is string, not a falsy or empty
  return name && name === '' + name && name !== '';
};

_methodHTTP.getNameList = function(name) {
  // Remove leading and trailing slashes and make command array
  name = name && name.replace(/^\//g, '') || ''; // /^\/|\/$/g
  // TODO: Get the format from the url - eg.: "/list/45.json" format should be
  // set in this function by splitting the last list item by . and have format
  // as the last item. How should we toggle:
  // "/list/45/item.name.json" and "/list/45/item.name"?
  // We would either have to check all known formats or allways determin the "."
  // as an extension. Resolving in "json" and "name" as handed format - the user
  // Could simply just add the format as a parametre? or be explicit about
  // naming
  return name && name.split('/') || [];
};

// Merge two arrays one containing keys and one values
_methodHTTP.createObject = function(keys, values) {
  var result = {};
  if (keys && values) {
    for (var i = 0; i < keys.length; i++) {
      result[keys[i]] = decodeURIComponent(values[i]) || '';
    }
  }
  return result;
};

_methodHTTP.addToMethodTree = function(methodName) {
  var list = _methodHTTP.getNameList(methodName);
  var name = '/';
  // Contains the list of params names
  var params = [];
  var currentMethodTree = _methodHTTP.methodTree;

  for (var i = 0; i < list.length; i++) {
    var lastListItem = (i === list.length - 1);

    // get the key name
    var key = list[i];
    // Check if it expects a value
    if (key[0] === ':') {
      // This is a value
      params.push(key.slice(1));
      key = ':value';
    }
    name += key + '/';

    // Set the key into the method tree
    if (typeof currentMethodTree[key] === 'undefined') {
      currentMethodTree[key] = {};
    }

    // Dig deeper
    currentMethodTree = currentMethodTree[key];

  }

  if (_.isEmpty(currentMethodTree[':ref'])) {
    currentMethodTree[':ref'] = {
      name: name,
      params: params
    };
  }

  return currentMethodTree[':ref'];
};

// This method should be optimized for speed since its called on allmost every
// http call to the server so we return null as soon as we know its not a method
_methodHTTP.getMethod = function(name) {
  // Check if the
  if (!_methodHTTP.nameFollowsConventions(name)) {
    return null;
  }
  var list = _methodHTTP.getNameList(name);
  // Check if we got a correct list
  if (!list || !list.length) {
    return null;
  }
  // Set current refernce in the _methodHTTP.methodTree
  var currentMethodTree = _methodHTTP.methodTree;
  // Buffer for values to hand on later
  var values = [];
  // Iterate over the method name and check if its found in the method tree
  for (var i = 0; i < list.length; i++) {
    // get the key name
    var key = list[i];
    // We expect to find the key or :value if not we break
    if (typeof currentMethodTree[key] !== 'undefined' ||
            typeof currentMethodTree[':value'] !== 'undefined') {
      // We got a result now check if its a value
      if (typeof currentMethodTree[key] === 'undefined') {
        // Push the value
        values.push(key);
        // Set the key to :value to dig deeper
        key = ':value';
      }

    } else {
      // Break - method call not found
      return null;
    }

    // Dig deeper
    currentMethodTree = currentMethodTree[key];
  }

  // Extract reference pointer
  var reference = currentMethodTree && currentMethodTree[':ref'];
  if (typeof reference !== 'undefined') {
    return {
      name: reference.name,
      params: _methodHTTP.createObject(reference.params, values)
    };
  } else {
    // Did not get any reference to the method
    return null;
  }
};

// This method retrieves the userId from the token and makes sure that the token
// is valid and not expired
_methodHTTP.getUserId = function() {
  var self = this;

  // // Get ip, x-forwarded-for can be comma seperated ips where the first is the
  // // client ip
  // var ip = self.req.headers['x-forwarded-for'] &&
  //         // Return the first item in ip list
  //         self.req.headers['x-forwarded-for'].split(',')[0] ||
  //         // or return the remoteAddress
  //         self.req.connection.remoteAddress;

  // Check authentication
  var userToken = self.query.token;

  // Check if we are handed strings
  try {
    userToken && check(userToken, String);
  } catch(err) {
    throw new Meteor.Error(404, 'Error user token and id not of type strings, Error: ' + (err.stack || err.message));
  }

  // Set the this.userId
  if (userToken) {
    // Look up user to check if user exists and is loggedin via token
    var user = Meteor.users.findOne({ 'services.resume.loginTokens.token': userToken });
    // TODO: check 'services.resume.loginTokens.when' to have the token expire

    // Set the userId in the scope
    return user && user._id;
  }

  return null;
};

// Public interface for adding server-side http methods - if setting a method to
// 'false' it would actually remove the method (can be used to unpublish a method)
HTTP.methods = function(newMethods) {
  _.each(newMethods, function(func, name) {
    if (_methodHTTP.nameFollowsConventions(name)) {
      // Check if we got a function
      //if (typeof func === 'function') {
        var method = _methodHTTP.addToMethodTree(name);
        // The func is good
        if (typeof _methodHTTP.methodHandlers[method.name] !== 'undefined') {
          if (func === false) {
            // If the method is set to false then unpublish
            delete _methodHTTP.methodHandlers[method.name];
            // Delete the reference in the _methodHTTP.methodTree
            delete method.name;
            delete method.params;
          } else {
            // We should not allow overwriting - following Meteor.methods
            throw new Error('HTTP method "' + name + '" is already registered');
          }
        } else {
          // We could have a function or a object
          // The object could have:
          // '/test/': {
          //   auth: function() ... returning the userId using over default
          //
          //   method: function() ...
          //   or
          //   post: function() ...
          //   put:
          //   get:
          //   delete:
          // }

          /*
          We conform to the object format:
          {
            auth:
            post:
            put:
            get:
            delete:
          }
          This way we have a uniform reference
          */

          var uniObj = {};
          if (typeof func === 'function') {
            uniObj = {
              'auth': _methodHTTP.getUserId,
              'POST': func,
              'PUT': func,
              'GET': func,
              'DELETE': func
            };
          } else {
            uniObj = {
              'auth': func.auth || _methodHTTP.getUserId,
              'POST': func.post || func.method,
              'PUT': func.put || func.method,
              'GET': func.get || func.method,
              'DELETE': func.delete || func.method
            };
          }

          // Registre the method
          _methodHTTP.methodHandlers[method.name] = uniObj; // func;

        }
      // } else {
      //   // We do require a function as a function to execute later
      //   throw new Error('HTTP.methods failed: ' + name + ' is not a function');
      // }
    } else {
      // We have to follow the naming spec defined in nameFollowsConventions
      throw new Error('HTTP.method "' + name + '" invalid naming of method');
    }
  });
};

var sendError = function(res, code, message) {
  res.writeHead(code);
  res.end(message);
};

// This handler collects the header data into either an object (if json) or the
// raw data. The data is passed to the callback
var requestHandler = function(req, res, callback) {
  if (typeof callback !== 'function') {
    return null;
  }

  var body = '', bufferData = [], dataLen = 0;

  // Extract the body
  req.on('data', function(data) {
    var cancel = false;
    if (typeof data === "string") {
      body += data;
      if (body.length > 1e6) {
        cancel = true;
      }
    } else if (data instanceof Buffer) {
      bufferData.push(data);
      dataLen += data.length;
    }

    if (cancel) {
      body = '';
      dataLen = 0;
      bufferData = [];
      // Flood attack or faulty client
      sendError(res, 413, 'Flood attack or faulty client');
      req.connection.destroy();
    }
  });

  // When message is ready to be passed on
  req.on('end', function() {
    if (res.finished) {
      return;
    }
    // Convert the body into json and extract the data object
    var result = {};
    if (dataLen) {
      result = new Buffer(dataLen);
      for (var i = 0, ln = bufferData.length, pos = 0; i < ln; i++) {
        bufferData[i].copy(result, pos);
        pos += bufferData[i].length;
      }
    } else {
      try {
        if (body !== '') {
          result = EJSON.parse(body);
        }
      } catch(err) {
        // Could not parse so we return the raw data
        result = body;
      }
    }

    try {
      callback(result);
    } catch(err) {
      sendError(res, 500, 'Error in requestHandler callback, Error: ' + (err.stack || err.message) );
    }
  });

};

// Handle the actual connection
WebApp.connectHandlers.use(function(req, res, next) {

  // Check to se if this is a http method call
  var method = _methodHTTP.getMethod(req._parsedUrl.pathname);

  // If method is null then it wasn't and we pass the request along
  if (method === null) {
    return next();
  }

  requestHandler(req, res, function(data) {
    var methodReference = method.name;

    // If methodsHandler not found or somehow the methodshandler is not a
    // function then return a 404
    if (typeof _methodHTTP.methodHandlers[methodReference] === 'undefined') {
      sendError(res, 404, 'Error HTTP method handler "' + methodReference + '" is not found');
      return;
    }

    // Set fiber scope
    var fiberScope = {
      // Pointers to Request / Response
      req: req,
      res: res,
      // Request / Response helpers
      statusCode: 200,
      method: req.method,
      // Headers for response
      headers: {
        'Content-Type': 'text/html'  // Set default type
      },
      // Arguments
      data: data,
      query: req.query,
      params: method.params,
      // Method reference
      reference: methodReference,
      methodObject: _methodHTTP.methodHandlers[methodReference],
    };

    // Helper functions this scope
    Fiber = Npm.require('fibers');
    runServerMethod = Fiber(function(self) {
      // We fetch methods data from methodsHandler, the handler uses the this.addItem()
      // function to populate the methods, this way we have better check control and
      // better error handling + messages

      // The scope for the user methodObject callbacks
      var thisScope = {
        // The user whos id and token was used to run this method, if set/found
        userId: null,
        // The id of the data
        _id: null,
        // Set the query params ?token=1&id=2 -> { token: 1, id: 2 }
        query: self.query,
        // Set params /foo/:name/test/:id -> { name: '', id: '' }
        params: self.params,
        // Method GET, PUT, POST, DELETE
        method: self.method,
        // User agent
        userAgent: req.headers['user-agent'],
        // All request headers
        requestHeaders: req.headers,
        // Set the userId
        setUserId: function(id) {
          this.userId = id;
        },
        // We dont simulate / run this on the client at the moment
        isSimulation: false,
        // Run the next method in a new fiber - This is default at the moment
        unblock: function() {},
        // Set the content type in header, defaults to text/html?
        setContentType: function(type) {
          self.headers['Content-Type'] = type;
        },
        setStatusCode: function(code) {
          self.statusCode = code;
        },
        addHeader: function(key, value) {
          self.headers[key] = value;
        }
      };

      var methodCall = self.methodObject[self.method];

      // If the method call is set for the POST/PUT/GET or DELETE then run the
      // respective methodCall if its a function
      if (typeof methodCall === 'function') {

        // Get the userId - This is either set as a method specific handler and
        // will allways default back to the builtin getUserId handler
        try {
          // Try to set the userId
          thisScope.userId = self.methodObject.auth.apply(self);
        } catch(err) {
          sendError(res, err.error, (err.message || err.stack));
          return;
        }

        // Get the result of the methodCall
        var result;
        // Get a result back to send to the client
        try {
          result = methodCall.apply(thisScope, [self.data]) || '';
        } catch(err) {
          if (err instanceof Meteor.Error) {
            // Return controlled error
            sendError(res, err.error, err.message);
          } else {
            // Return error trace - this is not intented
            sendError(res, 503, 'Error in method "' + self.reference + '", Error: ' + (err.stack || err.message) );
          }
          return;
        }

        // If OK / 200 then Return the result
        if (self.statusCode === 200) {
          var resultBuffer = new Buffer(result);
          // Check if user wants to overwrite content length for some reason?
          if (typeof self.headers['Content-Length'] === 'undefined') {
            self.headers['Content-Length'] = resultBuffer.length;
          }
          // Set headers
          _.each(self.headers, function(value, key) {
            self.res.setHeader(key, value);
          });
          // End response
          self.res.end(resultBuffer);
        } else {
          // Set headers
          _.each(self.headers, function(value, key) {
            // If value is defined then set the header, this allows for unsetting
            // the default content-type
            if (typeof value !== 'undefined')
              self.res.setHeader(key, value);
          });
          // Allow user to alter the status code and send a message
          sendError(res, self.statusCode, result);
        }

      } else {
        sendError(res, 404, 'Service not found');
      }

    });
    // Run http methods handler
    try {
      runServerMethod.run(fiberScope);
    } catch(err) {
      sendError(res, 500, 'Error running the server http method handler, Error: ' + (err.stack || err.message));
    }

  }); // EO Request handler

});
<|endoftext|>


<commit_before>// require("Logger");
// require("InputValidator");
// require("MathAugments");

/*
 * Provides a crossover operator.
 *
 * @see Crossover
 */
var CrossoverOperator =
{
    onePointConstantLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);
        CrossoverOperator.validateSameLength(genome0, genome1)

        var index = Math.vizziniRandomIntFromRange(0, genome0.length);

        var part0 = genome0.slice(0, index);
        var part1 = genome1.slice(index);

        var answer = part0.concat(part1);

        return CrossoverOperator
                .assignCreator(answer, "onePointConstantLength");
    },

    onePointVariableLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);

        var index0 = Math.vizziniRandomIntFromRange(0, genome0.length);
        var index1 = Math.vizziniRandomIntFromRange(0, genome1.length);

        var part0 = genome0.slice(0, index0);
        var part1 = genome1.slice(index1);

        var answer = part0.concat(part1);

        return CrossoverOperator
                .assignCreator(answer, "onePointVariableLength");
    },

    twoPointConstantLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);
        CrossoverOperator.validateSameLength(genome0, genome1)

        if (genome0.length === 1)
        {
            // Degenerate case.
            var parent = (Math.random() < 0.5 ? genome0 : genome1);
            var answer = parent.slice();
            answer.creator = parent.creator;

            return answer;
        }
        else
        {
            var indices = CrossoverOperator.selectTwoIndices(genome0.length);

            var part0 = genome0.slice(0, indices[0]);
            var part1 = genome1.slice(indices[0], indices[1]);
            var part2 = genome0.slice(indices[1]);

            var answer = part0.concat(part1.concat(part2));

            return CrossoverOperator.assignCreator(answer,
                    "twoPointConstantLength");
        }
    },

    twoPointVariableLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);

        if (genome0.length === 1 || genome1.length === 1)
        {
            // Degenerate case.
            var parent = (Math.random() < 0.5 ? genome0 : genome1);
            var answer = parent.slice();
            answer.creator = parent.creator;

            return answer;
        }
        else
        {
            var indices0 = CrossoverOperator.selectTwoIndices(genome0.length);
            var indices1 = CrossoverOperator.selectTwoIndices(genome1.length);

            var part0 = genome0.slice(0, indices0[0]);
            var part1 = genome1.slice(indices1[0], indices1[1]);
            var part2 = genome0.slice(indices0[1]);

            var answer = part0.concat(part1.concat(part2));

            return CrossoverOperator.assignCreator(answer,
                    "twoPointVariableLength");
        }
    },

    uniformConstantLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);
        CrossoverOperator.validateSameLength(genome0, genome1)

        var answer = [];

        for (var i = 0; i < genome0.length; i++)
        {
            if (Math.random() < 0.5)
            {
                answer.push(genome0[i]);
            }
            else
            {
                answer.push(genome1[i]);
            }
        }

        return CrossoverOperator.assignCreator(answer, "uniformConstantLength");
    },

    assignCreator: function(genome, suffix)
    {
        genome.creator = "CrossoverOperator." + suffix;

        return genome;
    },

    selectTwoIndices: function(length)
    {
        if (length < 2) { throw "Can't selectTwoIndices from length = "
                + length; }

        var answer = [ 0, 0 ];

        while (answer[0] === answer[1])
        {
            answer[0] = Math.vizziniRandomIntFromRange(0, length);
            answer[1] = Math.vizziniRandomIntFromRange(0, length);
        }

        answer.sort();

        return answer;
    },

    validateSameLength: function(genome0, genome1)
    {
        if (genome0.length !== genome1.length) { throw "Genomes are different lengths: genome0.length = "
                + genome0.length + " genome1.length = " + genome1.length; }
    },
}

function Crossoverer(crossoverFunction)
{
    InputValidator.validateNotNull("crossoverFunction", crossoverFunction);

    this.execute = function(genome0, genome1)
    {
        return crossoverFunction(genome0, genome1);
    }
}
<commit_msg>Fixed the selectTwoIndices() sort to work for numbers.<commit_after>// require("Logger");
// require("InputValidator");
// require("MathAugments");

/*
 * Provides a crossover operator.
 *
 * @see Crossover
 */
var CrossoverOperator =
{
    onePointConstantLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);
        CrossoverOperator.validateSameLength(genome0, genome1);

        var index = Math.vizziniRandomIntFromRange(0, genome0.length);

        var part0 = genome0.slice(0, index);
        var part1 = genome1.slice(index);

        var answer = part0.concat(part1);

        return CrossoverOperator
                .assignCreator(answer, "onePointConstantLength");
    },

    onePointVariableLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);

        var index0 = Math.vizziniRandomIntFromRange(0, genome0.length);
        var index1 = Math.vizziniRandomIntFromRange(0, genome1.length);

        var part0 = genome0.slice(0, index0);
        var part1 = genome1.slice(index1);

        var answer = part0.concat(part1);

        return CrossoverOperator
                .assignCreator(answer, "onePointVariableLength");
    },

    twoPointConstantLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);
        CrossoverOperator.validateSameLength(genome0, genome1);

        if (genome0.length === 1)
        {
            // Degenerate case.
            var parent = (Math.random() < 0.5 ? genome0 : genome1);
            var answer = parent.slice();
            answer.creator = parent.creator;

            return answer;
        }
        else
        {
            var indices = CrossoverOperator.selectTwoIndices(genome0.length);

            var part0 = genome0.slice(0, indices[0]);
            var part1 = genome1.slice(indices[0], indices[1]);
            var part2 = genome0.slice(indices[1]);

            var answer = part0.concat(part1.concat(part2));

            return CrossoverOperator.assignCreator(answer,
                    "twoPointConstantLength");
        }
    },

    twoPointVariableLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);

        if (genome0.length === 1 || genome1.length === 1)
        {
            // Degenerate case.
            var parent = (Math.random() < 0.5 ? genome0 : genome1);
            var answer = parent.slice();
            answer.creator = parent.creator;

            return answer;
        }
        else
        {
            var indices0 = CrossoverOperator.selectTwoIndices(genome0.length);
            var indices1 = CrossoverOperator.selectTwoIndices(genome1.length);

            var part0 = genome0.slice(0, indices0[0]);
            var part1 = genome1.slice(indices1[0], indices1[1]);
            var part2 = genome0.slice(indices0[1]);

            var answer = part0.concat(part1.concat(part2));

            return CrossoverOperator.assignCreator(answer,
                    "twoPointVariableLength");
        }
    },

    uniformConstantLength: function(genome0, genome1)
    {
        InputValidator.validateNotNull("genome0", genome0);
        InputValidator.validateNotNull("genome1", genome1);
        CrossoverOperator.validateSameLength(genome0, genome1)

        var answer = [];

        for (var i = 0; i < genome0.length; i++)
        {
            if (Math.random() < 0.5)
            {
                answer.push(genome0[i]);
            }
            else
            {
                answer.push(genome1[i]);
            }
        }

        return CrossoverOperator.assignCreator(answer, "uniformConstantLength");
    },

    assignCreator: function(genome, suffix)
    {
        genome.creator = "CrossoverOperator." + suffix;

        return genome;
    },

    selectTwoIndices: function(length)
    {
        if (length < 2) { throw "Can't selectTwoIndices from length = "
                + length; }

        var answer = [ 0, 0 ];

        while (answer[0] === answer[1])
        {
            answer[0] = Math.vizziniRandomIntFromRange(0, length);
            answer[1] = Math.vizziniRandomIntFromRange(0, length);
        }

        answer.sort(function(a, b)
        {
            return a - b;
        });

        return answer;
    },

    validateSameLength: function(genome0, genome1)
    {
        if (genome0.length !== genome1.length) { throw "Genomes are different lengths: genome0.length = "
                + genome0.length + " genome1.length = " + genome1.length; }
    },
}

function Crossoverer(crossoverFunction)
{
    InputValidator.validateNotNull("crossoverFunction", crossoverFunction);

    this.execute = function(genome0, genome1)
    {
        return crossoverFunction(genome0, genome1);
    }
}
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     99
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v100 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     100
// ==/UserScript==
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     107
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v108 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     108
// ==/UserScript==
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';

export default class Icon extends Component {
  static propTypes = {
    symbol: PropTypes.string.isRequired,
    className: PropTypes.string
  }
  static defaultProps = {
    hidden: 'false'
  }
  render() {
    const {symbol, className, hidden} = this.props;
    const classStr = `glyphicon glyphicon-${symbol} ${className}`
    return (

    );
  }
}
<commit_msg>use classnames module in icon<commit_after>import React, { Component, PropTypes } from 'react';
import classNames from 'classnames'

export default class Icon extends Component {
  static propTypes = {
    symbol: PropTypes.string.isRequired,
    className: PropTypes.string
  }
  static defaultProps = {
    hidden: 'false'
  }
  render() {
    const {symbol, className, hidden} = this.props;
    const classStr = `glyphicon glyphicon-${symbol}`

    return (

    );
  }
}
<|endoftext|>


<commit_before>var React = require('react');

var Icon = React.createClass({
    propTypes: {
        id:   React.PropTypes.string,
        type: React.PropTypes.string,
        className: React.PropTypes.string
    },

    getDefaultProps: function() {
        return {
            type: 'octicon'
        };
    },

    render: function() {
        var type      = this.props.type;
        var id        = this.props.id;
        var className = this.props.className;

        if (!className) {
            className = type + ' ' + type + '-' + id;
        }

        return ;
    }
});

module.exports = Icon;
<commit_msg>Add prop "spin" for icon<commit_after>const React = require('react');
const classNames = require('classnames');

const Icon = React.createClass({
    propTypes: {
        // Icon of the icon in the collection
        id:        React.PropTypes.string,
        // Type of collection
        type:      React.PropTypes.string,
        // Extra className
        className: React.PropTypes.string,
        // Is the icon spinning?
        spin:      React.PropTypes.bool
    },

    getDefaultProps: function() {
        return {
            type: 'octicon',
            spin: false
        };
    },

    render: function() {
        let { type, id, className, spin } = this.props;

        className = classNames(
            type + ' ' + type + '-' + id,
            className,
            {
                'icon-spin': spin
            }
        );

        return ;
    }
});

module.exports = Icon;
<|endoftext|>


<commit_before>/**
 * Execute the macro insdie an img tag in an html file, which the src attribute
 * filled in.  The macro will load the image and input the height and width of
 * the image attributes with the correct dimensions
 *
 * Type:  On Demand
 *
 * @source        https://github.com/Komodo/macros
 * @author        Eric Promislow
 * @author        Carey Hoffman
 * @version       0.3
 */

var view = ko.views.manager.currentView;
var scimoz = view.scimoz;
var currentPos = scimoz.currentPos;
var currentLine = scimoz.lineFromPosition(currentPos);
var lineStartPos = scimoz.positionFromLine(currentLine);
var lineEndPos = scimoz.getLineEndPosition(currentLine);
var text = scimoz.getTextRange(lineStartPos, lineEndPos);
if (/<img.*src=["'](.+?)["']/.test(text)) {
    var url = RegExp.$1;
    var img = new Image();
    var baseUrl = view.koDoc.file.dirName;
    alert(baseUrl)
    url = ko.uriparse.pathToURI(baseUrl + "//" + url);
    img.src = url;
    alert(img.src)
    var writeTag = function() {
        if (!img.height || !img.width) {
            ko.statusBar.AddMessage("No img info at " + img.src, 500, true);
        } else {
            var newText = (' height="' + img.height + '" ' +
                           ' width="' + img.width + '"');
            scimoz.insertText(lineEndPos, newText);
        }
    }
    img.addEventListener("load", writeTag(), false);
}
<commit_msg>No highlighting, categorized and longer statusbar message.<commit_after>/**
 * Execute the macro insdie an img tag in an html file, which the src attribute
 * filled in.  The macro will load the image and input the height and width of
 * the image attributes with the correct dimensions
 *
 * Type:  On Demand
 *
 * @source        https://github.com/Komodo/macros
 * @author        Eric Promislow
 * @author        Carey Hoffman
 * @version       0.3
 */

var view = ko.views.manager.currentView;
var scimoz = view.scimoz;
var currentPos = scimoz.currentPos;
var currentLine = scimoz.lineFromPosition(currentPos);
var lineStartPos = scimoz.positionFromLine(currentLine);
var lineEndPos = scimoz.getLineEndPosition(currentLine);
var text = scimoz.getTextRange(lineStartPos, lineEndPos);
if (/<img.*src=["'](.+?)["']/.test(text)) {
    var url = RegExp.$1;
    var img = new Image();
    var baseUrl = view.koDoc.file.dirName;
    alert(baseUrl)
    url = ko.uriparse.pathToURI(baseUrl + "//" + url);
    img.src = url;
    alert(img.src)
    var writeTag = function() {
        if (!img.height || !img.width) {
            ko.statusBar.AddMessage("No img info at " + img.src,
                                    "img_dimensions_macro",
                                    3000,
                                    false);
        } else {
            var newText = (' height="' + img.height + '" ' +
                           ' width="' + img.width + '"');
            scimoz.insertText(lineEndPos, newText);
        }
    }
    img.addEventListener("load", writeTag(), false);
}
<|endoftext|>


<commit_before>// Forward compability
if (typeof UI === 'undefined' || typeof UI.registerHelper !== 'function') {
  UI = {
    registerHelper: function(name, f) {
      if (typeof Handlebars !== 'undefined') {
        return Handlebars.registerHelper(name, f);
      } else {
        throw new Error('No UI or Handlebars found');
      }
    }
  };
}

if (typeof UI !== 'undefined') {
    UI.registerHelper('getLength', function (a) {
      return a && a.length;
    });

    UI.registerHelper('isSelected', function (a, b) {
      return (a === b)?' selected': '';
    });

    UI.registerHelper('isChecked', function (a, b) {
      return (a === b)?' checked': '';
    });

    UI.registerHelper('cutString', function (str, len) {
      return (str.length > len)?str.substr(0, Math.max(len-3, 0))+'...':str;
    });

    UI.registerHelper('$eq', function (a, b) {
      return (a === b); //Only text, numbers, boolean - not array & objects
    });

    UI.registerHelper('$neq', function (a, b) {
      return (a !== b); //Only text, numbers, boolean - not array & objects
    });

    UI.registerHelper('$in', function (a, b, c, d) {
      return ( a === b || a === c || a === d);
    });

    UI.registerHelper('$nin', function (a, b, c, d) {
      return ( a !== b && a !== c && a !== d);
    });

    UI.registerHelper('$exists', function (a) {
      return ( a !== undefined);
    });

    UI.registerHelper('$lt', function (a, b) {
      return (a  b);
    });

    UI.registerHelper('$lte', function (a, b) {
      return (a <= b);
    });

    UI.registerHelper('$gte', function (a, b) {
      return (a >= b);
    });

    UI.registerHelper('$and', function (a, b) {
      return (a && b);
    });

    UI.registerHelper('$or', function (a, b) {
      return (a || b);
    });

    UI.registerHelper('$not', function (a) {
      return (!a);
    });

    UI.registerHelper('nl2br', function (text) {
        var nl2br = (text + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '
' + '$2');
        return new Spacebars.SafeString(nl2br);
    });

    UI.registerHelper('getText', function (text, lang) { // Deprecating
      var langKey = lang || null;
      return Helpers.getText(text, langKey);
    });

  UI.registerHelper("$mapped", function(arr) {
    if(!Array.isArray(arr)){
      try {
        arr = arr.fetch()
      }
      catch (e){
        console.log("Error in $mapped: perhaps you aren't sending in a collection or array.")
        return [];
      }
    }

    var $length = arr.length;

    var mappedArray = arr.map(function(item,index) {
      item.$length = $length;
      item.$index = index;
      item.$first = index === 0;
      item.$last  = index === arr.length-1;
      return item;
    });

    return mappedArray || [];
  });

    // UI.registerHelper('userRole', function ( /* arguments */) {
    //   var role = Session.get('currentRole');
    //   return _.any(arguments, function(value) { return (value == role); });
    // });

    /*
        Then $uper helper - Credit goes to @belisarius222 aka Ted Blackman for sparking an idear for a solution
    */
    Helpers.superScope = {};

    Helpers.addScope = function(name, obj) {
      // TODO: Get rid of underscore
      Helpers.superScope[name] = _.bind(function() { return this; }, obj);
    };

    Helpers.removeScope = function(name) {
      delete UI._globalHelpers[name];
      delete Helpers.superScope[name];
    };

    Helpers.addScope('Session', Session);
    Helpers.addScope('Meteor', Meteor);

    UI.registerHelper('$', function() {
      return Helpers.superScope;
    });
}
<commit_msg>Quick fix for code consistency<commit_after>// Forward compability
if (typeof UI === 'undefined' || typeof UI.registerHelper !== 'function') {
  UI = {
    registerHelper: function(name, f) {
      if (typeof Handlebars !== 'undefined') {
        return Handlebars.registerHelper(name, f);
      } else {
        throw new Error('No UI or Handlebars found');
      }
    }
  };
}

if (typeof UI !== 'undefined') {
    UI.registerHelper('getLength', function (a) {
      return a && a.length;
    });

    UI.registerHelper('isSelected', function (a, b) {
      return (a === b)?' selected': '';
    });

    UI.registerHelper('isChecked', function (a, b) {
      return (a === b)?' checked': '';
    });

    UI.registerHelper('cutString', function (str, len) {
      return (str.length > len)?str.substr(0, Math.max(len-3, 0))+'...':str;
    });

    UI.registerHelper('$eq', function (a, b) {
      return (a === b); //Only text, numbers, boolean - not array & objects
    });

    UI.registerHelper('$neq', function (a, b) {
      return (a !== b); //Only text, numbers, boolean - not array & objects
    });

    UI.registerHelper('$in', function (a, b, c, d) {
      return ( a === b || a === c || a === d);
    });

    UI.registerHelper('$nin', function (a, b, c, d) {
      return ( a !== b && a !== c && a !== d);
    });

    UI.registerHelper('$exists', function (a) {
      return ( a !== undefined);
    });

    UI.registerHelper('$lt', function (a, b) {
      return (a  b);
    });

    UI.registerHelper('$lte', function (a, b) {
      return (a <= b);
    });

    UI.registerHelper('$gte', function (a, b) {
      return (a >= b);
    });

    UI.registerHelper('$and', function (a, b) {
      return (a && b);
    });

    UI.registerHelper('$or', function (a, b) {
      return (a || b);
    });

    UI.registerHelper('$not', function (a) {
      return (!a);
    });

    UI.registerHelper('nl2br', function (text) {
        var nl2br = (text + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '
' + '$2');
        return new Spacebars.SafeString(nl2br);
    });

    UI.registerHelper('getText', function (text, lang) { // Deprecating
      var langKey = lang || null;
      return Helpers.getText(text, langKey);
    });

  UI.registerHelper("$mapped", function(arr) {
    if(!Array.isArray(arr)){
      try {
        arr = arr.fetch()
      }
      catch (e){
        console.log("Error in $mapped: perhaps you aren't sending in a collection or array.")
        return [];
      }
    }

    var $length = arr.length;

    var mappedArray = arr.map(function(item,index) {
      item.$length = $length;
      item.$index = index;
      item.$first = index === 0;
      item.$last  = index === $length-1;
      return item;
    });

    return mappedArray || [];
  });

    // UI.registerHelper('userRole', function ( /* arguments */) {
    //   var role = Session.get('currentRole');
    //   return _.any(arguments, function(value) { return (value == role); });
    // });

    /*
        Then $uper helper - Credit goes to @belisarius222 aka Ted Blackman for sparking an idear for a solution
    */
    Helpers.superScope = {};

    Helpers.addScope = function(name, obj) {
      // TODO: Get rid of underscore
      Helpers.superScope[name] = _.bind(function() { return this; }, obj);
    };

    Helpers.removeScope = function(name) {
      delete UI._globalHelpers[name];
      delete Helpers.superScope[name];
    };

    Helpers.addScope('Session', Session);
    Helpers.addScope('Meteor', Meteor);

    UI.registerHelper('$', function() {
      return Helpers.superScope;
    });
}
<|endoftext|>


<commit_before>const _ = require('lodash');
const qs = require('query-string');
const mustache = require('mustache');
const messages = require('./messages');

const handleError = err => {
  console.error(JSON.stringify(err.originalError.response.data));
};

const MOVIE_DETAIL_TEMPLATE = `{{title_long}}
Casts : {{casts}}
Rating : {{rating}}

{{{description_full}}}
`;

const MOVIE_MORE_TEXT = `{{year}}
{{genres}}
{{mpa_rating}}`;

class Handler {
  constructor(lineClient, ytsClient, firebaseClient) {
    this.lineClient = lineClient;
    this.ytsClient = ytsClient;
    this.firebaseClient = firebaseClient;
  }

  handleRequest(payload) {
    payload.events.forEach(event => {
      this.handleEvent(event);
    });
  }

  handleEvent(event) {
    const type = event.type;
    switch (type) {
      case 'message':
        this.handleMessageEvent(event);
        break;

      case 'postback':
        this.handlePostbackEvent(event);
        break;

      case 'follow':
        this.handleFollowEvent(event);
        break;

      default:
        console.log(`Unknown type ${type}`);
        break;
    }
  }

  handleFollowEvent(event) {
    this.lineClient.getProfile(event.source.userId)
      .then(profile=> {
        this.firebaseClient.addUser(profile);
        const greetingMessage = `Hello ${profile.displayName}! My name is YTS Bot. Chat bot to help you search and stream HD movies directly on your chat app.
Try typing "search <your movie title>" to getting started.

Happy watching!`
        this.lineClient.replyMessage(event.replyToken, messages.textMessage(greetingMessage));
      });
  }

  handleMessageEvent(event) {
    const type = event.message.type;
    switch (type) {
      case 'text':
        this.handleTextMessage(
          event.replyToken,
          event.source,
          event.message.text
        );
        break;

      default:
        console.log(`unhandled message type ${type}`);
        break;
    }
  }

  sendMovieList(replyToken, result) {
    if (result.movie_count == 0) {
      return this.lineClient
        .replyMessage(
          replyToken,
          messages.textMessage(`Empty result from database`)
        )
        .catch(handleError);
    }

    const carouselMessage = result.movies.map(movie => {
      const actions = [
        messages.actionUriTemplate(
          'Watch Trailer',
          `https://www.youtube.com/watch?v=${movie.yt_trailer_code}`
        ),
        messages.actionPostbackTemplate(
          'Details',
          qs.stringify({
            keyword: 'movie-detail',
            data: movie.id,
          })
        ),
      ];
      return messages.carouselColumnTemplate(
        movie.large_cover_image,
        movie.title,
        _.truncate(movie.summary, { length: 50, separator: /\W/ }),
        actions
      );
    });

    const message = messages.templateMessage(
      `Search result`,
      messages.carouselTemplate(carouselMessage)
    );
    return this.lineClient.replyMessage(replyToken, message).catch(handleError);
  }

  handleTextMessage(replyToken, source, text) {
    const token = text.split(' ');
    const keyword = token[0].toLowerCase();
    const term = token.slice(1, token.length).join(' ');
    switch (keyword) {
      case 'search':
        this.ytsClient
          .searchMovie(term)
          .then(result => {
            return this.sendMovieList(replyToken, result);
          })
          .catch(handleError);
        break;
      default:
        console.log(`unknown keyword ${keyword}`);
        break;
    }
  }

  handlePostbackEvent(event) {
    const postbackData = event.postback.data;
    this.handlePostback(event.replyToken, event.source, postbackData);
  }

  handlePostback(replyToken, source, data) {
    const parsedData = qs.parse(data);
    switch (parsedData.keyword) {
      case 'movie-detail':
        this.ytsClient
          .getMovie(parsedData.data)
          .then(result => {
            const movie = result.movie;
            const movieImageMessage = messages.imageMessage(
              movie.large_cover_image,
              movie.medium_cover_image
            );
            const movieDetailTextMessage = messages.textMessage(
              mustache.render(MOVIE_DETAIL_TEMPLATE, {
                title_long: movie.title_long,
                casts: (movie.cast || []).map(cast => cast.name).join(', '),
                rating: movie.rating,
                description_full: movie.description_full,
              })
            );
            const watchActions = movie.torrents.slice(0, 3).map(torr => {
              return messages.actionPostbackTemplate(
                `Watch in ${torr.quality}`,
                qs.stringify({
                  keyword: 'watchlink',
                  movie: torr.url,
                  image: movie.large_cover_image,
                  title: `${movie.title} (${torr.quality})`
                })
              );
            });

            const similarAction = messages.actionPostbackTemplate(
              'Simillar',
              qs.stringify({
                keyword: 'suggestion',
                data: movie.id,
              })
            );
            watchActions.push(similarAction);

            const buttonTemplate = messages.buttonTemplate(
              undefined,
              movie.title,
              mustache.render(MOVIE_MORE_TEXT, {
                year: movie.year,
                genres: (movie.genres.slice(0,3) || []).join(', '),
                mpa_rating: movie.mpa_rating,
              }),
              watchActions
            );

            const buttonMessage = messages.templateMessage(
              `${movie.title} options`,
              buttonTemplate
            );
            this.lineClient
              .replyMessage(replyToken, [
                movieImageMessage,
                movieDetailTextMessage,
                buttonMessage,
              ])
              .catch(handleError);
          })
          .catch(handleError);
        break;

      case 'suggestion':
        this.ytsClient.getSuggestions(parsedData.data).then(result => {
          return this.sendMovieList(replyToken, result);
        });
        break;

        case 'watchlink':
        this.firebaseClient.addWatchSession(source.userId, parsedData)
          .then(result => {
            this.firebaseClient.getFirestore().doc(`/session/${result.id}`)
              .onSnapshot(doc => {
                if (doc.data().status === 'ready') {
                  const sessionData = doc.data();
                  this.lineClient.pushMessage(sessionData.userId, messages.textMessage(`Watch ${parsedData.title} here ${result.url}`));
                };
              });
          });
          this.lineClient.replyMessage(replyToken, messages.textMessage(`Preparing ${parsedData.title}... We will notify you once the movie is ready`));
        break;
      default:
        break;
    }
  }
}

module.exports = Handler;
<commit_msg>update messages to avoid crash<commit_after>const _ = require('lodash');
const qs = require('query-string');
const mustache = require('mustache');
const messages = require('./messages');

const handleError = err => {
  console.error(JSON.stringify(err.originalError.response.data));
};

const MOVIE_DETAIL_TEMPLATE = `{{title_long}}
Casts : {{casts}}
Rating : {{rating}}
Genre: {{genre}}

{{{description_full}}}
`;

const MOVIE_MORE_TEXT = `{{year}}
Duration : {{duration}} minutes
{{mpa_rating}}`;

class Handler {
  constructor(lineClient, ytsClient, firebaseClient) {
    this.lineClient = lineClient;
    this.ytsClient = ytsClient;
    this.firebaseClient = firebaseClient;
  }

  handleRequest(payload) {
    payload.events.forEach(event => {
      this.handleEvent(event);
    });
  }

  handleEvent(event) {
    const type = event.type;
    switch (type) {
      case 'message':
        this.handleMessageEvent(event);
        break;

      case 'postback':
        this.handlePostbackEvent(event);
        break;

      case 'follow':
        this.handleFollowEvent(event);
        break;

      default:
        console.log(`Unknown type ${type}`);
        break;
    }
  }

  handleFollowEvent(event) {
    this.lineClient.getProfile(event.source.userId)
      .then(profile=> {
        this.firebaseClient.addUser(profile);
        const greetingMessage = `Hello ${profile.displayName}! My name is YTS Bot. Chat bot to help you search and stream HD movies directly on your chat app.
Try typing "search <your movie title>" to getting started.

Happy watching!`
        this.lineClient.replyMessage(event.replyToken, messages.textMessage(greetingMessage));
      });
  }

  handleMessageEvent(event) {
    const type = event.message.type;
    switch (type) {
      case 'text':
        this.handleTextMessage(
          event.replyToken,
          event.source,
          event.message.text
        );
        break;

      default:
        console.log(`unhandled message type ${type}`);
        break;
    }
  }

  sendMovieList(replyToken, result) {
    if (result.movie_count == 0) {
      return this.lineClient
        .replyMessage(
          replyToken,
          messages.textMessage(`Empty result from database`)
        )
        .catch(handleError);
    }

    const carouselMessage = result.movies.map(movie => {
      const actions = [
        messages.actionUriTemplate(
          'Watch Trailer',
          `https://www.youtube.com/watch?v=${movie.yt_trailer_code}`
        ),
        messages.actionPostbackTemplate(
          'Details',
          qs.stringify({
            keyword: 'movie-detail',
            data: movie.id,
          })
        ),
      ];
      return messages.carouselColumnTemplate(
        movie.large_cover_image,
        movie.title,
        _.truncate(movie.summary, { length: 50, separator: /\W/ }),
        actions
      );
    });

    const message = messages.templateMessage(
      `Search result`,
      messages.carouselTemplate(carouselMessage)
    );
    return this.lineClient.replyMessage(replyToken, message).catch(handleError);
  }

  handleTextMessage(replyToken, source, text) {
    const token = text.split(' ');
    const keyword = token[0].toLowerCase();
    const term = token.slice(1, token.length).join(' ');
    switch (keyword) {
      case 'search':
        this.ytsClient
          .searchMovie(term)
          .then(result => {
            return this.sendMovieList(replyToken, result);
          })
          .catch(handleError);
        break;
      default:
        console.log(`unknown keyword ${keyword}`);
        break;
    }
  }

  handlePostbackEvent(event) {
    const postbackData = event.postback.data;
    this.handlePostback(event.replyToken, event.source, postbackData);
  }

  handlePostback(replyToken, source, data) {
    const parsedData = qs.parse(data);
    switch (parsedData.keyword) {
      case 'movie-detail':
        this.ytsClient
          .getMovie(parsedData.data)
          .then(result => {
            const movie = result.movie;
            const movieImageMessage = messages.imageMessage(
              movie.large_cover_image,
              movie.medium_cover_image
            );
            const movieDetailTextMessage = messages.textMessage(
              mustache.render(MOVIE_DETAIL_TEMPLATE, {
                title_long: movie.title_long,
                casts: (movie.cast || []).map(cast => cast.name).join(', '),
                rating: movie.rating,
                description_full: movie.description_full,
                genre: (movie.genres || []).join(', ')
              })
            );
            const watchActions = movie.torrents.slice(0, 3).map(torr => {
              return messages.actionPostbackTemplate(
                `Watch in ${torr.quality}`,
                qs.stringify({
                  keyword: 'watchlink',
                  movie: torr.url,
                  image: movie.large_cover_image,
                  title: `${movie.title} (${torr.quality})`
                })
              );
            });

            const similarAction = messages.actionPostbackTemplate(
              'Simillar',
              qs.stringify({
                keyword: 'suggestion',
                data: movie.id,
              })
            );
            watchActions.push(similarAction);

            const buttonTemplate = messages.buttonTemplate(
              undefined,
              movie.title,
              mustache.render(MOVIE_MORE_TEXT, {
                year: movie.year,
                duration: movie.runtime,
                mpa_rating: movie.mpa_rating,
              }),
              watchActions
            );

            const buttonMessage = messages.templateMessage(
              `${movie.title} options`,
              buttonTemplate
            );
            this.lineClient
              .replyMessage(replyToken, [
                movieImageMessage,
                movieDetailTextMessage,
                buttonMessage,
              ])
              .catch(handleError);
          })
          .catch(handleError);
        break;

      case 'suggestion':
        this.ytsClient.getSuggestions(parsedData.data).then(result => {
          return this.sendMovieList(replyToken, result);
        });
        break;

        case 'watchlink':
        this.firebaseClient.addWatchSession(source.userId, parsedData)
          .then(result => {
            this.firebaseClient.getFirestore().doc(`/session/${result.id}`)
              .onSnapshot(doc => {
                if (doc.data().status === 'ready') {
                  const sessionData = doc.data();
                  this.lineClient.pushMessage(sessionData.userId, messages.textMessage(`Watch ${parsedData.title} here ${result.url}`));
                };
              });
          });
          this.lineClient.replyMessage(replyToken, messages.textMessage(`Preparing ${parsedData.title}... We will notify you once the movie is ready`));
        break;
      default:
        break;
    }
  }
}

module.exports = Handler;
<|endoftext|>


<commit_before>const _ = require('lodash');
const qs = require('query-string');
const mustache = require('mustache');
const messages = require('./messages');

const handleError = err => {
  console.error(JSON.stringify(err.originalError.response.data));
};

const MOVIE_DETAIL_TEMPLATE = `{{title_long}}
Casts : {{casts}}
Rating : {{rating}}
Genre: {{genre}}

{{{description_full}}}
`;

const MOVIE_MORE_TEXT = `{{year}}
Duration : {{duration}} minutes
{{mpa_rating}}`;

class Handler {
  constructor(lineClient, ytsClient, firebaseClient, osClient) {
    this.lineClient = lineClient;
    this.ytsClient = ytsClient;
    this.firebaseClient = firebaseClient;
    this.osClient = osClient;
  }

  handleRequest(payload) {
    payload.events.forEach(event => {
      this.handleEvent(event);
    });
  }

  handleEvent(event) {
    const type = event.type;
    switch (type) {
      case 'message':
        this.handleMessageEvent(event);
        break;

      case 'postback':
        this.handlePostbackEvent(event);
        break;

      case 'follow':
        this.handleFollowEvent(event);
        break;

      default:
        console.log(`Unknown type ${type}`);
        break;
    }
  }

  handleFollowEvent(event) {
    this.lineClient.getProfile(event.source.userId)
      .then(profile=> {
        this.firebaseClient.addUser(profile);
        const greetingMessage = `Hello ${profile.displayName}! My name is YTS Bot. Chat bot to help you search and stream HD movies directly on your chat app.
Try typing "search <your movie title>" to getting started.

Happy watching!`
        this.lineClient.replyMessage(event.replyToken, messages.textMessage(greetingMessage));
      });
  }

  handleMessageEvent(event) {
    const type = event.message.type;
    switch (type) {
      case 'text':
        this.handleTextMessage(
          event.replyToken,
          event.source,
          event.message.text
        );
        break;

      default:
        console.log(`unhandled message type ${type}`);
        break;
    }
  }

  sendMovieList(replyToken, result) {
    if (result.movie_count == 0) {
      return this.lineClient
        .replyMessage(
          replyToken,
          messages.textMessage(`Empty result from database`)
        )
        .catch(handleError);
    }

    const carouselMessage = result.movies.map(movie => {
      const actions = [
        messages.actionUriTemplate(
          'Watch Trailer',
          `https://www.youtube.com/watch?v=${movie.yt_trailer_code}`
        ),
        messages.actionPostbackTemplate(
          'Details',
          qs.stringify({
            keyword: 'movie-detail',
            data: movie.id,
          })
        ),
        messages.actionPostbackTemplate(
          'Simillar',
          qs.stringify({
            keyword: 'suggestion',
            data: movie.id,
          })
        )
      ];
      return messages.carouselColumnTemplate(
        movie.large_cover_image,
        movie.title,
        _.truncate(movie.summary, { length: 50, separator: /\W/ }),
        actions
      );
    });

    const message = messages.templateMessage(
      `Search result`,
      messages.carouselTemplate(carouselMessage)
    );
    return this.lineClient.replyMessage(replyToken, message).catch(handleError);
  }

  handleTextMessage(replyToken, source, text) {
    const token = text.split(' ');
    const keyword = token[0].toLowerCase();
    const term = token.slice(1, token.length).join(' ');
    switch (keyword) {
      case 'search':
        this.ytsClient
          .searchMovie(term)
          .then(result => {
            return this.sendMovieList(replyToken, result);
          })
          .catch(handleError);
        break;
      default:
        console.log(`unknown keyword ${keyword}`);
        break;
    }
  }

  handlePostbackEvent(event) {
    const postbackData = event.postback.data;
    this.handlePostback(event.replyToken, event.source, postbackData);
  }

  handlePostback(replyToken, source, data) {
    const parsedData = qs.parse(data);
    switch (parsedData.keyword) {
      case 'movie-detail':
        this.ytsClient
          .getMovie(parsedData.data)
          .then(result => {
            const movie = result.movie;
            const movieImageMessage = messages.imageMessage(
              movie.large_cover_image,
              movie.medium_cover_image
            );
            const movieDetailTextMessage = messages.textMessage(
              mustache.render(MOVIE_DETAIL_TEMPLATE, {
                title_long: movie.title_long,
                casts: (movie.cast || []).map(cast => cast.name).join(', '),
                rating: movie.rating,
                description_full: movie.description_full,
                genre: (movie.genres || []).join(', ')
              })
            );
            const watchActions = movie.torrents.slice(0, 3).map(torr => {
              return messages.actionPostbackTemplate(
                `Watch in ${torr.quality}`,
                qs.stringify({
                  keyword: 'watchlink',
                  movie: torr.url,
                  image: movie.large_cover_image,
                  title: movie.title,
                  qty: torr.quality,
                  imdb: movie.imdb_code,
                  size: torr.size_bytes,
                })
              );
            });

            const buttonTemplate = messages.buttonTemplate(
              undefined,
              movie.title,
              mustache.render(MOVIE_MORE_TEXT, {
                year: movie.year,
                duration: movie.runtime,
                mpa_rating: movie.mpa_rating,
              }),
              watchActions
            );

            const buttonMessage = messages.templateMessage(
              `${movie.title} options`,
              buttonTemplate
            );
            this.lineClient
              .replyMessage(replyToken, [
                movieImageMessage,
                movieDetailTextMessage,
                buttonMessage,
              ])
              .catch(handleError);
          })
          .catch(handleError);
        break;

      case 'suggestion':
        this.ytsClient.getSuggestions(parsedData.data).then(result => {
          return this.sendMovieList(replyToken, result);
        });
        break;

        case 'watchlink':
        this.firebaseClient.addWatchSession(source.userId, parsedData)
          .then(result => {
            const unsubscribe = this.firebaseClient.getFirestore().doc(`/session/${result.id}`)
            .onSnapshot(doc => {
              if (doc.data().status === 'ready') {
                const sessionData = doc.data();
                unsubscribe();
                this.osClient.getSubsLink(result.id, {
                  imdbid: parsedData.imdb,
                  filesize: parsedData.size
                }).then(() => {
                  this.lineClient.pushMessage(sessionData.userId, messages.textMessage(`Watch ${parsedData.title} (${parsedData.qty}) here
${result.url}`))
                });
              };
            });
          });
          this.lineClient.replyMessage(replyToken, messages.textMessage(`Preparing ${parsedData.title} (${parsedData.qty})...
We will notify you once the movie is ready`));
        break;
      default:
        break;
    }
  }
}

module.exports = Handler;
<commit_msg>Change details to watch now<commit_after>const _ = require('lodash');
const qs = require('query-string');
const mustache = require('mustache');
const messages = require('./messages');

const handleError = err => {
  console.error(JSON.stringify(err.originalError.response.data));
};

const MOVIE_DETAIL_TEMPLATE = `{{title_long}}
Casts : {{casts}}
Rating : {{rating}}
Genre: {{genre}}

{{{description_full}}}
`;

const MOVIE_MORE_TEXT = `{{year}}
Duration : {{duration}} minutes
{{mpa_rating}}`;

class Handler {
  constructor(lineClient, ytsClient, firebaseClient, osClient) {
    this.lineClient = lineClient;
    this.ytsClient = ytsClient;
    this.firebaseClient = firebaseClient;
    this.osClient = osClient;
  }

  handleRequest(payload) {
    payload.events.forEach(event => {
      this.handleEvent(event);
    });
  }

  handleEvent(event) {
    const type = event.type;
    switch (type) {
      case 'message':
        this.handleMessageEvent(event);
        break;

      case 'postback':
        this.handlePostbackEvent(event);
        break;

      case 'follow':
        this.handleFollowEvent(event);
        break;

      default:
        console.log(`Unknown type ${type}`);
        break;
    }
  }

  handleFollowEvent(event) {
    this.lineClient.getProfile(event.source.userId)
      .then(profile=> {
        this.firebaseClient.addUser(profile);
        const greetingMessage = `Hello ${profile.displayName}! My name is YTS Bot. Chat bot to help you search and stream HD movies directly on your chat app.
Try typing "search <your movie title>" to getting started.

Happy watching!`
        this.lineClient.replyMessage(event.replyToken, messages.textMessage(greetingMessage));
      });
  }

  handleMessageEvent(event) {
    const type = event.message.type;
    switch (type) {
      case 'text':
        this.handleTextMessage(
          event.replyToken,
          event.source,
          event.message.text
        );
        break;

      default:
        console.log(`unhandled message type ${type}`);
        break;
    }
  }

  sendMovieList(replyToken, result) {
    if (result.movie_count == 0) {
      return this.lineClient
        .replyMessage(
          replyToken,
          messages.textMessage(`Empty result from database`)
        )
        .catch(handleError);
    }

    const carouselMessage = result.movies.map(movie => {
      const actions = [
        messages.actionUriTemplate(
          'Watch Trailer',
          `https://www.youtube.com/watch?v=${movie.yt_trailer_code}`
        ),
        messages.actionPostbackTemplate(
          'Watch Now',
          qs.stringify({
            keyword: 'movie-detail',
            data: movie.id,
          })
        ),
        messages.actionPostbackTemplate(
          'Simillar',
          qs.stringify({
            keyword: 'suggestion',
            data: movie.id,
          })
        )
      ];
      return messages.carouselColumnTemplate(
        movie.large_cover_image,
        movie.title,
        _.truncate(movie.summary, { length: 50, separator: /\W/ }),
        actions
      );
    });

    const message = messages.templateMessage(
      `Search result`,
      messages.carouselTemplate(carouselMessage)
    );
    return this.lineClient.replyMessage(replyToken, message).catch(handleError);
  }

  handleTextMessage(replyToken, source, text) {
    const token = text.split(' ');
    const keyword = token[0].toLowerCase();
    const term = token.slice(1, token.length).join(' ');
    switch (keyword) {
      case 'search':
        this.ytsClient
          .searchMovie(term)
          .then(result => {
            return this.sendMovieList(replyToken, result);
          })
          .catch(handleError);
        break;
      default:
        console.log(`unknown keyword ${keyword}`);
        break;
    }
  }

  handlePostbackEvent(event) {
    const postbackData = event.postback.data;
    this.handlePostback(event.replyToken, event.source, postbackData);
  }

  handlePostback(replyToken, source, data) {
    const parsedData = qs.parse(data);
    switch (parsedData.keyword) {
      case 'movie-detail':
        this.ytsClient
          .getMovie(parsedData.data)
          .then(result => {
            const movie = result.movie;
            const movieImageMessage = messages.imageMessage(
              movie.large_cover_image,
              movie.medium_cover_image
            );
            const movieDetailTextMessage = messages.textMessage(
              mustache.render(MOVIE_DETAIL_TEMPLATE, {
                title_long: movie.title_long,
                casts: (movie.cast || []).map(cast => cast.name).join(', '),
                rating: movie.rating,
                description_full: movie.description_full,
                genre: (movie.genres || []).join(', ')
              })
            );
            const watchActions = movie.torrents.slice(0, 3).map(torr => {
              return messages.actionPostbackTemplate(
                `Watch in ${torr.quality}`,
                qs.stringify({
                  keyword: 'watchlink',
                  movie: torr.url,
                  image: movie.large_cover_image,
                  title: movie.title,
                  qty: torr.quality,
                  imdb: movie.imdb_code,
                  size: torr.size_bytes,
                })
              );
            });

            const buttonTemplate = messages.buttonTemplate(
              undefined,
              movie.title,
              mustache.render(MOVIE_MORE_TEXT, {
                year: movie.year,
                duration: movie.runtime,
                mpa_rating: movie.mpa_rating,
              }),
              watchActions
            );

            const buttonMessage = messages.templateMessage(
              `${movie.title} options`,
              buttonTemplate
            );
            this.lineClient
              .replyMessage(replyToken, [
                movieImageMessage,
                movieDetailTextMessage,
                buttonMessage,
              ])
              .catch(handleError);
          })
          .catch(handleError);
        break;

      case 'suggestion':
        this.ytsClient.getSuggestions(parsedData.data).then(result => {
          return this.sendMovieList(replyToken, result);
        });
        break;

        case 'watchlink':
        this.firebaseClient.addWatchSession(source.userId, parsedData)
          .then(result => {
            const unsubscribe = this.firebaseClient.getFirestore().doc(`/session/${result.id}`)
            .onSnapshot(doc => {
              if (doc.data().status === 'ready') {
                const sessionData = doc.data();
                unsubscribe();
                this.osClient.getSubsLink(result.id, {
                  imdbid: parsedData.imdb,
                  filesize: parsedData.size
                }).then(() => {
                  this.lineClient.pushMessage(sessionData.userId, messages.textMessage(`Watch ${parsedData.title} (${parsedData.qty}) here
${result.url}`))
                });
              };
            });
          });
          this.lineClient.replyMessage(replyToken, messages.textMessage(`Preparing ${parsedData.title} (${parsedData.qty})...
We will notify you once the movie is ready`));
        break;
      default:
        break;
    }
  }
}

module.exports = Handler;
<|endoftext|>


<commit_before>module.exports = (function(){
	return function paramsForUrl(query){
		 var paramInfo = {}
		 var queryContainsParams = (query.indexOf("?") !== -1) ? true : false
		if (queryContainsParams) {
		  var paramList = query.split("?")[1].split("&")
		  paramList.forEach(function(paramPair) {
		    var keyValue = paramPair.split("=")
		    paramInfo[keyValue.shift()] = keyValue.shift()
		  })
		}
	  return paramInfo
	}
})()<commit_msg>Condense the urlParams helper method by introducing the build-in querystring module.<commit_after>var qs = require('querystring')

module.exports = function (query){
	// Ex. Input: "/users/1/media?lat":"37.819877","lng":"-122.47894"
	if (query.indexOf("?") === -1) { return {} }
	return qs.parse(query.split("?")[1])
}
<|endoftext|>


<commit_before>var byline = require("byline");
var _ = require("lodash");
var log = require("../lib/log");
var events = require("events");
var needle = require("needle");

module.exports = function(stream, source)
{
    var emitter = new events.EventEmitter();

    stream.pipe(byline.createStream())
    .on("data", function(line)
    {
        var parts = line.toString().split("|"),
            infoHash = parts[0], cat = (parts[2] || "").toLowerCase(),
            additional = { }; // additional info found

        var infoUrl = parts[3];
        if (infoUrl.match("kat.cr|kickass.to")) additional.hints = { url: infoUrl }; // OR any torrent website that we know contains IMDB ID on it's info page

        if (cat.match("porn") || cat.match("adult")) return; // exclude that for now

        //if (cat.match("video") && (cat.match("dvd") || cat.match("rip") || cat.match("tv")))
        if (cat.match("movie") || cat.match("dvd") || cat.match("rip") || cat.match("tv")/* || cat.match("video")*/) {
            additional.category = cat;
            hashReady(infoHash, _.extend(additional, source.addon || { }));
        };
    })
    .on("error", function(err) { log.error("dump", err) })
    .on("end", function() { emitter.emit("end") });

    var checkingMinSeeders = source.minSeedersUrl && source.minSeeders;
    if (checkingMinSeeders) {
        var seedersStream = require("../lib/importer").getStream({ url: source.minSeedersUrl });

    };

    function hashReady(hash, extra) {
        emitter.emit("infoHash", hash, extra);
    };

    return emitter;
}
<commit_msg>Dump: check how many seeders we have from a tracker dump, e.g. bitsnoop<commit_after>var byline = require("byline");
var _ = require("lodash");
var log = require("../lib/log");
var events = require("events");
var needle = require("needle");

module.exports = function(stream, source)
{
    var emitter = new events.EventEmitter();
    var i = 0, j = 0; // i - how many streams, j - how many completed

    stream.pipe(byline.createStream())
    .on("data", function(line)
    {
        var parts = line.toString().split("|"),
            infoHash = parts[0].toLowerCase(), cat = (parts[2] || "").toLowerCase(),
            additional = { }; // additional info found

        var infoUrl = parts[3];
        if (infoUrl.match("kat.cr|kickass.to")) additional.hints = { url: infoUrl }; // OR any torrent website that we know contains IMDB ID on it's info page

        if (cat.match("porn") || cat.match("adult")) return; // exclude that for now

        //if (cat.match("video") && (cat.match("dvd") || cat.match("rip") || cat.match("tv")))
        if (cat.match("movie") || cat.match("dvd") || cat.match("rip") || cat.match("tv")/* || cat.match("video")*/) {
            additional.category = cat;
            hashReady(infoHash, _.extend(additional, source.addon || { }));
        };
    })
    .on("error", function(err) { log.error("dump", err) })
    .on("end", checkEnded());

    var checkingSeeders = source.minSeedersUrl && source.minSeeders;
    if (checkingSeeders) {
        require("../lib/importer").getStream({ url: source.minSeedersUrl })
        .pipe(byline.createStream())
        .on("data", function(line) {
            var parts = line.toString().split("|");
            var infoHash = parts[0].toLowerCase(), uploaders = parseInt(parts[1]), downloaders = parseInt(parts[2]);
            if (uploaders >= source.minSeeders) hashReady(infoHash, { uploaders: uploaders, downloaders: downloaders });
        })
        .on("end", checkEnded())
    };

    // TODO: make sure this is cleaned up
    var hashes = { };
    function hashReady(hash, extra) {
        if (!checkingSeeders) return emitter.emit("infoHash", hash, extra);

        hashes[hash] = hashes[hash] || { hit: 0 };
        hashes[hash].hit++;
        _.extend(hashes[hash], extra || { });
        if (hashes[hash].hit == 2) emitter.emit("infoHash", hash, extra);
    };

    function checkEnded() {
        i++;
        return function() { if (++j == i) emitter.emit("end") };
    };

    return emitter;
}
<|endoftext|>


<commit_before>
/*
* An ordered collection backed by an Array.
* List has access to all Sequence and Iterable methods.
* List can be seen as a richer Array.
*/
var List = createType('List', Sequence);

List.fromArray = function(array) {
	return List.apply(null, array);
};

List.prototype._init = function() {
	this.items = cloneArray(arguments);
};

/*
* Appends the item at the last position of this list.
*/
List.prototype.add = function(item) {
	this.items.push(item);
	return this;
};

/*
* Adds the item at a specific index.
*/
List.prototype.addAt = function(item, index) {
	this._assertRange(index);
	this.items.splice(index, 0, item);
	return this;
};

/*
* Replaces the item at the given index with a new value.
*/
List.prototype.update = function(index, item) {
	this._assertRange(index);
	var previousItem = this.items[index];
	if (previousItem !== item) {
		this.items[index] = item;
	}
	return this;
};

/*
* Inserts an item in this sorted list using binary search according
* to the sortFunction that was used to sort the list
* or that matches the current item ordering.
*/
List.prototype.insert = function(item, sortFunction) {
	sortFunction = sortFunction || this._defaultSortFunction;
	var low = 0, high = this.size();
	while (low < high) {
   	var mid = (low + high) >> 1;
      sortFunction(item, this.items[mid]) > 0
      	? low = mid + 1
      	: high = mid;
   }
   this.addAt(item, low);
   return this;
};

/*
* Removes the item from this list.
*/
List.prototype.remove = function(item) {
	var index = this.indexOf(item);
	if (index > -1) {
		return this.items.splice(index, 1)[0];
	}
	return false;
};

/*
* Removes and returns the item located at the specified index.
*/
List.prototype.removeAt = function(index) {
	var item = this.items.splice(index, 1)[0];
	return item;
};

/*
* Removes the first item from this list.
* This is a mutating equivalent of Iterable's drop(1).
*/
List.prototype.removeFirst = function() {
	this._assertNotEmpty('removeFirst');
	return this.removeAt(0);
};

/*
* Removes the last item from this list.
* This is a mutating equivalent of Iterable's dropRight(1).
*/
List.prototype.removeLast = function() {
	this._assertNotEmpty('removeLast');
	return this.removeAt(this.items.length - 1);
};

/*
* Removes all items from this list.
*/
List.prototype.removeAll = function() {
	var size = this.size();
	if (size > 0) {
		this.items.splice(0, size);
	}
	return this;
};

/*
* Removes all items satisfying a predicate from this list.
* Returns the List of removed items.
* This is a mutating, reversed equivalent of Iterable's filter.
*/
List.prototype.removeIf = function(predicate) {
	var removed = [];
	for (var i=0, length=this.items.length; i < length; i++) {
		if (predicate(this.items[i])) {
			removed.push(this.items[i]);
			this.items.splice(i, 1);
			i--;
		}
	}
	return List.fromArray(removed);
};

/*
* Sorts this list by using a sort function.
* The signature for the sort function is the same as for Arrays'.
*/
List.prototype.sort = function(sortFunction) {
	this.items.sort(sortFunction);
	return this;
};

/*
* Sorts this list by comparing the items transformed by an extractor function.
* The extractor function would typically return a property of each item or compute a value.
*/
List.prototype.sortBy = function(extractor) {
	var self = this;
	this.items.sort(function(a, b) {
		var A = extractor(a), B = extractor(b);
		return (A < B) ? -1 : (A > B) ? +1 : 0;
	});
	return this;
};

/*
* Converts this list to a Set.
*/
List.prototype.toSet = function() {
	return Set.fromArray(this.items);
};

List.prototype._defaultSortFunction = function(a, b) {
	return (a < b) ? -1 : (a > b) ? 1 : 0;
};

List.prototype._assertRange = function(index) {
	if (index < 0 || index > this.size()) {
		throw new Error('Illegal insertion at index ' + index + ' in List with size ' + (this.size() - 1));
	}
};

/*
* Returns a list of integers from start to stop (inclusive),
* incremented or decremented by step.
*
* You can also use the shortcut range(n) which returns the list
* of the n first integers, starting from 0.
*/
var range = function(start, stop, step) {
   if (arguments.length == 1) {
   	stop = arguments[0] - 1;
   	start = 0;
   }
   step = step || 1;

   var items = [];
   var next = start;
   var increasing = (step > 0);

   while ((increasing && next <= stop) || (!increasing && next >= stop)) {
      items.push(next);
      next = next + step;
   }

   var range = List.fromArray(items);

   // Override Sequence's contains with a O(1) alternative
   // when using 'continuous' int ranges.
   if (step == 1)
   	range.contains = function(item) {
   		return isNumber(item) && item >= start && item <= stop;
   	};

   return range;
};

Collection.range = range;
Collection.List = List;<commit_msg>Formatting<commit_after>
/*
* An ordered collection backed by an Array.
* List has access to all Sequence and Iterable methods.
* List can be seen as a richer Array.
*/
var List = createType('List', Sequence);

List.fromArray = function(array) {
	return List.apply(null, array);
};

List.prototype._init = function() {
	this.items = cloneArray(arguments);
};

/*
* Appends the item at the last position of this list.
*/
List.prototype.add = function(item) {
	this.items.push(item);
	return this;
};

/*
* Adds the item at a specific index.
*/
List.prototype.addAt = function(item, index) {
	this._assertRange(index);
	this.items.splice(index, 0, item);
	return this;
};

/*
* Replaces the item at the given index with a new value.
*/
List.prototype.update = function(index, item) {
	this._assertRange(index);
	var previousItem = this.items[index];
	if (previousItem !== item) {
		this.items[index] = item;
	}
	return this;
};

/*
* Inserts an item in this sorted list using binary search according
* to the sortFunction that was used to sort the list
* or that matches the current item ordering.
*/
List.prototype.insert = function(item, sortFunction) {
	sortFunction = sortFunction || this._defaultSortFunction;
	var low = 0, high = this.size();
	while (low < high) {
   	var mid = (low + high) >> 1;
      sortFunction(item, this.items[mid]) > 0
      	? low = mid + 1
      	: high = mid;
   }
   this.addAt(item, low);
   return this;
};

/*
* Removes the item from this list.
*/
List.prototype.remove = function(item) {
	var index = this.indexOf(item);
	if (index > -1) {
		return this.items.splice(index, 1)[0];
	}
	return false;
};

/*
* Removes and returns the item located at the specified index.
*/
List.prototype.removeAt = function(index) {
	var item = this.items.splice(index, 1)[0];
	return item;
};

/*
* Removes the first item from this list.
* This is a mutating equivalent of Iterable's drop(1).
*/
List.prototype.removeFirst = function() {
	this._assertNotEmpty('removeFirst');
	return this.removeAt(0);
};

/*
* Removes the last item from this list.
* This is a mutating equivalent of Iterable's dropRight(1).
*/
List.prototype.removeLast = function() {
	this._assertNotEmpty('removeLast');
	return this.removeAt(this.items.length - 1);
};

/*
* Removes all items from this list.
*/
List.prototype.removeAll = function() {
	var size = this.size();
	if (size > 0) {
		this.items.splice(0, size);
	}
	return this;
};

/*
* Removes all items satisfying a predicate from this list.
* Returns the List of removed items.
* This is a mutating, reversed equivalent of Iterable's filter.
*/
List.prototype.removeIf = function(predicate) {
	var removed = [];
	for (var i=0, length=this.items.length; i < length; i++) {
		if (predicate(this.items[i])) {
			removed.push(this.items[i]);
			this.items.splice(i, 1);
			i--;
		}
	}
	return List.fromArray(removed);
};

/*
* Sorts this list by using a sort function.
* The signature for the sort function is the same as for Arrays'.
*/
List.prototype.sort = function(sortFunction) {
	this.items.sort(sortFunction);
	return this;
};

/*
* Sorts this list by comparing the items transformed by an extractor function.
* The extractor function would typically return a property of each item or compute a value.
*/
List.prototype.sortBy = function(extractor) {
	var self = this;
	this.items.sort(function(a, b) {
		var A = extractor(a), B = extractor(b);
		return (A < B) ? -1 : (A > B) ? +1 : 0;
	});
	return this;
};

/*
* Converts this list to a Set.
*/
List.prototype.toSet = function() {
	return Set.fromArray(this.items);
};

List.prototype._defaultSortFunction = function(a, b) {
	return (a < b) ? -1 : (a > b) ? 1 : 0;
};

List.prototype._assertRange = function(index) {
	if (index < 0 || index > this.size()) {
		throw new Error('Illegal insertion at index ' + index + ' in List with size ' + (this.size() - 1));
	}
};

/*
* Returns a list of integers from start to stop (inclusive),
* incremented or decremented by step.
*
* You can also use the shortcut range(n) which returns the list
* of the n first integers, starting from 0.
*/
var range = function(start, stop, step) {
   if (arguments.length == 1) {
   	stop = arguments[0] - 1;
   	start = 0;
   }
   step = step || 1;

   var items = [];
   var next = start;
   var increasing = (step > 0);

   while ((increasing && next <= stop) || (!increasing && next >= stop)) {
      items.push(next);
      next = next + step;
   }

   var range = List.fromArray(items);

   // Override Sequence's contains with a O(1) alternative
   // when using 'continuous' int ranges.
   if (step == 1)
       range.contains = function(item) {
	       return isNumber(item) && item >= start && item <= stop;
       };

   return range;
};

Collection.range = range;
Collection.List = List;<|endoftext|>