<commit_before>'use strict';

const
  request = require('request'),
  standardUpdates = require('./updates.js'),
  standardMethods = require('./methods.js');

/* Telegram Bot */

class TeleBot {

  constructor(cfg) {

    if (typeof cfg != 'object') cfg = { token: cfg };

    if (!cfg.token || cfg.token.split(':').length != 2) {
      throw Error('[bot.error] invalid bot token');
    }

    this.cfg = cfg;
    this.token = cfg.token;
    this.id = this.token.split(':')[0];
    this.api = `https://api.telegram.org/bot${ this.token }`;
    this.fileLink = `https://api.telegram.org/file/bot${ this.token }/`;

    this.limit = Number(cfg.limit) || 100;
    this.sleep = Number(cfg.sleep) || 1000;
    this.timeout = cfg.timeout >= 0 ? cfg.timeout : 0;
    this.retryTimeout = cfg.retryTimeout >= 0 ? cfg.retryTimeout : 5000;

    this.updateId = 0;
    this.loopFn = null;

    this.flags = {
      pool: true,
      retry: false,
      looping: false
    };

    this.modList = {};
    this.eventList = {};

    this.updateTypes = standardUpdates;

    this.processUpdate = (update, props) => {
      for (let name in this.updateTypes) {
        if (name in update) {
          update = update[name];
          return this.updateTypes[name].call(this, update, props);
        }
      }
    };

  }

  /* Modules */

  use(fn) {
    return fn.call(this, this, this.cfg.modules);
  }

  /* Connection */

  connect() {

    const f = this.flags;
    f.looping = true;

    console.log('[bot.info] bot started');
    this.event('connect');

    // Global loop function
    this.loopFn = setInterval(x => {

      // Stop on false looping flag
      if (!f.looping) clearInterval(this.loopFn);

      // Skip processing on false pool flag
      if (!f.pool) return;

      f.pool = false;

      // Get updates
      this.getUpdates().then(x => {

        // Retry connecting
        if (f.retry) {

          const now = Date.now();
          const diff = (now - f.retry) / 1000;

          console.log(`[bot.info.update] reconnected after ${ diff } seconds`);
          this.event('reconnected', {
            startTime: f.retry, endTime: now, diffTime: diff
          });

          f.retry = false;

        }

        // Tick
        return this.event('tick');

      }).then(x => {

        // Seems okay for the next pool
        f.pool = true;

      }).catch(error => {

        // Set retry flag as current date (for timeout calculations)
        if (f.retry === false) f.retry = Date.now();

        console.error(`[bot.error.update]`, error.stack || error);
        this.event(['error', 'error.update'], { error });

        return Promise.reject();

      }).catch(x => {

        const seconds = this.retryTimeout / 1000;
        console.log(`[bot.info.update] reconnecting in ${ seconds } seconds...`);
        this.event('reconnecting');

        // Set reconnecting timeout
        setTimeout(x => (f.pool = true), this.retryTimeout);

      });

    }, this.sleep);

  }

  /* Stop looping */

  disconnect(message) {
    this.flags.looping = false;
    console.log(`[bot.info] bot disconnected ${ message ? ': ' + message : '' }`);
    this.event('disconnect', message);
  }

  /* Fetch updates */

  getUpdates(offset=this.updateId, limit=this.limit, timeout=this.timeout) {

    // Request updates from Telegram server
    return this.request('/getUpdates', {
      offset, limit, timeout
    }).then(body =>
      this.receiveUpdates(body)
    );

  }

  /* Recive updates */

  receiveUpdates(body) {

    // Globals
    var
      mod,
      props = {},
      updateList = body.result,
      promise = Promise.resolve();

    // No updates
    if (!updateList.length) return promise;

    // We have updates
    return this.event('update', updateList).then(eventProps => {

      // Run update list modifiers
      mod = this.modRun('updateList', {
        list: updateList,
        props: extendProps(props, eventProps)
      });

      updateList = mod.list;
      props = mod.props;

      // Every Telegram update
      for (let update of updateList) {

        // Update ID
        const nextId = ++update.update_id;
        if (this.updateId < nextId) this.updateId = nextId;

        // Run update modifiers
        mod = this.modRun('update', { update, props });

        update = mod.update;
        props = mod.props;

        // Process update
        promise = promise.then(x => this.processUpdate(update, props));

      }

      return promise;

    }).catch(error => {

      console.log('[bot.error]', error.stack || error);
      this.event('error', { error });

      // Don't trigger server reconnect
      return Promise.resolve();

    });

  }

  /* Send request to server */

  request(url, form, data) {
    const options = { url: this.api + url, json: true };
    if (form) { options.form = form; } else { options.formData = data; };
    return new Promise((resolve, reject) => {
      request.post(options, (error, response, body) => {
        if (error || !body.ok || response.statusCode == 404) {
          return reject(error || body || 404);
        }
        return resolve(body);
      });
    });
  }

  /* Modifications */

  mod(names, fn) {
    if (typeof names == 'string') names = [names];
    const mods = this.modList;
    for (let name of names) {
      if (!mods[name]) mods[name] = [];
      if (mods[name].includes(fn)) return;
      mods[name].push(fn);
    }
    return fn;
  }

  modRun(name, data) {
    const list = this.modList[name];
    if (!list || !list.length) return data;
    for (let fn of list) data = fn.call(this, data);
    return data;
  }

  removeMod(name, fn) {
    let list = this.modList[name];
    if (!list) return false;
    let index = list.indexOf(fn);
    if (index == -1) return false;
    list.splice(index, 1);
    return true;
  }

  /* Events */

  on(types, fn, opt) {
    if (!opt) opt = {};
    if (typeof types == 'string') types = [types];
    for (let type of types) {
      let event = this.eventList[type];
      if (!event) {
        this.eventList[type] = { fired: null, list: [fn] };
      } else {
        if (event.list.includes(fn)) continue;
        event.list.push(fn);
        if (opt.fired && event.fired) {
          let fired = event.fired;
          new Promise((resolve, reject) => {
            let output = fn.call(fired.self, fired.data, fired.details);
            if (output instanceof Promise) output.then(resolve).catch(reject);
            else resolve(output);
          }).catch(error => {
            eventPromiseError.call(this, type, fired, error);
          });
          if (opt.cleanFired) this.eventList[type].fired = null;
        }
      }
    }
  }

  event(types, data, self) {
    let promises = [];
    if (typeof types == 'string') types = [types];
    for (let type of types) {
      let event = this.eventList[type];
      let details = { type, time: Date.now() };
      let fired = { self, data, details };
      if (!event) {
        this.eventList[type] = { fired, list: [] };
        continue;
      }
      event.fired = fired;
      event = event.list;
      for (let fn of event) {
        promises.push((new Promise((resolve, reject) => {
          let that = this;
          details.remove = (function(fn) {
            return x => {
              that.dev.info(`remove "${type}" event:`, fn);
              that.removeEvent(type, fn);
            }
          }(fn));
          fn = fn.call(self, data, details);
          if (fn instanceof Promise) {
            fn.then(resolve).catch(reject);
          } else {
            resolve(fn);
          }
        })).catch(error => {
          eventPromiseError.call(this, type, fired, error);
          return reject(error);
        }));
      }
    }
    return Promise.all(promises);
  }

  cleanEvent(type) {
    let events = this.eventList;
    if (!events.hasOwnProperty(type)) return false;
    events[type].fired = null;
    return true;
  }

  removeEvent(type, fn) {
    let events = this.eventList;
    if (!events.hasOwnProperty(type)) return false;
    let event = events[type].list;
    let index = event.indexOf(fn);
    if (index == -1) return false;
    event.splice(index, 1);
    return true;
  }

  destroyEvent(type) {
    let events = this.eventList;
    if (!events.hasOwnProperty(type)) return false;
    delete events[type];
    return true;
  }

  /* Process global properties */

  properties(form={}, opt={}) {

    // Reply to message
    if (opt.reply) form.reply_to_message_id = opt.reply;

    // Markdown/HTML support for message
    if (opt.parse) form.parse_mode = opt.parse;

    // User notification
    if (opt.notify === false) form.disable_notification = true;

    // Web preview
    if (opt.preview === false) form.disable_web_page_preview = true;

    // Markup object
    if (opt.markup !== undefined) {
      if (opt.markup == 'hide' || opt.markup === false) {
        // Hide keyboard
        form.reply_markup = JSON.stringify({ hide_keyboard: true });
      } else if (opt.markup == 'reply') {
        // Fore reply
        form.reply_markup = JSON.stringify({ force_reply: true });
      } else {
        // JSON keyboard
        form.reply_markup = opt.markup;
      }
    }

    return (this.modRun('property', { form, options: opt })).form;

  }

  /* Method adder */

  static addMethods(methods) {

    for (let id in methods) {

      const method = methods[id];

      // If method is a function
      if (typeof method == 'function') {
        this.prototype[id] = method;
        continue;
      }

      // Set method name
      const name = method.short || id;

      // Argument function
      let argFn = method.arguments;
      if (argFn && typeof argFn != 'function') {
        if (typeof argFn == 'string') argFn = [argFn];
        let args = argFn;
        argFn = function() {
          const form = {};
          args.forEach((v, i) => form[v] = arguments[i]);
          return form;
        };
      }

      // Options function
      let optFn = method.options;

      // Create method
      this.prototype[name] = function() {
        this.event(name, arguments);
        let form = {}, args = [].slice.call(arguments);
        let options = args[args.length - 1];
        if (typeof options != 'object') options = {};
        if (argFn) form = argFn.apply(this, args);
        if (optFn) options = optFn.apply(this, [].concat(form, options));
        form = this.properties(form, options);
        let request = this.request(`/${id}`, form);
        if (method.then) request = request.then(module.then);
        return request;
      };

    }

    console.log(`[bot.info] new methods: ${ Object.keys(methods).join(', ') }`);

  }
};

/* Add standard methods */

TeleBot.addMethods(standardMethods);

/* Functions */

function eventPromiseError(type, fired, error) {
  return new Promise((resolve, reject) => {
    console.error('[bot.error.event]', error.stack || error);
    if (type != 'error.event') {
      this.event('error.event', { error, data: fired.data })
        .then(resolve).catch(reject);
    } else {
      resolve();
    }
  });
}

function extendProps(props, input) {
  for (let obj of input) {
    for (let naprops in obj) {
      const key = props[naprops], value = obj[naprops];
      if (key !== undefined) {
        if (!Array.isArray(key)) props[naprops] = [key];
        props[naprops].push(value);
        continue;
      }
      props[naprops] = value;
    }
  }
  return props;
}

/* Exports */

module.exports = TeleBot;
<commit_msg>Self argument to event call<commit_after>'use strict';

const
  request = require('request'),
  standardUpdates = require('./updates.js'),
  standardMethods = require('./methods.js');

/* Telegram Bot */

class TeleBot {

  constructor(cfg) {

    if (typeof cfg != 'object') cfg = { token: cfg };

    if (!cfg.token || cfg.token.split(':').length != 2) {
      throw Error('[bot.error] invalid bot token');
    }

    this.cfg = cfg;
    this.token = cfg.token;
    this.id = this.token.split(':')[0];
    this.api = `https://api.telegram.org/bot${ this.token }`;
    this.fileLink = `https://api.telegram.org/file/bot${ this.token }/`;

    this.limit = Number(cfg.limit) || 100;
    this.sleep = Number(cfg.sleep) || 1000;
    this.timeout = cfg.timeout >= 0 ? cfg.timeout : 0;
    this.retryTimeout = cfg.retryTimeout >= 0 ? cfg.retryTimeout : 5000;

    this.updateId = 0;
    this.loopFn = null;

    this.flags = {
      pool: true,
      retry: false,
      looping: false
    };

    this.modList = {};
    this.eventList = {};

    this.updateTypes = standardUpdates;

    this.processUpdate = (update, props) => {
      for (let name in this.updateTypes) {
        if (name in update) {
          update = update[name];
          return this.updateTypes[name].call(this, update, props);
        }
      }
    };

  }

  /* Modules */

  use(fn) {
    return fn.call(this, this, this.cfg.modules);
  }

  /* Connection */

  connect() {

    const f = this.flags;
    f.looping = true;

    console.log('[bot.info] bot started');
    this.event('connect');

    // Global loop function
    this.loopFn = setInterval(x => {

      // Stop on false looping flag
      if (!f.looping) clearInterval(this.loopFn);

      // Skip processing on false pool flag
      if (!f.pool) return;

      f.pool = false;

      // Get updates
      this.getUpdates().then(x => {

        // Retry connecting
        if (f.retry) {

          const now = Date.now();
          const diff = (now - f.retry) / 1000;

          console.log(`[bot.info.update] reconnected after ${ diff } seconds`);
          this.event('reconnected', {
            startTime: f.retry, endTime: now, diffTime: diff
          });

          f.retry = false;

        }

        // Tick
        return this.event('tick');

      }).then(x => {

        // Seems okay for the next pool
        f.pool = true;

      }).catch(error => {

        // Set retry flag as current date (for timeout calculations)
        if (f.retry === false) f.retry = Date.now();

        console.error(`[bot.error.update]`, error.stack || error);
        this.event(['error', 'error.update'], { error });

        return Promise.reject();

      }).catch(x => {

        const seconds = this.retryTimeout / 1000;
        console.log(`[bot.info.update] reconnecting in ${ seconds } seconds...`);
        this.event('reconnecting');

        // Set reconnecting timeout
        setTimeout(x => (f.pool = true), this.retryTimeout);

      });

    }, this.sleep);

  }

  /* Stop looping */

  disconnect(message) {
    this.flags.looping = false;
    console.log(`[bot.info] bot disconnected ${ message ? ': ' + message : '' }`);
    this.event('disconnect', message);
  }

  /* Fetch updates */

  getUpdates(offset=this.updateId, limit=this.limit, timeout=this.timeout) {

    // Request updates from Telegram server
    return this.request('/getUpdates', {
      offset, limit, timeout
    }).then(body =>
      this.receiveUpdates(body)
    );

  }

  /* Recive updates */

  receiveUpdates(body) {

    // Globals
    var
      mod,
      props = {},
      updateList = body.result,
      promise = Promise.resolve();

    // No updates
    if (!updateList.length) return promise;

    // We have updates
    return this.event('update', updateList).then(eventProps => {

      // Run update list modifiers
      mod = this.modRun('updateList', {
        list: updateList,
        props: extendProps(props, eventProps)
      });

      updateList = mod.list;
      props = mod.props;

      // Every Telegram update
      for (let update of updateList) {

        // Update ID
        const nextId = ++update.update_id;
        if (this.updateId < nextId) this.updateId = nextId;

        // Run update modifiers
        mod = this.modRun('update', { update, props });

        update = mod.update;
        props = mod.props;

        // Process update
        promise = promise.then(x => this.processUpdate(update, props));

      }

      return promise;

    }).catch(error => {

      console.log('[bot.error]', error.stack || error);
      this.event('error', { error });

      // Don't trigger server reconnect
      return Promise.resolve();

    });

  }

  /* Send request to server */

  request(url, form, data) {
    const options = { url: this.api + url, json: true };
    if (form) { options.form = form; } else { options.formData = data; };
    return new Promise((resolve, reject) => {
      request.post(options, (error, response, body) => {
        if (error || !body.ok || response.statusCode == 404) {
          return reject(error || body || 404);
        }
        return resolve(body);
      });
    });
  }

  /* Modifications */

  mod(names, fn) {
    if (typeof names == 'string') names = [names];
    const mods = this.modList;
    for (let name of names) {
      if (!mods[name]) mods[name] = [];
      if (mods[name].includes(fn)) return;
      mods[name].push(fn);
    }
    return fn;
  }

  modRun(name, data) {
    const list = this.modList[name];
    if (!list || !list.length) return data;
    for (let fn of list) data = fn.call(this, data);
    return data;
  }

  removeMod(name, fn) {
    let list = this.modList[name];
    if (!list) return false;
    let index = list.indexOf(fn);
    if (index == -1) return false;
    list.splice(index, 1);
    return true;
  }

  /* Events */

  on(types, fn, opt) {
    if (!opt) opt = {};
    if (typeof types == 'string') types = [types];
    for (let type of types) {
      let event = this.eventList[type];
      if (!event) {
        this.eventList[type] = { fired: null, list: [fn] };
      } else {
        if (event.list.includes(fn)) continue;
        event.list.push(fn);
        if (opt.fired && event.fired) {
          let fired = event.fired;
          new Promise((resolve, reject) => {
            let output = fn.call(fired.self, fired.data, fired.self, fired.details);
            if (output instanceof Promise) output.then(resolve).catch(reject);
            else resolve(output);
          }).catch(error => {
            eventPromiseError.call(this, type, fired, error);
          });
          if (opt.cleanFired) this.eventList[type].fired = null;
        }
      }
    }
  }

  event(types, data, self) {
    let promises = [];
    if (typeof types == 'string') types = [types];
    for (let type of types) {
      let event = this.eventList[type];
      let details = { type, time: Date.now() };
      let fired = { self, data, details };
      if (!event) {
        this.eventList[type] = { fired, list: [] };
        continue;
      }
      event.fired = fired;
      event = event.list;
      for (let fn of event) {
        promises.push((new Promise((resolve, reject) => {
          let that = this;
          details.remove = (function(fn) {
            return x => {
              that.dev.info(`remove "${type}" event:`, fn);
              that.removeEvent(type, fn);
            }
          }(fn));
          fn = fn.call(self, data, self, details);
          if (fn instanceof Promise) {
            fn.then(resolve).catch(reject);
          } else {
            resolve(fn);
          }
        })).catch(error => {
          eventPromiseError.call(this, type, fired, error);
          return reject(error);
        }));
      }
    }
    return Promise.all(promises);
  }

  cleanEvent(type) {
    let events = this.eventList;
    if (!events.hasOwnProperty(type)) return false;
    events[type].fired = null;
    return true;
  }

  removeEvent(type, fn) {
    let events = this.eventList;
    if (!events.hasOwnProperty(type)) return false;
    let event = events[type].list;
    let index = event.indexOf(fn);
    if (index == -1) return false;
    event.splice(index, 1);
    return true;
  }

  destroyEvent(type) {
    let events = this.eventList;
    if (!events.hasOwnProperty(type)) return false;
    delete events[type];
    return true;
  }

  /* Process global properties */

  properties(form={}, opt={}) {

    // Reply to message
    if (opt.reply) form.reply_to_message_id = opt.reply;

    // Markdown/HTML support for message
    if (opt.parse) form.parse_mode = opt.parse;

    // User notification
    if (opt.notify === false) form.disable_notification = true;

    // Web preview
    if (opt.preview === false) form.disable_web_page_preview = true;

    // Markup object
    if (opt.markup !== undefined) {
      if (opt.markup == 'hide' || opt.markup === false) {
        // Hide keyboard
        form.reply_markup = JSON.stringify({ hide_keyboard: true });
      } else if (opt.markup == 'reply') {
        // Fore reply
        form.reply_markup = JSON.stringify({ force_reply: true });
      } else {
        // JSON keyboard
        form.reply_markup = opt.markup;
      }
    }

    return (this.modRun('property', { form, options: opt })).form;

  }

  /* Method adder */

  static addMethods(methods) {

    for (let id in methods) {

      const method = methods[id];

      // If method is a function
      if (typeof method == 'function') {
        this.prototype[id] = method;
        continue;
      }

      // Set method name
      const name = method.short || id;

      // Argument function
      let argFn = method.arguments;
      if (argFn && typeof argFn != 'function') {
        if (typeof argFn == 'string') argFn = [argFn];
        let args = argFn;
        argFn = function() {
          const form = {};
          args.forEach((v, i) => form[v] = arguments[i]);
          return form;
        };
      }

      // Options function
      let optFn = method.options;

      // Create method
      this.prototype[name] = function() {
        this.event(name, arguments);
        let form = {}, args = [].slice.call(arguments);
        let options = args[args.length - 1];
        if (typeof options != 'object') options = {};
        if (argFn) form = argFn.apply(this, args);
        if (optFn) options = optFn.apply(this, [].concat(form, options));
        form = this.properties(form, options);
        let request = this.request(`/${id}`, form);
        if (method.then) request = request.then(module.then);
        return request;
      };

    }

    console.log(`[bot.info] new methods: ${ Object.keys(methods).join(', ') }`);

  }
};

/* Add standard methods */

TeleBot.addMethods(standardMethods);

/* Functions */

function eventPromiseError(type, fired, error) {
  return new Promise((resolve, reject) => {
    console.error('[bot.error.event]', error.stack || error);
    if (type != 'error.event') {
      this.event('error.event', { error, data: fired.data })
        .then(resolve).catch(reject);
    } else {
      resolve();
    }
  });
}

function extendProps(props, input) {
  for (let obj of input) {
    for (let naprops in obj) {
      const key = props[naprops], value = obj[naprops];
      if (key !== undefined) {
        if (!Array.isArray(key)) props[naprops] = [key];
        props[naprops].push(value);
        continue;
      }
      props[naprops] = value;
    }
  }
  return props;
}

/* Exports */

module.exports = TeleBot;
<|endoftext|>


<commit_before>var system = require('system'),
    captureUrl = 'http://localhost:1111/capture';
if (system.args.length==2) {
    captureUrl = system.args[1];
}

phantom.silent = false;

var page = new WebPage();

page.open(captureUrl, function(status) {
  if(!phantom.silent) {
    //console.log(status);
    if (status !== 'success') {
      console.log('phantomjs failed to connect');
      phantom.exit(1);
    }

    page.onConsoleMessage = function (msg, line, id) {
      var fileName = id.split('/');
      // format the output message with filename, line number and message
      // weird gotcha: phantom only uses the first console.log argument it gets :(
      console.log(fileName[fileName.length-1]+', '+ line +': '+ msg);
    };

    page.onAlert = function(msg) {
      console.log(msg);
    };
  }
});<commit_msg>Handled error case with phantom runner<commit_after>var system = require('system'),
    captureUrl = 'http://localhost:1111/capture';
if (system.args.length==2) {
    captureUrl = system.args[1];
}

phantom.silent = false;

var page = new WebPage();

page.open(captureUrl, function(status) {
  if(!phantom.silent) {
    //console.log(status);
    if (status !== 'success') {
      console.log('phantomjs failed to connect');
      phantom.exit(1);
    }

    page.onConsoleMessage = function (msg, line, id) {
      if (typeof id === "undefined") {
        console.log("Error in ether: "+msg+"");
        return false;
      }
      var fileName = id.split('/');

      // format the output message with filename, line number and message
      // weird gotcha: phantom only uses the first console.log argument it gets :(
      console.log(fileName[fileName.length-1]+', '+ line +': '+ msg);
    };

    page.onAlert = function(msg) {
      console.log(msg);
    };
  }
});<|endoftext|>


<commit_before>//returns a plaintext result of the decryption
//or an empty string if decryption fails.
function decrypt(fakeblock) {
    debugger
    var user_meta = loadLocalStore('user_meta');
    if (!Object.size(user_meta)){
        return ""
    }

    var encrypted_data = fakeblock.users[user_meta.username];
    if (encrypted_data === undefined) {
        return ""
    }

    var e_sentinals = encrypted_data.e_sentinals;
    var e_shared_secrets = encrypted_data.e_shared_secrets;
    var priv_key = deserializePrivKey(user_meta.priv_key);
    for (var i = 0; i < e_sentinals.length; i++) {
        if (cryptico.decrypt(e_sentinals[i], priv_key) === SENTINAL) {
            var shared_secret = cryptico.decrypt(e_shared_secrets[i], user_meta.priv_key);
            return CryptoJS.AES.decrypt(Base64.decode(fakeblock.cipher_text), shared_secret).toString(CryptoJS.enc.Utf8);
        }
    }
    return ""
}

<commit_msg>ahppyyyyy<commit_after>//returns a plaintext result of the decryption
//or an empty string if decryption fails.
function decrypt(fakeblock) {
    var user_meta = loadLocalStore('user_meta');
    if (!Object.size(user_meta)){
        return ""
    }

    var encrypted_data = fakeblock.users[user_meta.username];
    if (encrypted_data === undefined) {
        return ""
    }

    var e_sentinals = encrypted_data.e_sentinals;
    var e_shared_secrets = encrypted_data.e_shared_secrets;
    var priv_key = deserializePrivKey(user_meta.priv_key);
    for (var i = 0; i < e_sentinals.length; i++) {
        if (cryptico.decrypt(e_sentinals[i], priv_key).plaintext === SENTINAL) {
            var shared_secret = cryptico.decrypt(e_shared_secrets[i], priv_key).plaintext;
            return CryptoJS.AES.decrypt(Base64.decode(fakeblock.cipher_text), shared_secret).toString(CryptoJS.enc.Utf8);
        }
    }
    return ""
}

<|endoftext|>


<commit_before>/*
 * Node.js tracker for Snowplow: tracker.js
 *
 * Copyright (c) 2014 Snowplow Analytics Ltd. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */

var version = require('./version');
var core = require('snowplow-tracker-core');
var request = require('request');

/**
 * Snowplow Node.js Tracker
 *
 * @param string endpoint The collector to which events will be sent
 * @param string namespace The namespace of the tracker
 * @param string appId The application ID
 * @param boolean encodeBase64 Whether unstructured events and custom contexts shoule be base 64 encoded
 * @param function callback Callback passed to the request function.
 */
function tracker(endpoint, namespace, appId, encodeBase64, callback) {
	endpoint = 'http://' + endpoint + '/i';
	encodeBase64 = encodeBase64 !== false;
	var trackerCore = core(encodeBase64, sendPayload);

	if (typeof callback !== 'function') callback = function () {};

	trackerCore.setPlatform('srv'); // default platform
	trackerCore.setTrackerVersion('node-' + version);
	trackerCore.setTrackerNamespace(namespace);
	trackerCore.setAppId(appId);

	/**
	 * Send the payload for an event to the endpoint
	 *
	 * @param object payload Dictionary of name-value pairs for the querystring
	 */
	function sendPayload(payload) {
		request({url: endpoint, qs: payload}, callback);
	}

	var trackEcommerceTransaction = trackerCore.trackEcommerceTransaction;

	/**
	 * Track an ecommerce transaction and all items in that transaction
	 * Each item is represented by a dictionary which may have the following fields:
	 * 1. string sku Required. SKU code of the item.
	 * 2. string name Optional. Name of the item.
	 * 3. string category Optional. Category of the item.
	 * 4. string price Required. Price of the item.
	 * 5. string quantity Required. Purchase quantity.
	 * 6. array context Optional. Custom context relating to the item.
	 * 7. number tstamp Optional. Timestamp for the item.
	 *
	 * @param string orderId Required. Internal unique order id number for this transaction.
	 * @param string affiliation Optional. Partner or store affiliation.
	 * @param string total Required. Total amount of the transaction.
	 * @param string tax Optional. Tax amount of the transaction.
	 * @param string shipping Optional. Shipping charge for the transaction.
	 * @param string city Optional. City to associate with transaction.
	 * @param string state Optional. State to associate with transaction.
	 * @param string country Optional. Country to associate with transaction.
	 * @param string currency Optional. Currency to associate with this transaction.
	 * @param array items Optional. Items which make up the transaction.
	 * @param array context Optional. Context relating to the event.
	 * @param number tstamp Optional. Timestamp for the event.
	 */
	trackerCore.trackEcommerceTransaction = function (orderId, affiliation, total, tax, shipping, city, state, country, currency, items, context, tstamp) {
		trackEcommerceTransaction(
			orderId,
			affiliation,
			total,
			tax,
			shipping,
			city,
			state,
			country,
			currency,
			context,
			tstamp
		);

		if (items) {
			for (var i=0; i<items.length; i++) {
				var item = items[i];
				trackerCore.trackEcommerceTransactionItem(
					orderId,
					item.sku,
					item.name,
					item.category,
					item.price,
					item.quantity,
					currency,
					item.context,
					tstamp
				);
			}
		}
	};

	return trackerCore;
}

module.exports = tracker;
<commit_msg>Added POST support to tracker.js (closes #4)<commit_after>/*
 * Node.js tracker for Snowplow: tracker.js
 *
 * Copyright (c) 2014 Snowplow Analytics Ltd. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */

var version = require('./version');
var core = require('snowplow-tracker-core');
var request = require('request');

/**
 * Snowplow Node.js Tracker
 *
 * @param string or array emitters The emitter or emitters to which events will be sent
 * @param string namespace The namespace of the tracker
 * @param string appId The application ID
 * @param boolean encodeBase64 Whether unstructured events and custom contexts should be base 64 encoded
 * @param function callback Callback passed to the request function.
 */
function tracker(emitters, namespace, appId, encodeBase64, callback) {
	if (!(emitters instanceof Array)) {
		emitters = [emitters];
	}
	encodeBase64 = encodeBase64 !== false;
	var trackerCore = core(encodeBase64, sendPayload);

	if (typeof callback !== 'function') callback = function () {};

	trackerCore.setPlatform('srv'); // default platform
	trackerCore.setTrackerVersion('node-' + version);
	trackerCore.setTrackerNamespace(namespace);
	trackerCore.setAppId(appId);

	/**
	 * Send the payload for an event to the endpoint
	 *
	 * @param object payload Dictionary of name-value pairs for the querystring
	 */
	function sendPayload(payload) {
		for (var i=0; i<emitters.length; i++) {
			emitters[i].input(payload);
		}
	}

	var trackEcommerceTransaction = trackerCore.trackEcommerceTransaction;

	/**
	 * Track an ecommerce transaction and all items in that transaction
	 * Each item is represented by a dictionary which may have the following fields:
	 * 1. string sku Required. SKU code of the item.
	 * 2. string name Optional. Name of the item.
	 * 3. string category Optional. Category of the item.
	 * 4. string price Required. Price of the item.
	 * 5. string quantity Required. Purchase quantity.
	 * 6. array context Optional. Custom context relating to the item.
	 * 7. number tstamp Optional. Timestamp for the item.
	 *
	 * @param string orderId Required. Internal unique order id number for this transaction.
	 * @param string affiliation Optional. Partner or store affiliation.
	 * @param string total Required. Total amount of the transaction.
	 * @param string tax Optional. Tax amount of the transaction.
	 * @param string shipping Optional. Shipping charge for the transaction.
	 * @param string city Optional. City to associate with transaction.
	 * @param string state Optional. State to associate with transaction.
	 * @param string country Optional. Country to associate with transaction.
	 * @param string currency Optional. Currency to associate with this transaction.
	 * @param array items Optional. Items which make up the transaction.
	 * @param array context Optional. Context relating to the event.
	 * @param number tstamp Optional. Timestamp for the event.
	 */
	trackerCore.trackEcommerceTransaction = function (orderId, affiliation, total, tax, shipping, city, state, country, currency, items, context, tstamp) {
		trackEcommerceTransaction(
			orderId,
			affiliation,
			total,
			tax,
			shipping,
			city,
			state,
			country,
			currency,
			context,
			tstamp
		);

		if (items) {
			for (var i=0; i<items.length; i++) {
				var item = items[i];
				trackerCore.trackEcommerceTransactionItem(
					orderId,
					item.sku,
					item.name,
					item.category,
					item.price,
					item.quantity,
					currency,
					item.context,
					tstamp
				);
			}
		}
	};

	return trackerCore;
}

module.exports = tracker;
<|endoftext|>


<commit_before>var irc = require('irc');
var fs = require('fs');
var path = require('path');
var async = require('async');
var redis = require('redis');
var sprintf = require('sprintf').sprintf;
var _ = require('underscore');

var Sandbox = require('./sandbox').Sandbox;
var AdminSandbox = require('./sandbox').AdminSandbox;

var log = require('logmagic').local('treslek.lib.treslek');

/*
 * TreslekBot. Where the magic happens.
 * {Obj} Config object.
 */
var TreslekBot = function(conf) {
  this.conf = conf;
  this.plugins = {};
  this.pluginInstances = {};
  this.adminCommands = {};
  this.registeredCommands = {};
  this.registeredHooks = {};
  this.commandUsage = {};
  this.redis = redis.createClient(this.conf.redis.port, this.conf.redis.host);
};

/*
 * Start the bot. Connect to irc, connect listeners, and load plugins.
 */
TreslekBot.prototype.start = function() {
  var that = this;

  this.irc = new irc.Client(this.conf.host, this.conf.nick, this.conf.ircOptions);

  this.irc.treslekProcessMessage = function(from, to, text, msg) {
    log.debug('processing message!', {from: from, to: to});
    if (from !== that.conf.nick && that.conf.ignored.indexOf(from) === -1) {
      async.parallel({
        log: function(callback) {
          that.logMessage(from, to, text, msg);
          callback();
        },
        commands: function(callback) {
          that.executeCommands(from, to, text, msg);
          callback();
        },
        hooks: function(callback) {
          that.executeHooks(from, to, text, msg);
          callback();
        },
        adminCommands: function(callback) {
          that.executeAdminCommands(from, to, text, msg);
          callback();
        }
      });
    }
  };

  this.irc.connect(1, function() {
    log.info('Connected to irc server.', {host: that.conf.host, options: that.conf.ircOptions});
  });

  this.irc.addListener('error', function(err) {
    log.error('Error', {err: err});
  });

  this.irc.addListener('topic', function(channel, topic, nick, msg) {
    var topicStore = sprintf('%s:topic:%s', that.conf.redis.prefix, channel);
    that.redis.set(topicStore, topic);
  });

  this.irc.addListener('message#', this.irc.treslekProcessMessage);

  this.irc.addListener('pm', function(from, text, msg) {
    log.info('privmsg', {from: from});
    this.irc.treslekProcessMessage(from, from, text, msg);
  }.bind(this));

  that.sandbox = new Sandbox(that.conf, that.irc, that.redis, Object.keys(that.registeredCommands), that.commandUsage);
  that.adminSandbox = new AdminSandbox(that);

  this.loadPlugins(function(err) {
    if (err) {
      log.error('Error loading plugins', {err: err});
      return;
    }
    log.info('Loaded plugins');
  });

  this.loadAdminPlugin(function(err) {
    if (err) {
      log.error('Error loading admin plugins', {err: err});
      return;
    }
    log.info('Loaded admin plugins');
  });
};

/*
 * Execute admin commands.
 */
TreslekBot.prototype.executeAdminCommands = function(from, to, text, msg) {
  var command,
      plugin,
      commandPrefix;

  if (this.conf.admins.indexOf(from) === -1) {
    return;
  }

  commandPrefix = this.conf.commandPrefix || '!';
  if (text.indexOf(commandPrefix) !== 0) {
    return;
  }

  text = text.substring(commandPrefix.length, text.length).split(' ');
  command = text.shift();

  if (!this.adminCommands.hasOwnProperty(command)) {
    return;
  }

  plugin = this.pluginInstances[this.adminCommands[command]];
  plugin[command](this.adminSandbox.bot, to, from, text.join(' '), function(err) {
    if (err) {
      log.error('error executing admin command', {err: err, command: command, from: from, text: text});
      return;
    }
    log.info('Successfully executed admin command', {command: command, from: from, text: text});
  });
};

/*
 * Given a message, see if we have any loaded plugins that register
 * a command for the message. Commands start with !.
 */
TreslekBot.prototype.executeCommands = function(from, to, text, msg) {
  var self = this,
      command,
      plugin,
      commandPrefix;

  commandPrefix = self.conf.commandPrefix || '!';
  if (text.indexOf(commandPrefix) !== 0) {
    return;
  }

  text = text.substring(commandPrefix.length, text.length).split(' ');
  command = text.shift();

  if (!this.registeredCommands.hasOwnProperty(command)) {
    return;
  }

  plugin = this.pluginInstances[this.registeredCommands[command]];
  plugin[command](this.sandbox.bot, to, from, text.join(' '), function(err) {
    if (err) {
      log.error('error executing command', {command: command, from: from, text: text.join(' ')});
      return;
    }
    log.info('Successfully executed command', {command: command, from: from, text: text.join(' ')});
  });
};

/*
 * Given a message, send the message to any hooks that have been registered
 * by loaded plugins.
 */
TreslekBot.prototype.executeHooks = function(from, to, text, msg) {
  var that = this;

  async.forEach(Object.keys(this.registeredHooks), function(hook, callback) {
    var plugin = that.pluginInstances[that.registeredHooks[hook]];
    plugin[hook](that.sandbox.bot, to, from, text, function(err) {
      if (err) {
        log.debug('error executing hook', {hook: hook, from: from, text: text});
        callback();
        return;
      }

      log.debug('successfully executed hook', {hook: hook, from: from});
      callback();
    });
  });
};

/*
 * Log a message to redis
 */
TreslekBot.prototype.logMessage = function(from, to, text, msg) {
  var that = this,
      logCount = sprintf('%s:logs:id', that.conf.redis.prefix),
      logStore = sprintf('%s:logs:%s', that.conf.redis.prefix, to);

  async.auto({
    'logId': function(callback) {
      that.redis.incr(logCount, function(err, reply) {
        if (err) {
          log.error('Error retrieving log id', {err: err});
          callback(err);
          return;
        }
        callback(null, reply);
      });
    },

    'createLog': ['logId', function(callback, results) {
      var hashKey = sprintf('%s:logs:%s', that.conf.redis.prefix, results.logId),
          logObj;

      logObj = {
        time: new Date().getTime().toString(),
        from: from,
        to: to,
        msg: text
      };

      that.redis.hmset(hashKey, logObj, function(err, reply){
        if (err) {
          log.error('Error creating log', {err: err});
          callback(err);
          return;
        }

        callback(null, results.logId);
      });
    }],

    'saveLog': ['createLog', function(callback, results) {
      async.parallel([
        function (callback) {
          that.redis.lpush(logStore, results.logId, function(err, reply) {
            if (err) {
              log.error('Error saving log', {err: err});
              callback(err);
              return;
            }

            callback();
          });
        },

        function (callback) {
          that.redis.lpush(sprintf('%s:%s', logStore, from), results.logId, function(err, reply) {
            if (err) {
              log.error('Error saving user log', {err: err});
              callback(err);
              return;
            }

            callback();
          });
        }
      ], callback);
    }]
  });
};

/*
 * Load a plugin, and register its commands and hooks.
 */
TreslekBot.prototype.loadPlugin = function(pluginFile, callback) {
  var plugin,
      pluginModule,
      usageKey,
      that = this;

  if (this.conf.enabledPlugins && this.conf.enabledPlugins.length > 0) {
    if (!_.contains(this.conf.enabledPlugins, path.basename(pluginFile))) {
      log.debug('Plugin is not enabled');
      callback(new Error(path.basename(pluginFile) + ' plugin is not enabled.'));
      return;
    }
  }

  try {
    if (require.cache.hasOwnProperty(pluginFile)) {
      delete require.cache[require.resolve(pluginFile)];
    }

    if (this.pluginInstances.hasOwnProperty(pluginFile)) {
      delete this.pluginInstances[pluginFile];
    }

    pluginModule = require(pluginFile).Plugin;
    this.plugins[pluginFile] = pluginModule;

    plugin = new pluginModule();

    this.pluginInstances[pluginFile] = plugin;

    if (plugin.hasOwnProperty('auto')) {
      plugin.auto.forEach(function(task) {
        plugin[task](that.sandbox.bot);
      });
    }

    if (plugin.hasOwnProperty('commands')) {
      plugin.commands.forEach(function(command) {
        that.registeredCommands[command] = pluginFile;
      });
    }

    if (plugin.hasOwnProperty('hooks')) {
      plugin.hooks.forEach(function(hook) {
        that.registeredHooks[hook] = pluginFile;
      });
    }

    if (plugin.hasOwnProperty('usage')) {
      for (usageKey in plugin.usage) {
        if (plugin.usage.hasOwnProperty(usageKey)) {
          that.commandUsage[usageKey] = plugin.usage[usageKey];
        }
      }
    }
    that.sandbox.update(Object.keys(that.registeredCommands), that.commandUsage);
  } catch (err) {
    console.dir(err);
    log.error('Error loading plugin', {plugin: plugin, err: err});
    callback(err);
    return;
  }

  callback();
};

/*
 * Unload a plugin and unregister all commands, hooks, and usage.
 */
TreslekBot.prototype.unloadPlugin = function(pluginFile, callback) {
  var that = this,
      plugin = this.pluginInstances[pluginFile],
      destroyFuncs,
      usageKey;

  if (!plugin) {
    callback();
    return;
  }

  if (plugin.hasOwnProperty('commands')) {
    plugin.commands.forEach(function(command) {
      delete that.registeredCommands[command];
    });
  }

  if (plugin.hasOwnProperty('hooks')) {
    plugin.hooks.forEach(function(hook) {
      delete that.registeredHooks[hook];
    });
  }

  if (plugin.hasOwnProperty('usage')) {
    for (usageKey in plugin.usage) {
      if (plugin.usage.hasOwnProperty(usageKey)) {
        if (that.commandUsage.hasOwnProperty(usageKey)) {
          delete that.commandUsage[usageKey];
        }
      }
    }
  }

  // run destroy functions
  destroyFuncs = plugin.unload || [];
  async.forEach(destroyFuncs, function(func, callback) {
    plugin[func](callback);
  }, function(err) {
    if (err) {
      log.error('Error destroying plugin.', {err: err});
    }
    delete that.pluginInstances[pluginFile];
    delete that.plugins[pluginFile];
    delete require.cache[require.resolve(pluginFile)];

    that.sandbox.update(Object.keys(that.registeredCommands), that.commandUsage);
    callback();
  });
};

/*
 * Reload a plugin. Unload it, and then load it again.
 */
TreslekBot.prototype.reloadPlugin = function(pluginFile, callback) {
  var that = this;

  that.unloadPlugin(pluginFile, function(err) {
    if (err) {
      log.error('Error unloading plugin', {err: err});
    }

    that.loadPlugin(pluginFile, callback);
  });
};

/*
 * Check for the plugins_dir specified in the config, and load all js
 * files located there as plugins.
 */
TreslekBot.prototype.loadPlugins = function(callback) {
  var that = this;

  fs.readdir(this.conf.plugins_dir, function(err, files) {
    if (err) {
      callback(err);
      return;
    }

    files.forEach(function(file) {
      var filePath = path.resolve(that.conf.plugins_dir, file),
          stats = fs.lstatSync(filePath);

      if (stats.isSymbolicLink()) {
        stats = fs.statSync(fs.readlinkSync(filePath));
      }

      if (stats.isFile() && filePath.slice(-3) === '.js') {
        filePath = filePath.slice(0, -3);
      } else if (!stats.isDirectory()) {
        filePath = '';
      }

      if (filePath) {
        that.loadPlugin(filePath, function(err) {
          if (err) {
            log.error('Error loading plugin', {err: err});
          }
        });
      }
    });
    callback();
  });
};

/*
 * Load the plugin at lib/admin.js.
 */
TreslekBot.prototype.loadAdminPlugin = function(callback) {

  var plugin,
      pluginModule,
      pluginFile = path.resolve(__dirname, 'admin'),
      that = this;

  try {
    if (require.cache.hasOwnProperty(pluginFile)) {
      delete require.cache[pluginFile];
    }
    pluginModule = require(pluginFile).Plugin;
    this.plugins[pluginFile] = pluginModule;

    plugin = new pluginModule();
    this.pluginInstances[pluginFile] = plugin;

    if (plugin.hasOwnProperty('commands')) {
      plugin.commands.forEach(function(command) {
        that.adminCommands[command] = pluginFile;
      });
    }
  } catch (err) {
    log.error('Error loading plugin', {plugin: plugin, err: err});
  }

  callback();
};

exports.TreslekBot = TreslekBot;
<commit_msg>feature(admins): Give channel operators admin access to the bot in addition to users specified as admins in the config.<commit_after>var irc = require('irc');
var fs = require('fs');
var path = require('path');
var async = require('async');
var redis = require('redis');
var sprintf = require('sprintf').sprintf;
var _ = require('underscore');

var Sandbox = require('./sandbox').Sandbox;
var AdminSandbox = require('./sandbox').AdminSandbox;

var log = require('logmagic').local('treslek.lib.treslek');

/*
 * TreslekBot. Where the magic happens.
 * {Obj} Config object.
 */
var TreslekBot = function(conf) {
  this.conf = conf;
  this.plugins = {};
  this.pluginInstances = {};
  this.adminCommands = {};
  this.registeredCommands = {};
  this.registeredHooks = {};
  this.commandUsage = {};
  this.redis = redis.createClient(this.conf.redis.port, this.conf.redis.host);
};

/*
 * Start the bot. Connect to irc, connect listeners, and load plugins.
 */
TreslekBot.prototype.start = function() {
  var that = this;

  this.irc = new irc.Client(this.conf.host, this.conf.nick, this.conf.ircOptions);

  this.irc.treslekProcessMessage = function(from, to, text, msg) {
    log.debug('processing message!', {from: from, to: to});
    if (from !== that.conf.nick && that.conf.ignored.indexOf(from) === -1) {
      async.parallel({
        log: function(callback) {
          that.logMessage(from, to, text, msg);
          callback();
        },
        commands: function(callback) {
          that.executeCommands(from, to, text, msg);
          callback();
        },
        hooks: function(callback) {
          that.executeHooks(from, to, text, msg);
          callback();
        },
        adminCommands: function(callback) {
          that.executeAdminCommands(from, to, text, msg);
          callback();
        }
      });
    }
  };

  this.irc.connect(1, function() {
    log.info('Connected to irc server.', {host: that.conf.host, options: that.conf.ircOptions});
  });

  this.irc.addListener('error', function(err) {
    log.error('Error', {err: err});
  });

  this.irc.addListener('topic', function(channel, topic, nick, msg) {
    var topicStore = sprintf('%s:topic:%s', that.conf.redis.prefix, channel);
    that.redis.set(topicStore, topic);
  });

  this.irc.addListener('message#', this.irc.treslekProcessMessage);

  this.irc.addListener('pm', function(from, text, msg) {
    log.info('privmsg', {from: from});
    this.irc.treslekProcessMessage(from, from, text, msg);
  }.bind(this));

  that.sandbox = new Sandbox(that.conf, that.irc, that.redis, Object.keys(that.registeredCommands), that.commandUsage);
  that.adminSandbox = new AdminSandbox(that);

  this.loadPlugins(function(err) {
    if (err) {
      log.error('Error loading plugins', {err: err});
      return;
    }
    log.info('Loaded plugins');
  });

  this.loadAdminPlugin(function(err) {
    if (err) {
      log.error('Error loading admin plugins', {err: err});
      return;
    }
    log.info('Loaded admin plugins');
  });
};

/**
 * @param {String} channel Channel to check.
 * @param {String} nick Nick to check.
 * @return {Bool} Return true if the nick is an op.
 */
TreslekBot.prototype.isOp = function(channel, nick) {
  var chan = this.irc.chanData(channel);

  return chan.users[nick] && chan.users[nick].indexOf('@') !== -1;
};

/*
 * Execute admin commands.
 */
TreslekBot.prototype.executeAdminCommands = function(from, to, text, msg) {
  var command,
      plugin,
      commandPrefix;

  if (this.conf.admins.indexOf(from) === -1 && !this.isOp(to, from)) {
    return;
  }

  commandPrefix = this.conf.commandPrefix || '!';
  if (text.indexOf(commandPrefix) !== 0) {
    return;
  }

  text = text.substring(commandPrefix.length, text.length).split(' ');
  command = text.shift();

  if (!this.adminCommands.hasOwnProperty(command)) {
    return;
  }

  plugin = this.pluginInstances[this.adminCommands[command]];
  plugin[command](this.adminSandbox.bot, to, from, text.join(' '), function(err) {
    if (err) {
      log.error('error executing admin command', {err: err, command: command, from: from, text: text});
      return;
    }
    log.info('Successfully executed admin command', {command: command, from: from, text: text});
  });
};

/*
 * Given a message, see if we have any loaded plugins that register
 * a command for the message. Commands start with !.
 */
TreslekBot.prototype.executeCommands = function(from, to, text, msg) {
  var self = this,
      command,
      plugin,
      commandPrefix;

  commandPrefix = self.conf.commandPrefix || '!';
  if (text.indexOf(commandPrefix) !== 0) {
    return;
  }

  text = text.substring(commandPrefix.length, text.length).split(' ');
  command = text.shift();

  if (!this.registeredCommands.hasOwnProperty(command)) {
    return;
  }

  plugin = this.pluginInstances[this.registeredCommands[command]];
  plugin[command](this.sandbox.bot, to, from, text.join(' '), function(err) {
    if (err) {
      log.error('error executing command', {command: command, from: from, text: text.join(' ')});
      return;
    }
    log.info('Successfully executed command', {command: command, from: from, text: text.join(' ')});
  });
};

/*
 * Given a message, send the message to any hooks that have been registered
 * by loaded plugins.
 */
TreslekBot.prototype.executeHooks = function(from, to, text, msg) {
  var that = this;

  async.forEach(Object.keys(this.registeredHooks), function(hook, callback) {
    var plugin = that.pluginInstances[that.registeredHooks[hook]];
    plugin[hook](that.sandbox.bot, to, from, text, function(err) {
      if (err) {
        log.debug('error executing hook', {hook: hook, from: from, text: text});
        callback();
        return;
      }

      log.debug('successfully executed hook', {hook: hook, from: from});
      callback();
    });
  });
};

/*
 * Log a message to redis
 */
TreslekBot.prototype.logMessage = function(from, to, text, msg) {
  var that = this,
      logCount = sprintf('%s:logs:id', that.conf.redis.prefix),
      logStore = sprintf('%s:logs:%s', that.conf.redis.prefix, to);

  async.auto({
    'logId': function(callback) {
      that.redis.incr(logCount, function(err, reply) {
        if (err) {
          log.error('Error retrieving log id', {err: err});
          callback(err);
          return;
        }
        callback(null, reply);
      });
    },

    'createLog': ['logId', function(callback, results) {
      var hashKey = sprintf('%s:logs:%s', that.conf.redis.prefix, results.logId),
          logObj;

      logObj = {
        time: new Date().getTime().toString(),
        from: from,
        to: to,
        msg: text
      };

      that.redis.hmset(hashKey, logObj, function(err, reply){
        if (err) {
          log.error('Error creating log', {err: err});
          callback(err);
          return;
        }

        callback(null, results.logId);
      });
    }],

    'saveLog': ['createLog', function(callback, results) {
      async.parallel([
        function (callback) {
          that.redis.lpush(logStore, results.logId, function(err, reply) {
            if (err) {
              log.error('Error saving log', {err: err});
              callback(err);
              return;
            }

            callback();
          });
        },

        function (callback) {
          that.redis.lpush(sprintf('%s:%s', logStore, from), results.logId, function(err, reply) {
            if (err) {
              log.error('Error saving user log', {err: err});
              callback(err);
              return;
            }

            callback();
          });
        }
      ], callback);
    }]
  });
};

/*
 * Load a plugin, and register its commands and hooks.
 */
TreslekBot.prototype.loadPlugin = function(pluginFile, callback) {
  var plugin,
      pluginModule,
      usageKey,
      that = this;

  if (this.conf.enabledPlugins && this.conf.enabledPlugins.length > 0) {
    if (!_.contains(this.conf.enabledPlugins, path.basename(pluginFile))) {
      log.debug('Plugin is not enabled');
      callback(new Error(path.basename(pluginFile) + ' plugin is not enabled.'));
      return;
    }
  }

  try {
    if (require.cache.hasOwnProperty(pluginFile)) {
      delete require.cache[require.resolve(pluginFile)];
    }

    if (this.pluginInstances.hasOwnProperty(pluginFile)) {
      delete this.pluginInstances[pluginFile];
    }

    pluginModule = require(pluginFile).Plugin;
    this.plugins[pluginFile] = pluginModule;

    plugin = new pluginModule();

    this.pluginInstances[pluginFile] = plugin;

    if (plugin.hasOwnProperty('auto')) {
      plugin.auto.forEach(function(task) {
        plugin[task](that.sandbox.bot);
      });
    }

    if (plugin.hasOwnProperty('commands')) {
      plugin.commands.forEach(function(command) {
        that.registeredCommands[command] = pluginFile;
      });
    }

    if (plugin.hasOwnProperty('hooks')) {
      plugin.hooks.forEach(function(hook) {
        that.registeredHooks[hook] = pluginFile;
      });
    }

    if (plugin.hasOwnProperty('usage')) {
      for (usageKey in plugin.usage) {
        if (plugin.usage.hasOwnProperty(usageKey)) {
          that.commandUsage[usageKey] = plugin.usage[usageKey];
        }
      }
    }
    that.sandbox.update(Object.keys(that.registeredCommands), that.commandUsage);
  } catch (err) {
    console.dir(err);
    log.error('Error loading plugin', {plugin: plugin, err: err});
    callback(err);
    return;
  }

  callback();
};

/*
 * Unload a plugin and unregister all commands, hooks, and usage.
 */
TreslekBot.prototype.unloadPlugin = function(pluginFile, callback) {
  var that = this,
      plugin = this.pluginInstances[pluginFile],
      destroyFuncs,
      usageKey;

  if (!plugin) {
    callback();
    return;
  }

  if (plugin.hasOwnProperty('commands')) {
    plugin.commands.forEach(function(command) {
      delete that.registeredCommands[command];
    });
  }

  if (plugin.hasOwnProperty('hooks')) {
    plugin.hooks.forEach(function(hook) {
      delete that.registeredHooks[hook];
    });
  }

  if (plugin.hasOwnProperty('usage')) {
    for (usageKey in plugin.usage) {
      if (plugin.usage.hasOwnProperty(usageKey)) {
        if (that.commandUsage.hasOwnProperty(usageKey)) {
          delete that.commandUsage[usageKey];
        }
      }
    }
  }

  // run destroy functions
  destroyFuncs = plugin.unload || [];
  async.forEach(destroyFuncs, function(func, callback) {
    plugin[func](callback);
  }, function(err) {
    if (err) {
      log.error('Error destroying plugin.', {err: err});
    }
    delete that.pluginInstances[pluginFile];
    delete that.plugins[pluginFile];
    delete require.cache[require.resolve(pluginFile)];

    that.sandbox.update(Object.keys(that.registeredCommands), that.commandUsage);
    callback();
  });
};

/*
 * Reload a plugin. Unload it, and then load it again.
 */
TreslekBot.prototype.reloadPlugin = function(pluginFile, callback) {
  var that = this;

  that.unloadPlugin(pluginFile, function(err) {
    if (err) {
      log.error('Error unloading plugin', {err: err});
    }

    that.loadPlugin(pluginFile, callback);
  });
};

/*
 * Check for the plugins_dir specified in the config, and load all js
 * files located there as plugins.
 */
TreslekBot.prototype.loadPlugins = function(callback) {
  var that = this;

  fs.readdir(this.conf.plugins_dir, function(err, files) {
    if (err) {
      callback(err);
      return;
    }

    files.forEach(function(file) {
      var filePath = path.resolve(that.conf.plugins_dir, file),
          stats = fs.lstatSync(filePath);

      if (stats.isSymbolicLink()) {
        stats = fs.statSync(fs.readlinkSync(filePath));
      }

      if (stats.isFile() && filePath.slice(-3) === '.js') {
        filePath = filePath.slice(0, -3);
      } else if (!stats.isDirectory()) {
        filePath = '';
      }

      if (filePath) {
        that.loadPlugin(filePath, function(err) {
          if (err) {
            log.error('Error loading plugin', {err: err});
          }
        });
      }
    });
    callback();
  });
};

/*
 * Load the plugin at lib/admin.js.
 */
TreslekBot.prototype.loadAdminPlugin = function(callback) {

  var plugin,
      pluginModule,
      pluginFile = path.resolve(__dirname, 'admin'),
      that = this;

  try {
    if (require.cache.hasOwnProperty(pluginFile)) {
      delete require.cache[pluginFile];
    }
    pluginModule = require(pluginFile).Plugin;
    this.plugins[pluginFile] = pluginModule;

    plugin = new pluginModule();
    this.pluginInstances[pluginFile] = plugin;

    if (plugin.hasOwnProperty('commands')) {
      plugin.commands.forEach(function(command) {
        that.adminCommands[command] = pluginFile;
      });
    }
  } catch (err) {
    log.error('Error loading plugin', {plugin: plugin, err: err});
  }

  callback();
};

exports.TreslekBot = TreslekBot;
<|endoftext|>


<commit_before>/*
 * lanyrd-scraper
 * https://github.com/markdalgleish/node-lanyrd-scraper
 *
 * Copyright (c) 2012 Mark Dalgleish
 * Licensed under the MIT license.
 */

var scraper = require('scraper'),
	Helper = require('./helper');

exports.scrape = function(url, callback) {
	var helper = new Helper();

	url = helper.resolveUrl(url);

	scraper(url, function(err, $) {
		if (err) {
			callback(err, null);
			return;
		}

		helper.setContext($);

		var data = {};

		data.title = helper.getText('h1.summary');
		data.tagline = helper.getText('h2.tagline');

		data.websiteUrl = helper.getHref('div.item-meta a.website');
		data.scheduleUrl = helper.getHref('div.item-meta a.seeschedule');

		data.location = '' + helper.getText('p.prominent-place a.sub-place') + ', ' + helper.getText('p.prominent-place span.place-context a');
		if (data.location === ', ') {
			data.location = undefined;
		}

		// Parse the address and notes
		var addressValues = $('#venues p:eq(1)').text().split('- ');
		var address = $.trim(addressValues[0]);
		addressValues.shift();
		var note = addressValues.length > 0 ? $.trim(addressValues.join('- ')) : undefined;

		data.venues = [];

		data.venues.push({
			name: helper.getText('#venues h3 a'),
			address: address,
			note: note,
			googleMapsUrl: helper.getHref('#venues a.map-icon')
		});

		data.startDate = helper.getTitle('.main-date .dtstart');
		data.endDate = helper.getTitle('.main-date .dtend');
		data.time = helper.getText('.main-date abbr.dtstart .time');

		data.hashtag = helper.getText('.item-meta a.hashtag');
		data.twitterHandle = helper.getText('.item-meta a.twitter');

		data.speakers = [];
		$('#speaker-list li').each(function(){
			var speaker = {},
				$this = $(this);

			speaker.name = $this.find('span.name').text();
			speaker.twitterHandle = helper.getText($this.find('span.handle'));

			data.speakers.push(speaker);
		});

		data.sessions = [];
		$('li.session-detail').each(function(){
			var $this = $(this),
				$titleLink = $this.find('h3 a'),
				$link = $this.find('p a'),
				$time = $this.find('.time');

			var session = {
				title: $titleLink.text(),
				url: helper.getHref($titleLink),
				startTime: helper.getText($time),
				speakers: []
			};

			var $paragraph = $this.find('p');

			// Remove 'presented by'
			$paragraph.find('strong').remove();

			// Loop through all speakers with accounts, remove tags from DOM
			$paragraph.find('a').remove().each(function(){
				var $link = $(this);

				session.speakers.push({
					name: $link.text(),
					twitterHandle: helper.getTwitterHandle($link)
				});
			});

			// Convert remaining names into an array
			$paragraph.text().replace(' and ', ', ').split(', ').forEach(function(speakerName){
				speakerName = $.trim(speakerName);

				if (speakerName !== '') {
					session.speakers.push({
						name: speakerName
					});
				}
			});

			data.sessions.push(session);
		});

		data.attendees = [];
		$('.attendees-placeholder li').each(function(){
			var attendee = {},
				$this = $(this);

			attendee.name = $this.find('img:first').attr('alt');
			attendee.twitterHandle = helper.getTwitterHandle($this.find('a'));

			data.attendees.push(attendee);
		});

		data.trackers = [];
		$('.trackers-placeholder li').each(function(){
			var tracker = {},
				$this = $(this);

			tracker.name = $this.find('img:first').attr('alt');
			tracker.twitterHandle = helper.getTwitterHandle($this.find('a'));

			data.trackers.push(tracker);
		});

		callback(null, data);
	});
};<commit_msg>Make selectors more forgiving<commit_after>/*
 * lanyrd-scraper
 * https://github.com/markdalgleish/node-lanyrd-scraper
 *
 * Copyright (c) 2012 Mark Dalgleish
 * Licensed under the MIT license.
 */

var scraper = require('scraper'),
	Helper = require('./helper');

exports.scrape = function(url, callback) {
	var helper = new Helper();

	url = helper.resolveUrl(url);

	scraper(url, function(err, $) {
		if (err) {
			callback(err, null);
			return;
		}

		helper.setContext($);

		var data = {};

		data.title = helper.getText('h1.summary');
		data.tagline = helper.getText('h2.tagline');

		data.websiteUrl = helper.getHref('.item-meta a.website');
		data.scheduleUrl = helper.getHref('.item-meta a.seeschedule');

		data.location = '' + helper.getText('.prominent-place .sub-place') + ', ' + helper.getText('.prominent-place .place-context a');
		if (data.location === ', ') {
			data.location = undefined;
		}

		// Parse the address and notes
		var addressValues = $('#venues p:eq(1)').text().split('- ');
		var address = $.trim(addressValues[0]);
		addressValues.shift();
		var note = addressValues.length > 0 ? $.trim(addressValues.join('- ')) : undefined;

		data.venues = [];

		data.venues.push({
			name: helper.getText('#venues h3 a'),
			address: address,
			note: note,
			googleMapsUrl: helper.getHref('#venues a.map-icon')
		});

		data.startDate = helper.getTitle('.main-date .dtstart');
		data.endDate = helper.getTitle('.main-date .dtend');
		data.time = helper.getText('.main-date abbr.dtstart .time');

		data.hashtag = helper.getText('.item-meta a.hashtag');
		data.twitterHandle = helper.getText('.item-meta a.twitter');

		data.speakers = [];
		$('#speaker-list li').each(function(){
			var speaker = {},
				$this = $(this);

			speaker.name = $this.find('span.name').text();
			speaker.twitterHandle = helper.getText($this.find('span.handle'));

			data.speakers.push(speaker);
		});

		data.sessions = [];
		$('li.session-detail').each(function(){
			var $this = $(this),
				$titleLink = $this.find('h3 a'),
				$link = $this.find('p a'),
				$time = $this.find('.time');

			var session = {
				title: $titleLink.text(),
				url: helper.getHref($titleLink),
				startTime: helper.getText($time),
				speakers: []
			};

			var $paragraph = $this.find('p');

			// Remove 'presented by'
			$paragraph.find('strong').remove();

			// Loop through all speakers with accounts, remove tags from DOM
			$paragraph.find('a').remove().each(function(){
				var $link = $(this);

				session.speakers.push({
					name: $link.text(),
					twitterHandle: helper.getTwitterHandle($link)
				});
			});

			// Convert remaining names into an array
			$paragraph.text().replace(' and ', ', ').split(', ').forEach(function(speakerName){
				speakerName = $.trim(speakerName);

				if (speakerName !== '') {
					session.speakers.push({
						name: speakerName
					});
				}
			});

			data.sessions.push(session);
		});

		data.attendees = [];
		$('.attendees-placeholder li').each(function(){
			var attendee = {},
				$this = $(this);

			attendee.name = $this.find('img:first').attr('alt');
			attendee.twitterHandle = helper.getTwitterHandle($this.find('a'));

			data.attendees.push(attendee);
		});

		data.trackers = [];
		$('.trackers-placeholder li').each(function(){
			var tracker = {},
				$this = $(this);

			tracker.name = $this.find('img:first').attr('alt');
			tracker.twitterHandle = helper.getTwitterHandle($this.find('a'));

			data.trackers.push(tracker);
		});

		callback(null, data);
	});
};<|endoftext|>


<commit_before>(function(window, document) {
    'use strict';

    function checkLocalStorage() {
        var stuff = 'stuff';

        try {
            localStorage.setItem(stuff, stuff);
            localStorage.removeItem(stuff);
            return true;
        } catch(e) {
            return false;
        }
    }

    function newXhr() {
        return window.XMLHttpRequest ? new window.XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');
    }

    function insertFile(storedObj) {
        (insertStrategies[storedObj.type] || function () {})(storedObj.content);
    }

    function insertScript(content) {
        var script = document.createElement('script');

        script.setAttribute('type', 'text/javascript');
        script.text = content;
        head.appendChild(script);
    }

    function insertStyle(content) {
        var style = document.createElement('style');

        style.setAttribute('type', 'text/css');
        style.setAttribute('media', 'screen');
        head.appendChild(style);
        if (style.styleSheet) {
            style.styleSheet.cssText = content;
        } else {
            style.innerHTML = content;
        }
    }

    function load(fileObj, qi) {
        var xhr = newXhr(),
            storedObj;

        xhr.open('GET', fileObj.url, !fileObj.sync);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if(xhr.status === 200) {
                    storedObj = {
                        content: xhr.responseText,
                        type: fileObj.type || xhr.getResponseHeader('content-type')
                    };
                    if (hasLocalStorage) localStorage[fileObj.url] = JSON.stringify(storedObj);
                    toInsertQ(qi, storedObj);
                } else {
                    throw new Error(xhr.statusText);
                }
            }
        };
        // By default XHRs never timeout so we do it ourselves.
        setTimeout(function () { if (xhr.readyState < 4) xhr.abort(); }, 15000);
        xhr.send();
    }

    function lazyLoad(fileObj) {
        var qi = insertQ.length,
            storedObj;

        insertQ[qi] = { loaded: false };
        if (hasLocalStorage) {
            storedObj = JSON.parse(localStorage[fileObj.url] || 'false');
            if (storedObj) return toInsertQ(qi, storedObj);
        }
        load(fileObj, qi);
    }

    function toInsertQ(qi, storedObj) {
        insertQ[qi].storedObj = storedObj;
        for (var i = 0, length = insertQ.length; i < length; i++) {
            var loadObj = insertQ[i];
            if (!loadObj.loaded) {
                if (!loadObj.storedObj) break;
                insertFile(loadObj.storedObj);
                loadObj.loaded = true;
                delete loadObj.storedObj;
            }
        }
    }

    function init(files) {
        for (var i = 0, length = arguments.length; i < length; i++) {
            lazyLoad(arguments[i]);
        }
    }

    var hasLocalStorage = checkLocalStorage(),
        insertStrategies = {
            'application/javascript': insertScript,
            'text/css': insertStyle
        },
        head = document.head || document.getElementsByTagName('head')[0],
        insertQ = [];

    window.trolleyjs = init;
})(this, document);
<commit_msg>Check for JSON parser support.<commit_after>(function(window, document) {
    'use strict';

    function checkLocalStorage() {
        var stuff = 'stuff';

        try {
            localStorage.setItem(stuff, stuff);
            localStorage.removeItem(stuff);
            return (typeof JSON === 'object' && typeof JSON.parse === 'function');
        } catch(e) {
            return false;
        }
    }

    function newXhr() {
        return window.XMLHttpRequest ? new window.XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');
    }

    function insertFile(storedObj) {
        (insertStrategies[storedObj.type] || function () {})(storedObj.content);
    }

    function insertScript(content) {
        var script = document.createElement('script');

        script.setAttribute('type', 'text/javascript');
        script.text = content;
        head.appendChild(script);
    }

    function insertStyle(content) {
        var style = document.createElement('style');

        style.setAttribute('type', 'text/css');
        style.setAttribute('media', 'screen');
        head.appendChild(style);
        if (style.styleSheet) {
            style.styleSheet.cssText = content;
        } else {
            style.innerHTML = content;
        }
    }

    function load(fileObj, qi) {
        var xhr = newXhr(),
            storedObj;

        xhr.open('GET', fileObj.url, !fileObj.sync);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if(xhr.status === 200) {
                    storedObj = {
                        content: xhr.responseText,
                        type: fileObj.type || xhr.getResponseHeader('content-type')
                    };
                    if (hasLocalStorage) localStorage[fileObj.url] = JSON.stringify(storedObj);
                    toInsertQ(qi, storedObj);
                } else {
                    throw new Error(xhr.statusText);
                }
            }
        };
        // By default XHRs never timeout so we do it ourselves.
        setTimeout(function () { if (xhr.readyState < 4) xhr.abort(); }, 15000);
        xhr.send();
    }

    function lazyLoad(fileObj) {
        var qi = insertQ.length,
            storedObj;

        insertQ[qi] = { loaded: false };
        if (hasLocalStorage) {
            storedObj = JSON.parse(localStorage[fileObj.url] || 'false');
            if (storedObj) return toInsertQ(qi, storedObj);
        }
        load(fileObj, qi);
    }

    function toInsertQ(qi, storedObj) {
        insertQ[qi].storedObj = storedObj;
        for (var i = 0, length = insertQ.length; i < length; i++) {
            var loadObj = insertQ[i];
            if (!loadObj.loaded) {
                if (!loadObj.storedObj) break;
                insertFile(loadObj.storedObj);
                loadObj.loaded = true;
                delete loadObj.storedObj;
            }
        }
    }

    function init(files) {
        for (var i = 0, length = arguments.length; i < length; i++) {
            lazyLoad(arguments[i]);
        }
    }

    var hasLocalStorage = checkLocalStorage(),
        insertStrategies = {
            'application/javascript': insertScript,
            'text/css': insertStyle
        },
        head = document.head || document.getElementsByTagName('head')[0],
        insertQ = [];

    window.trolleyjs = init;
})(this, document);
<|endoftext|>


<commit_before>var _ = require('lodash'),
    OAuth = require('oauth'),
    EventEmitter = require('events').EventEmitter,
    util = require('util'),
    TweetParser = require('./tweet-parser');

function TStream(config) {

    EventEmitter.call(this);

    var default_config = {
        consumer_key: null,
        consumer_secret: null,
        access_token_key: null,
        access_token_secret: null,
        urls: {
            request_token_url: 'https://api.twitter.com/oauth/request_token',
            access_token_url: 'https://api.twitter.com/oauth/access_token',
            authenticate_url: 'https://api.twitter.com/oauth/authenticate',
            authorize_url: 'https://api.twitter.com/oauth/authorize',
            stream_base: 'https://stream.twitter.com/1.1',
            user_stream_base: 'https://userstream.twitter.com/1.1',
            site_stream_base: 'https://sitestream.twitter.com/1.1'
        },
        headers: {
            'Accept': '*/*',
            // 'Connection': 'close',
            'User-Agent': 'tstream'
        }
    };

    this.config = _.extend(default_config, config);

    // oauth init
    this.oauth = new OAuth.OAuth(
        this.config.urls.request_token_url,
        this.config.urls.access_token_url,
        this.config.consumer_key,
        this.config.consumer_secret,
        '1.0', null, 'HMAC-SHA1', null,
        this.config.headers
    );

};

util.inherits(TStream, EventEmitter);

TStream.prototype.stream = function(method, params, callback) {

    var self = this;

    // Initialize the parser that will handler the response
    // from the Twitter streaming endpoint
    var parser = new TweetParser();

    if (typeof params === 'function') {
        callback = params;
        params = null;
    }

    // construct the url of the streaming endpoint
    var streaming_endpoint_prefix = '';
    if (method === 'user' || method === 'site') {
        streaming_endpoint_prefix = this.config.urls[method+'_stream_base'];
    } else {
        streaming_endpoint_prefix = this.config.urls.stream_base;
    }
    var streaming_endpoint = streaming_endpoint_prefix + '/' + escape(method) + '.json';

    var default_params = {
        'stall_warnings': null
    }

    params = _.extend(default_params, params);

    var connectToTwittetStream = function() {

        // fire off the request
        var request = this.oauth.post(
            streaming_endpoint,
            this.config.access_token_key,
            this.config.access_token_secret,
            params,
            null
        );

        request.on('error', function(error) {
            self.emit('error', error);
        });

        request.on('response', function(response) {

            if(response.statusCode > 200) {
                self.emit('error', new Error('HTTP ' + response.statusCode));
            }

            response.on('data', function(chunk) {
                parser.parse(chunk);
            });

            response.on('error', function(error) {
                self.emit('error', error);
            });

            response.on('end', function() {
                self.emit('end', 'Response ended');
                reConnect();
            });

            response.on('close', function() {
                self.emit('close', 'Connection closed');
                reConnect();
            });

        });

        request.end();

    };

    // start the stream
    connectToTwittetStream();

    var reconnectCount = 0;

    var reConnect = function() {
        reconnectCount += 1;
        console.log('Reconnecting (' + reconnectCount + ') at ' + new Date());
        connectToTwittetStream();
    };

    // the parser will parse the chunks of data in the response
    // and emit events, the data returned by twitter may not always
    // be just tweets
    parser.on('tweet', function(tweet) {
        self.emit('data', tweet);
    });

    // handle the non-tweet events in the stream
    parser.on('special', function(data) {
        self.emit(data.type, data.meta);
    });

    if (typeof callback === 'function') {
        callback(self);
    }

}

module.exports = TStream;<commit_msg>Fix scope issues with the reconnect code<commit_after>var _ = require('lodash'),
    OAuth = require('oauth'),
    EventEmitter = require('events').EventEmitter,
    util = require('util'),
    TweetParser = require('./tweet-parser');

function TStream(config) {

    EventEmitter.call(this);

    var default_config = {
        consumer_key: null,
        consumer_secret: null,
        access_token_key: null,
        access_token_secret: null,
        urls: {
            request_token_url: 'https://api.twitter.com/oauth/request_token',
            access_token_url: 'https://api.twitter.com/oauth/access_token',
            authenticate_url: 'https://api.twitter.com/oauth/authenticate',
            authorize_url: 'https://api.twitter.com/oauth/authorize',
            stream_base: 'https://stream.twitter.com/1.1',
            user_stream_base: 'https://userstream.twitter.com/1.1',
            site_stream_base: 'https://sitestream.twitter.com/1.1'
        },
        headers: {
            'Accept': '*/*',
            // 'Connection': 'close',
            'User-Agent': 'tstream'
        }
    };

    this.config = _.extend(default_config, config);

    // oauth init
    this.oauth = new OAuth.OAuth(
        this.config.urls.request_token_url,
        this.config.urls.access_token_url,
        this.config.consumer_key,
        this.config.consumer_secret,
        '1.0', null, 'HMAC-SHA1', null,
        this.config.headers
    );

    this.reconnectCount = 0;

    this.streamParams = {};
    this.streaming_endpoint;
    this.parser;

};

util.inherits(TStream, EventEmitter);

TStream.prototype.stream = function(method, params, callback) {

    var self = this;

    // Initialize the parser that will handler the response
    // from the Twitter streaming endpoint
    this.parser = new TweetParser();

    if (typeof params === 'function') {
        callback = params;
        params = null;
    }

    // construct the url of the streaming endpoint
    var streaming_endpoint_prefix = '';
    if (method === 'user' || method === 'site') {
        streaming_endpoint_prefix = this.config.urls[method+'_stream_base'];
    } else {
        streaming_endpoint_prefix = this.config.urls.stream_base;
    }
    this.streaming_endpoint = streaming_endpoint_prefix + '/' + escape(method) + '.json';

    var default_params = {
        'stall_warnings': null
    }

    this.streamParams = _.extend(default_params, params);

    // start the stream
    this.connectToTwittetStream();

    // the parser will parse the chunks of data in the response
    // and emit events, the data returned by twitter may not always
    // be just tweets
    this.parser.on('tweet', function(tweet) {
        self.emit('data', tweet);
    });

    // handle the non-tweet events in the stream
    this.parser.on('special', function(data) {
        self.emit(data.type, data.meta);
    });

    if (typeof callback === 'function') {
        callback(self);
    }

}

TStream.prototype.connectToTwittetStream = function() {

    var self = this;

    // fire off the request
    var request = this.oauth.post(
        this.streaming_endpoint,
        this.config.access_token_key,
        this.config.access_token_secret,
        this.streamParams,
        null
    );

    request.on('error', function(error) {
        self.emit('error', error);
    });

    request.on('response', function(response) {

        if(response.statusCode > 200) {
            self.emit('error', new Error('HTTP ' + response.statusCode));
        }

        response.on('data', function(chunk) {
            self.parser.parse(chunk);
        });

        response.on('error', function(error) {
            self.emit('error', error);
        });

        response.on('end', function() {
            self.emit('end', 'Response ended');
            self.reConnect();
        });

        response.on('close', function() {
            self.emit('close', 'Connection closed');
            self.reConnect();
        });

    });

    request.end();

};

TStream.prototype.reConnect = function() {
    this.reconnectCount += 1;
    if (this.reconnectCount > 10) {
        return;
    }
    console.log('Reconnecting (' + this.reconnectCount + ') at ' + new Date());
    this.connectToTwittetStream();
};

module.exports = TStream;<|endoftext|>


<commit_before>module.exports = TwoStep;
module.exports.Steppy = Steppy;

var slice = Array.prototype.slice;

/**
 * Group represents one step in Step's execution flow. Makes a decision when
 * the execution should jump to the next step.
 *
 * @param {function(err, args)} callback
 *   Callback to be called on successful execution end

 * @param {function(err, args)} [errorCallback]
 *   If defined, will be called in case of error (default value: callback arg)
 */
function Group(callback, errorCallback) {
	this.args = [null];
	this.left = 0;
	this.callback = callback;
	this.errorCallback = errorCallback || callback;
	this.isDone = false;
}

Group.prototype.done = function done() {
	if (this.isDone) {
		return;
	}
	this.isDone = true;
	//Don't go to next step if no args is passed
	if (this.args.length != 1) {
		this.callback.apply(null, this.args);
	}
};

Group.prototype.error = function error(err) {
	if (this.isDone) {
		return;
	}
	this.isDone = true;
	this.errorCallback(err);
};

// Simple utility for passing a sync value to the next step.
Group.prototype.pass = function pass() {
	var values = slice.call(arguments);
	for (var i = 0, l = values.length; i < l; i++) {
		this.args.push(values[i]);
	}
};

// Register a slot in the next step and return a callback
Group.prototype.slot = function slot() {
	var group = this;
	var index = group.args.length;
	group.args.length++;
	group.left++;
	return function (err, data) {
		process.nextTick(function() {
			if (err) {
				return group.error(err);
			}
			group.args[index] = data;
			if (--group.left === 0) {
				group.done();
			}
		});
	};
};

// Creates a nested group where several callbacks go into a single array.
Group.prototype.makeGroup = function makeGroup() {
	var group = this;
	var index = this.args.length;
	this.args.length++;
	group.left++;
	return new Group(function (err) {
		if (err) {
			return group.error(err);
		}
		var data = slice.call(arguments, 1);
		group.args[index] = data;
		if (--group.left === 0) {
			group.done();
		}
	});
};

/**
 * TwoStep section
 */

// Execute steps immediately
function TwoStep() {
	exec(slice.call(arguments), []);
}

// Create a composite function with steps built-in
TwoStep.fn = function () {
	var steps = slice.call(arguments);
	return function () {
		var args = slice.call(arguments);
		var callback = args.pop();
		exec(steps, args, callback);
	};
};

// Stepper function
function exec(steps, args, callback) {
	var pos = 0;
	function next() {
		var step = steps[pos++];
		if (!step) {
			callback && callback.apply(null, arguments);
			return;
		}
		var group = new Group(next);
		try {
			step.apply(group, arguments);
		} catch (e) {
			group.error(e);
			return;
		}
		if (group.left === 0) {
			group.done();
		}
	};
	next.apply(null, args);
}

// Expose just for fun and extensibility
TwoStep.Group = Group;

//Simple step function, just return callack
TwoStep.simple = function(callback) {
	return callback;
};

//If error happens somewhere after this step, just throw Error next
TwoStep.throwIfError = function(callback) {
	return function() {
		if (arguments[0]) {
			throw arguments[0];
		}
		return callback.apply(this, arguments);
	};
};

/**
 * Steppy section
 */

// Execute steps immediately
function Steppy() {
	execSteppy(slice.call(arguments), []);
}

// Stepper function
function execSteppy(steps, args, callback) {
	var pos = 0;

	function next() {
		var step = steps[pos++];
		if (!step) {
			return callback && callback.apply(null, arguments);
		}
		//trow error from any step to the last, error handling step,
		//and from the last step rethrow error outside Steppy
		var errorHandler = (pos < steps.length) ?
			steps[steps.length - 1] : function(err) {throw err};

		var group = new Group(next, errorHandler);
		try {
			step.apply(group, arguments);
		} catch (e) {
			return group.error(e);
		}
		if (group.left === 0) {
			group.done();
		}
	};
	next.apply(null, args);
}

// Expose just for fun and extensibility
Steppy.Group = Group;
<commit_msg>small style improvements in Group code<commit_after>module.exports = TwoStep;
module.exports.Steppy = Steppy;

var slice = Array.prototype.slice;

/**
 * Group represents one step in Step's execution flow. Makes a decision when
 * the execution should jump to the next step.
 *
 * @param {function(err, args)} callback
 *   Callback to be called on successful execution end

 * @param {function(err, args)} [errorCallback]
 *   If defined, will be called in case of error (default value: callback arg)
 */
function Group(callback, errorCallback) {
	this.args = [null];
	this.left = 0;
	this.callback = callback;
	this.errorCallback = errorCallback || callback;
	this.isDone = false;
}

Group.prototype.done = function done() {
	if (this.isDone) {
		return;
	}
	this.isDone = true;
	//Don't go to next step if no args is passed
	if (this.args.length != 1) {
		this.callback.apply(null, this.args);
	}
};

Group.prototype.error = function error(err) {
	if (this.isDone) {
		return;
	}
	this.isDone = true;
	this.errorCallback(err);
};

// Simple utility for passing a sync value to the next step.
Group.prototype.pass = function pass() {
	var values = slice.call(arguments);
	this.args = this.args.concat(values);
};

// Register a slot in the next step and return a callback
Group.prototype.slot = function slot() {
	var group = this;
	var index = group.args.push(undefined) - 1;
	group.left++;
	return function (err, data) {
		process.nextTick(function() {
			if (err) {
				return group.error(err);
			}
			group.args[index] = data;
			if (!--group.left) {
				group.done();
			}
		});
	};
};

// Creates a nested group where several callbacks go into a single array.
Group.prototype.makeGroup = function makeGroup() {
	var group = this;
	var index = group.args.push(undefined) - 1;
	group.left++;
	return new Group(function (err) {
		if (err) {
			return group.error(err);
		}
		var data = slice.call(arguments, 1);
		group.args[index] = data;
		if (!--group.left) {
			group.done();
		}
	});
};

/**
 * TwoStep section
 */

// Execute steps immediately
function TwoStep() {
	exec(slice.call(arguments), []);
}

// Create a composite function with steps built-in
TwoStep.fn = function () {
	var steps = slice.call(arguments);
	return function () {
		var args = slice.call(arguments);
		var callback = args.pop();
		exec(steps, args, callback);
	};
};

// Stepper function
function exec(steps, args, callback) {
	var pos = 0;
	function next() {
		var step = steps[pos++];
		if (!step) {
			callback && callback.apply(null, arguments);
			return;
		}
		var group = new Group(next);
		try {
			step.apply(group, arguments);
		} catch (e) {
			group.error(e);
			return;
		}
		if (group.left === 0) {
			group.done();
		}
	};
	next.apply(null, args);
}

// Expose just for fun and extensibility
TwoStep.Group = Group;

//Simple step function, just return callack
TwoStep.simple = function(callback) {
	return callback;
};

//If error happens somewhere after this step, just throw Error next
TwoStep.throwIfError = function(callback) {
	return function() {
		if (arguments[0]) {
			throw arguments[0];
		}
		return callback.apply(this, arguments);
	};
};

/**
 * Steppy section
 */

// Execute steps immediately
function Steppy() {
	execSteppy(slice.call(arguments), []);
}

// Stepper function
function execSteppy(steps, args, callback) {
	var pos = 0;

	function next() {
		var step = steps[pos++];
		if (!step) {
			return callback && callback.apply(null, arguments);
		}
		//trow error from any step to the last, error handling step,
		//and from the last step rethrow error outside Steppy
		var errorHandler = (pos < steps.length) ?
			steps[steps.length - 1] : function(err) {throw err};

		var group = new Group(next, errorHandler);
		try {
			step.apply(group, arguments);
		} catch (e) {
			return group.error(e);
		}
		if (group.left === 0) {
			group.done();
		}
	};
	next.apply(null, args);
}

// Expose just for fun and extensibility
Steppy.Group = Group;
<|endoftext|>


<commit_before><commit_msg>add back Patch to work-around not defined errors during compilation of nodejs code<commit_after><|endoftext|>


<commit_before>/* global describe, it, xit */
/* jslint node: true, esnext: true */

"use strict";

const chai = require('chai'),
  assert = chai.assert,
  expect = chai.expect,
  should = chai.should(),
  events = require('events'),
  util = require('util'),
  rgm = require('../RegistryMixin');

class Interceptor {
  static get name() {
    return "t1";
  }

  constructor(arg1, arg2) {
    if (arg2) {
      this.arg1 = arg2;
    } else
      this.arg1 = arg1;
  }
}

const InterceptorFactory = {
  type: 't1',
  createInstance(arg1, arg2) {
    if (arg2) {
      return {
        arg1: arg2
      };
    }

    return {
      arg1: arg1
    };
  }
};

function MyEmitter() {
  events.call(this);
}
util.inherits(MyEmitter, events);

describe('RegistrarMixin', () => {

  describe('empty', () => {
    const object = new MyEmitter();

    rgm.defineRegistryProperties(object, 'interceptor', {
      withCreateInstance: true,
      factoryType: 'new'
    });

    it('no entries', () => assert.deepEqual(object.interceptors, {}));
  });

  testRegistry('class', Interceptor, {
    withCreateInstance: true,
    factoryType: 'new',
    hasBeenRegistered: i =>
      i.hasBeenRegisteredCalled = true
  });

  testRegistry('function', InterceptorFactory, {
    withCreateInstance: true,
    factoryType: 'object',
    'factoryMethod': 'createInstance'
  });
});

function testRegistry(name, factory, registryOptions) {
  describe(`${name} entries`, () => {
    const object = new MyEmitter();

    rgm.defineRegistryProperties(object, 'interceptor', registryOptions);

    let registered;
    object.addListener('interceptorRegistered', r => registered = r);

    object.registerInterceptor(factory);

    describe('registered event', () => {
      it('send', () => assert.equal(registered, factory));
    });

    if (registryOptions.hasBeenRegistered) {
      describe('hasBeenRegistered', () => {
        it('is called', () => assert.equal(factory.hasBeenRegisteredCalled, true));
      });
    }

    it('has one entry', () => assert.equal(object.interceptors.t1, factory));

    describe('create instance', () => {
      const inst1 = object.createInterceptorInstance("t1", "arg1");
      it('created', () => assert.equal(inst1.arg1, "arg1"));
    });

    describe('create instance from config', () => {
      const inst1 = object.createInterceptorInstanceFromConfig({
        type: "t1",
        "someOtherArgs": 1
      }, "arg1");
      it('created', () => assert.equal(inst1.arg1, "arg1"));
    });

    describe('unregister', () => {
      it('entry missing', () => {
        let unregistered;
        object.addListener('interceptorUnregistered', ur => unregistered = ur);
        object.unregisterInterceptor("t1");
        assert.equal(object.interceptors.t1, undefined);
        assert.equal(unregistered, factory);
      });
    });

    object.registerInterceptor(factory);
    it('one entry still there', () => assert.equal(object.interceptors.t1, factory));
  });
}
<commit_msg>test: check willBeUnregistered too<commit_after>/* global describe, it, xit */
/* jslint node: true, esnext: true */

"use strict";

const chai = require('chai'),
  assert = chai.assert,
  expect = chai.expect,
  should = chai.should(),
  events = require('events'),
  util = require('util'),
  rgm = require('../RegistryMixin');

class Interceptor {
  static get name() {
    return "t1";
  }

  constructor(arg1, arg2) {
    if (arg2) {
      this.arg1 = arg2;
    } else
      this.arg1 = arg1;
  }
}

const InterceptorFactory = {
  type: 't1',
  createInstance(arg1, arg2) {
    if (arg2) {
      return {
        arg1: arg2
      };
    }

    return {
      arg1: arg1
    };
  }
};

function MyEmitter() {
  events.call(this);
}
util.inherits(MyEmitter, events);

describe('RegistrarMixin', () => {

  describe('empty', () => {
    const object = new MyEmitter();

    rgm.defineRegistryProperties(object, 'interceptor', {
      withCreateInstance: true,
      factoryType: 'new'
    });

    it('no entries', () => assert.deepEqual(object.interceptors, {}));
  });

  testRegistry('class', Interceptor, {
    withCreateInstance: true,
    factoryType: 'new',
    hasBeenRegistered: i => i.hasBeenRegisteredCalled = true,
    willBeUnregistered: i => i.willBeUnregisteredCalled = true
  });

  testRegistry('function', InterceptorFactory, {
    withCreateInstance: true,
    factoryType: 'object',
    'factoryMethod': 'createInstance'
  });
});

function testRegistry(name, factory, registryOptions) {
  describe(`${name} entries`, () => {
    const object = new MyEmitter();

    rgm.defineRegistryProperties(object, 'interceptor', registryOptions);

    let registered;
    object.addListener('interceptorRegistered', r => registered = r);

    object.registerInterceptor(factory);

    describe('registered event', () => {
      it('send', () => assert.equal(registered, factory));
    });

    if (registryOptions.hasBeenRegistered) {
      describe('hasBeenRegistered', () => {
        it('is called', () => assert.equal(factory.hasBeenRegisteredCalled, true));
      });
    }

    it('has one entry', () => assert.equal(object.interceptors.t1, factory));

    describe('create instance', () => {
      const inst1 = object.createInterceptorInstance("t1", "arg1");
      it('created', () => assert.equal(inst1.arg1, "arg1"));
    });

    describe('create instance from config', () => {
      const inst1 = object.createInterceptorInstanceFromConfig({
        type: "t1",
        "someOtherArgs": 1
      }, "arg1");
      it('created', () => assert.equal(inst1.arg1, "arg1"));
    });

    describe('unregister', () => {
      it('entry missing', () => {
        let unregistered;
        object.addListener('interceptorUnregistered', ur => unregistered = ur);
        object.unregisterInterceptor("t1");
        assert.equal(object.interceptors.t1, undefined);
        assert.equal(unregistered, factory);
      });

      if (registryOptions.willBeUnregistered) {
        describe('willBeUnregistered', () => {
          it('is called', () => assert.equal(factory.willBeUnregisteredCalled, true));
        });
      }
    });

    object.registerInterceptor(factory);
    it('one entry still there', () => assert.equal(object.interceptors.t1, factory));
  });
}
<|endoftext|>