<commit_before>/**
 * The function `reverse` computes the reversal
 * of a given `String` (don't copy-pasta!)
 *
 * @param {String} S to reverse
 * @return {String}
 */
function reverse(S){
      // YOUR CODE HERE: NO INTERNET COPY-PASTA!
    var newString = S;
    var reversed = "";
    for (var i = newString.length-1; i >= 0; i--){
      reversed = reversed + newString[i];
    }
    return reversed;
}

console.assert(reverse("") === ""); // Really?

console.assert(reverse("A") === "A"); // Jerk.

console.assert(reverse("cat") === "tac");

console.assert(reverse("ward") === "draw");

console.assert(reverse("books") === "skoob"); // Rokey, dokey...

console.assert(
  reverse("we don't want no trouble") === "elbuort on tnaw t'nod ew"
);

/**
 * Function `encode` accepts a `String` and produces
 * the appropriate ROT13 "encoded" version, i.e. every
 * character in `phrase` is "rotated" ahead by 13 characters.
 *
 * @see String.prototype.charCodeAt
 * @see String.prototype.fromCharCode
 * @see http://en.wikipedia.org/wiki/ROT13
 *
 * // Start with just `phrase`...
 * @param {String} phrase to encode
 * // Add `N` in part 2!
 * // @param {Number} N rotation to apply, default 13
 * @return {String} encoded with ROT13
 */
function encode(phrase,N ){
    var rot = N || 13;
    var oldString = phrase;
    var newString = "";
    for (var i in oldString){
      var currentNum = oldString.charCodeAt(i);
      var newNum = currentNum + rot;
      if (newNum > 122) {
        newNum = newNum - 26;
      }
      newString = newString + String.fromCharCode(newNum);
    }
    return newString;
    // YOUR CODE HERE... EMPHASIS ON **YOUR**
}

/**
 * Function `decode` accepts a `phrase` and `N` and
 * decoded it appropriately, i.e. every _word_ character
 * in `phrase` is rotated backward by `N` characters.
 *
 * @param {String} phrase to decode
 * @param {Number} N rotation to apply, default 13
 * @return {String} decoded by ROT-N
 */
function decode(phrase, N){
  var rot = N || 13;
  var oldString = phrase;
  var newString = "";
  for (var i in oldString){
    var currentNum = oldString.charCodeAt(i);
    var newNum = currentNum - rot;
    if (newNum < 97) {
      newNum = newNum + 26;
    }
    newString = newString + String.fromCharCode(newNum);
  }
  return newString;
  // YOUR CODE HERE... EMPHASIS ON **YOUR**
}

// Produce more examples, please...
console.assert(encode("abc") === "nop");
console.assert(encode("hello") === "uryyb");
console.assert(encode("uryyb") === "hello");

console.assert(encode("hello", 2) === "jgnnq")
console.assert(decode("jgnnq", 2) === "hello")
<commit_msg>ROT13 more samples including mirror velma<commit_after>/**
 * The function `reverse` computes the reversal
 * of a given `String` (don't copy-pasta!)
 *
 * @param {String} S to reverse
 * @return {String}
 */
function reverse(S){
      // YOUR CODE HERE: NO INTERNET COPY-PASTA!
    var newString = S;
    var reversed = "";
    for (var i = newString.length-1; i >= 0; i--){
      reversed = reversed + newString[i];
    }
    return reversed;
}

console.assert(reverse("") === ""); // Really?

console.assert(reverse("A") === "A"); // Jerk.

console.assert(reverse("cat") === "tac");

console.assert(reverse("ward") === "draw");

console.assert(reverse("books") === "skoob"); // Rokey, dokey...

console.assert(
  reverse("we don't want no trouble") === "elbuort on tnaw t'nod ew"
);

/**
 * Function `encode` accepts a `String` and produces
 * the appropriate ROT13 "encoded" version, i.e. every
 * character in `phrase` is "rotated" ahead by 13 characters.
 *
 * @see String.prototype.charCodeAt
 * @see String.prototype.fromCharCode
 * @see http://en.wikipedia.org/wiki/ROT13
 *
 * // Start with just `phrase`...
 * @param {String} phrase to encode
 * // Add `N` in part 2!
 * // @param {Number} N rotation to apply, default 13
 * @return {String} encoded with ROT13
 */
function encode(phrase,N ){
    var rot = N || 13;
    var oldString = phrase;
    var newString = "";
    for (var i in oldString){
      var currentNum = oldString.charCodeAt(i);
      var newNum = currentNum + rot;
      if (newNum > 122) {
        newNum = newNum - 26;
      }
      newString = newString + String.fromCharCode(newNum);
    }
    return newString;
    // YOUR CODE HERE... EMPHASIS ON **YOUR**
}

/**
 * Function `decode` accepts a `phrase` and `N` and
 * decoded it appropriately, i.e. every _word_ character
 * in `phrase` is rotated backward by `N` characters.
 *
 * @param {String} phrase to decode
 * @param {Number} N rotation to apply, default 13
 * @return {String} decoded by ROT-N
 */
function decode(phrase, N){
  var rot = N || 13;
  var oldString = phrase;
  var newString = "";
  for (var i in oldString){
    var currentNum = oldString.charCodeAt(i);
    var newNum = currentNum - rot;
    if (newNum < 97) {
      newNum = newNum + 26;
    }
    newString = newString + String.fromCharCode(newNum);
  }
  return newString;
  // YOUR CODE HERE... EMPHASIS ON **YOUR**
}

// Produce more examples, please...
console.assert(encode("abc") === "nop");
console.assert(encode("hello") === "uryyb");
console.assert(encode("uryyb") === "hello");

console.assert(encode("velma") === "iryzn");

console.assert(encode("hello", 2) === "jgnnq")
console.assert(encode("hello", 8) === "pmttw")
console.assert(decode("jgnnq", 2) === "hello")

console.assert(decode("velma") === "iryzn")
<|endoftext|>


<commit_before>/**
 * The function `reverse` computes the reversal
 * of a given `String` (don't copy-pasta!)
 *
 * @param {String} S to reverse
 * @return {String}
 */
function reverse(S){
  var empty = "";
  for (var i = S.length - 1; i >= 0; i--) {
    empty += S[i]; }
return empty;

};
    // YOUR CODE HERE: NO INTERNET COPY-PASTA!

// console.assert(reverse("") === ""); // Really?

console.assert(reverse("A") === "A"); // Jerk.

console.assert(reverse("cat") === "tac");

console.assert(reverse("ward") === "draw");

console.assert(reverse("books") === "skoob"); // Rokey, dokey...

console.assert(
  reverse("we don't want no trouble") === "elbuort on tnaw t'nod ew"
);

/**
 * Function `encode` accepts a `String` and produces
 * the appropriate ROT13 "encoded" version, i.e. every
 * character in `phrase` is "rotated" ahead by 13 characters.
 *
 * @see String.prototype.charCodeAt
 * @see String.prototype.fromCharCode
 * @see http://en.wikipedia.org/wiki/ROT13
 *
 * // Start with just `phrase`...
 * @param {String} phrase to encode
 * // Add `N` in part 2!
 * // @param {Number} N rotation to apply, default 13
 * @return {String} encoded with ROT13
 */

function encode(phrase, N){
  var code = phrase.split("");
  for (var i=0; i < code.length; i++) {
    code[i] = code[i].charCodeAt() + N;
    if (code[i] > 122) {
      code[i] = String.fromCharCode(code[i] - 26);
    } else {
      code[i] = String.fromCharCode(code[i]);
    }
  }
  return code.join("");
}

// console.assert(encode("hello", 2) === "106", "103", "110", "110", "113");
// console.assert(encode("a") === 97);
/**
 * Function `decode` accepts a `phrase` and `N` and
 * decoded it appropriately, i.e. every _word_ character
 * in `phrase` is rotated backward by `N` characters.
 *
 * @param {String} phrase to decode
 * @param {Number} N rotation to apply, default 13
 * @return {String} decoded by ROT-N
 */
function decode(phrase, N){
 var code = phrase.split("");
 for (var i=0; i < code.length; i++) {
   code[i] = code[i].charCodeAt() - N;
   if (code[i] < 97) {
     code[i] = String.fromCharCode(code[i] + 26);
   } else {
     code[i] = String.fromCharCode(code[i]);
   }
 }
 return code.join("");
}

// Produce more examples, please...

// console.assert(encode("hello") === "uryyb"); ADDED N VALUE
// console.assert(encode("uryyb") === "hello"); ADDED N VALUE
console.assert(encode("bye", 2) === "dag")
console.assert(encode("hello", 2) === "jgnnq")
console.assert(decode("jgnnq", 2) === "hello")
console.assert(decode("dag", 2) === "bye")
<commit_msg>Final check passed<commit_after>/**
 * The function `reverse` computes the reversal
 * of a given `String` (don't copy-pasta!)
 *
 * @param {String} S to reverse
 * @return {String}
 */
function reverse(S){
  var empty = "";
  for (var i = S.length - 1; i >= 0; i--) {
    empty += S[i]; }
return empty;

};
    // YOUR CODE HERE: NO INTERNET COPY-PASTA!

console.assert(reverse("") === ""); // Really?

console.assert(reverse("A") === "A"); // Jerk.

console.assert(reverse("cat") === "tac");

console.assert(reverse("ward") === "draw");

console.assert(reverse("books") === "skoob"); // Rokey, dokey...

console.assert(
  reverse("we don't want no trouble") === "elbuort on tnaw t'nod ew"
);

/**
 * Function `encode` accepts a `String` and produces
 * the appropriate ROT13 "encoded" version, i.e. every
 * character in `phrase` is "rotated" ahead by 13 characters.
 *
 * @see String.prototype.charCodeAt
 * @see String.prototype.fromCharCode
 * @see http://en.wikipedia.org/wiki/ROT13
 *
 * // Start with just `phrase`...
 * @param {String} phrase to encode
 * // Add `N` in part 2!
 * // @param {Number} N rotation to apply, default 13
 * @return {String} encoded with ROT13
 */

function encode(phrase, N){
  var code = phrase.split("");
  for (var i=0; i < code.length; i++) {
    code[i] = code[i].charCodeAt() + N;
    if (code[i] > 122) {
      code[i] = String.fromCharCode(code[i] - 26);
    } else {
      code[i] = String.fromCharCode(code[i]);
    }
  }
  return code.join("");
}

// console.assert(encode("hello", 2) === "106", "103", "110", "110", "113");
// console.assert(encode("a") === 97);
/**
 * Function `decode` accepts a `phrase` and `N` and
 * decoded it appropriately, i.e. every _word_ character
 * in `phrase` is rotated backward by `N` characters.
 *
 * @param {String} phrase to decode
 * @param {Number} N rotation to apply, default 13
 * @return {String} decoded by ROT-N
 */
function decode(phrase, N){
 var code = phrase.split("");
 for (var i=0; i < code.length; i++) {
   code[i] = code[i].charCodeAt() - N;
   if (code[i] < 97) {
     code[i] = String.fromCharCode(code[i] + 26);
   } else {
     code[i] = String.fromCharCode(code[i]);
   }
 }
 return code.join("");
}

// Produce more examples, please...

// console.assert(encode("hello") === "uryyb"); ADDED N VALUE
// console.assert(encode("uryyb") === "hello"); ADDED N VALUE
console.assert(encode("bye", 2) === "dag")
console.assert(encode("hello", 2) === "jgnnq")
console.assert(decode("jgnnq", 2) === "hello")
console.assert(decode("dag", 2) === "bye")
<|endoftext|>


<commit_before>'use strict';

const co           =  require('co');
const debug        =  require('debug')('shiba');
const debugblock   =  require('debug')('shiba:blocknotify');
const debugautomute = require('debug')('shiba:automute');

const profanity    =  require('./profanity');
const Blockchain   =  require('./Util/Blockchain');
const Unshort      =  require('./Util/Unshort');

const Client       =  require('./Client');
const Lib          =  require('./Lib');
const Config       =  require('./Config');
const Pg           =  require('./Pg');

const CmdAutomute  =  require('./Cmd/Automute');
const CmdConvert   =  require('./Cmd/Convert');
const CmdCrash     =  require('./Cmd/Crash');
const CmdMedian    =  require('./Cmd/Median');
const CmdStreak    =  require('./Cmd/Streak');

const mkAutomuteStore = require('./Store/Automute');
const mkChatStore     = require('./Store/Chat');

// Command syntax
const cmdReg = /^\s*!([a-zA-z]*)\s*(.*)$/i;

function Shiba() {

  let self = this;

  co(function*(){
    // Last received block information.
    self.block = yield* Pg.getLatestBlock();
    // Awkward name for an array that holds names of users which
    // will be when a new block has been mined.
    self.blockNotifyUsers = yield* Pg.getBlockNotifications();

    // List of automute regexps
    self.automuteStore = yield* mkAutomuteStore();
    self.chatStore     = yield* mkChatStore();

    self.cmdAutomute    = new CmdAutomute(self.automuteStore);
    self.cmdConvert     = new CmdConvert();
    self.cmdCrash       = new CmdCrash();
    self.cmdMedian      = new CmdMedian();
    self.cmdStreak      = new CmdStreak();

    // Connect to the site.
    self.client = new Client(Config);

    // Setup the chat bindings.
    self.client.on('join', co.wrap(function*(data) {
      yield* self.chatStore.mergeMessages(data.chat);
    }));
    self.client.on('msg', co.wrap(self.chatStore.addMessage.bind(self.chatStore)));
    self.client.on('msg', co.wrap(self.onMsg.bind(self)));
    self.setupScamComment();
    self.setupBlockchain();
  }).catch(function(err) {
    // Abort immediately on startup.
    console.error(err);
    throw err;
  });
}

Shiba.prototype.setupScamComment = function() {
  let self = this;
  self.client.on('game_crash', function(data) {
    let gameInfo = self.client.getGameInfo();
    console.assert(gameInfo.hasOwnProperty('verified'));
    if (gameInfo.verified !== 'ok') {
      self.client.doSay('wow. such scam. very hash failure.');
    }
  });
};

Shiba.prototype.setupBlockchain = function() {
  this.blockchain = new Blockchain();
  this.blockchain.on('block', this.onBlock.bind(this));
};

Shiba.prototype.onBlock = function(block) {
  let newBlock =
    { height: block.height,
      hash: block.hash,
      confirmation: new Date(block.time*1000),
      notification: new Date()
    };

  let self = this;
  co(function*(){
    yield* Pg.putBlock(newBlock);

    // Check if block is indeed new and only signal in this case.
    if (newBlock.height > self.block.height) {
      self.block = newBlock;

      if (self.blockNotifyUsers.length > 0) {
        let users = self.blockNotifyUsers.join(': ') + ': ';
        let line = users + 'Block #' + newBlock.height + ' mined.';
        self.client.doSay(line);
        self.blockNotifyUsers = [];
        yield* Pg.clearBlockNotifications();
      }
    }
  }).catch(err => console.error('[ERROR] onBlock:', err));
};

Shiba.prototype.checkAutomute = function*(msg) {

  // Match entire message against the regular expressions.
  let automutes = this.automuteStore.get();
  if (automutes.find(r => msg.message.match(r))) {
    this.client.doMute(msg.username, '36h');
    return true;
  }

  // Extract a list of URLs.
  // TODO: The regular expression could be made more intelligent.
  let urls  = msg.message.match(/https?:\/\/[^\s]+/ig) || [];
  let urls2 = msg.message.match(/(\s|^)(bit.ly|vk.cc|goo.gl)\/[^\s]+/ig) || [];
  urls2     = urls2.map(x => x.replace(/^\s*/,'http:\/\/'));
  urls      = urls.concat(urls2);

  // No URLs found.
  if (urls.length === 0) return false;

  // Unshorten extracted URLs.
  urls2 = yield* Unshort.unshorts(urls);
  urls  = urls.concat(urls2 || []);

  debugautomute('Url list: ' + JSON.stringify(urls));

  for (let url of urls) {
    debugautomute('Checking url: ' + url);

    // Run the regular expressions against the unshortened url.
    let r = automutes.find(r => url.match(r));
    if (r) {
      debugautomute('URL matched ' + r);
      this.client.doMute(msg.username, '72h');
      return true;
    }
  }

  return false;
};

Shiba.prototype.checkRate = function*(msg) {
  let rates = [{count: 4, seconds: 1, mute: '15m'},
               {count: 5, seconds: 5, mute: '15m'},
               {count: 8, seconds: 12, mute: '15m'}
              ];

  let self = this;
  let rate = rates.find(rate => {
    let after    = new Date(Date.now() - rate.seconds*1000);
    let messages = self.chatStore.getChatMessages(msg.username, after);
    return messages.length > rate.count;
  });

  if (rate) {
    this.client.doMute(msg.username, rate.mute);
    return true;
  }

  return false;
};

Shiba.prototype.onMsg = function*(msg) {
  if (msg.type === 'say')
    return yield* this.onSay(msg);
};

Shiba.prototype.onSay = function*(msg) {
  if (msg.username === this.client.username) return;

  if (yield* this.checkAutomute(msg)) return;
  if (yield* this.checkRate(msg)) return;

  // Everything checked out fine so far. Continue with the command
  // processing phase.
  let cmdMatch = msg.message.match(cmdReg);
  if (cmdMatch) yield* this.onCmd(msg, cmdMatch[1], cmdMatch[2]);
};

Shiba.prototype.checkCmdRate = function*(msg) {
  let after    = new Date(Date.now() - 10*1000);
  let messages = this.chatStore.getChatMessages(msg.username, after);

  let count = 0;
  messages.forEach(m => { if (m.message.match(cmdReg)) ++count; });

  if (count >= 5) {
    this.client.doMute(msg.username, '5m');
    return true;
  } else if (count >= 4) {
    this.client.doSay('bites ' + msg.username);
    return true;
  }

  return false;
};

Shiba.prototype.onCmd = function*(msg, cmd, rest) {
  // Cmd rate limiter
  if (yield* this.checkCmdRate(msg)) return;

  switch(cmd.toLowerCase()) {
  case 'custom':
    yield* this.onCmdCustom(msg, rest);
    break;
  case 'lick':
  case 'lck':
  case 'lic':
  case 'lik':
  case 'lk':
    yield* this.onCmdLick(msg, rest);
    break;
  case 'seen':
  case 'sen':
  case 'sn':
  case 's':
    yield* this.onCmdSeen(msg, rest);
    break;
  case 'faq':
  case 'help':
    yield* this.onCmdHelp(msg, rest);
    break;
  case 'convert':
  case 'conver':
  case 'conv':
  case 'cv':
  case 'c':
    yield* this.onCmdConvert(msg, rest);
    break;
  case 'block':
  case 'blck':
  case 'blk':
  case 'bl':
    yield* this.onCmdBlock(msg, rest);
    break;
  case 'crash':
  case 'cras':
  case 'crsh':
  case 'cra':
  case 'cr':
    yield* this.cmdCrash.handle(this.client, msg, rest);
    break;
  case 'automute':
    yield* this.cmdAutomute.handle(this.client, msg, rest);
    break;
  case 'median':
  case 'med':
    yield* this.cmdMedian.handle(this.client, msg, rest);
    break;
  case 'streak':
    yield* this.cmdStreak.handle(this.client, msg, rest);
    break;
  }
};

Shiba.prototype.onCmdHelp = function*(msg, rest) {
  this.client.doSay(
      'very explanation. much insight: ' +
      'https://github.com/moneypot/shiba/wiki/');
};

Shiba.prototype.onCmdCustom = function*(msg, rest) {
  if (msg.role !== 'admin' &&
      msg.role !== 'moderator') return;

  let customReg   = /^([a-z0-9_\-]+)\s+(.*)$/i;
  let customMatch = rest.match(customReg);

  if (!customMatch) {
    this.client.doSay('wow. very usage failure. such retry');
    this.client.doSay('so example, very cool: !custom Ryan very dog lover');
    return;
  }

  let customUser  = customMatch[1];
  let customMsg   = customMatch[2];

  try {
    yield* Pg.putLick(customUser, customMsg, msg.username);
    this.client.doSay('wow. so cool. very obedient');
  } catch(err) {
    console.error('[ERROR] onCmdCustom:', err.stack);
    this.client.doSay('wow. such database fail');
  }
};

Shiba.prototype.onCmdLick = function*(msg, user) {
  user = user.toLowerCase();
  user = user.replace(/^\s+|\s+$/g,'');

  // We're cultivated and don't lick ourselves.
  if (user === this.client.username.toLowerCase()) return;

  if (profanity[user]) {
    this.client.doSay('so trollol. very annoying. such mute');
    this.client.doMute(msg.username, '5m');
    return;
  }

  try {
    // Get licks stored in the DB.
    let data     = yield* Pg.getLick(user);
    let username = data.username;
    let customs  = data.licks;

    // Add standard lick to the end.
    customs.push('licks ' + username);

    // Randomly pick a lick message with lower probability of the
    // standard one.
    let r = Math.random() * (customs.length - 0.8);
    let m = customs[Math.floor(r)];
    this.client.doSay(m);
  } catch(err) {
    if (err === 'USER_DOES_NOT_EXIST')
      this.client.doSay('very stranger. never seen');
    else
      console.error('[ERROR] onCmdLick:', err);
  }
};

Shiba.prototype.onCmdSeen = function*(msg, user) {
  user = user.toLowerCase().replace(/^\s+|\s+$/g,'');

  // Make sure the username is valid
  if (Lib.isInvalidUsername(user)) {
    this.client.doSay('such name. very invalid');
    return;
  }

  // In case a user asks for himself.
  if (user === msg.username.toLowerCase()) {
    this.client.doSay('go find a mirror @' + msg.username);
    return;
  }

  // Special treatment of block.
  if (user === 'block') {
    yield* this.onCmdBlock(msg);
    return;
  }

  // Special treatment of rape.
  if (user === 'rape') {
    yield* this.cmdCrash.handle(this.client, msg, '< 1.05');
    return;
  }

  // Somebody asks us when we've seen ourselves.
  if (user === this.client.username.toLowerCase()) {
    this.client.doSay('strange loops. much confusion.');
    return;
  }

  if (profanity[user]) {
    this.client.doSay('so trollol. very annoying. such mute');
    this.client.doMute(msg.username, '5m');
    return;
  }

  let message;
  try {
    message = yield* Pg.getLastSeen(user);
  } catch(err) {
    if (err === 'USER_DOES_NOT_EXIST')
      this.client.doSay('very stranger. never seen');
    else {
      console.error('[ERROR] onCmdSeen:', err.stack);
      this.client.doSay('wow. such database fail');
    }
    return;
  }

  if (!message.time) {
    // User exists but hasn't said a word.
    this.client.doSay('very silent. never spoken');
    return;
  }

  let diff = Date.now() - message.time;
  let line;
  if (diff < 1000) {
    line = 'Seen ' + message.username + ' just now.';
  } else {
    line = 'Seen ' + message.username + ' ';
    line += Lib.formatTimeDiff(diff);
    line += ' ago.';
  }
  this.client.doSay(line);
};

Shiba.prototype.onCmdConvert = function*(msg, conv) {
  yield* this.cmdConvert.handle(this.client, msg, conv);
};

Shiba.prototype.onCmdBlock = function*(msg) {
  let time  = this.block.notification;
  let diff  = Date.now() - time;

  let line = 'Seen block #' + this.block.height;
  if (diff < 1000) {
    line += ' just now.';
  } else {
    line += ' ';
    line += Lib.formatTimeDiff(diff);
    line += ' ago.';
  }

  // Add the user to the list of users being notified about a new block.
  if (this.blockNotifyUsers.indexOf(msg.username) < 0) {
    debugblock("Adding user '%s' to block notfy list", msg.username);
    this.blockNotifyUsers.push(msg.username);
    yield* Pg.putBlockNotification(msg.username);
  } else {
    debugblock("User '%s' is already on block notfy list", msg.username);
    line += ' ' + msg.username + ': Have patience!';
  }

  this.client.doSay(line);
};

let shiba = new Shiba();
<commit_msg>Minor cleanups<commit_after>'use strict';

const co           =  require('co');
const debug        =  require('debug')('shiba');
const debugblock   =  require('debug')('shiba:blocknotify');
const debugautomute = require('debug')('shiba:automute');

const profanity    =  require('./profanity');
const Blockchain   =  require('./Util/Blockchain');
const Unshort      =  require('./Util/Unshort');

const Client       =  require('./Client');
const Lib          =  require('./Lib');
const Config       =  require('./Config');
const Pg           =  require('./Pg');

const CmdAutomute  =  require('./Cmd/Automute');
const CmdConvert   =  require('./Cmd/Convert');
const CmdCrash     =  require('./Cmd/Crash');
const CmdMedian    =  require('./Cmd/Median');
const CmdStreak    =  require('./Cmd/Streak');

const mkAutomuteStore = require('./Store/Automute');
const mkChatStore     = require('./Store/Chat');

// Command syntax
const cmdReg = /^\s*!([a-zA-z]*)\s*(.*)$/i;

function Shiba() {

  let self = this;

  co(function*(){
    // Last received block information.
    self.block = yield* Pg.getLatestBlock();
    // Awkward name for an array that holds names of users which
    // will be when a new block has been mined.
    self.blockNotifyUsers = yield* Pg.getBlockNotifications();

    // List of automute regexps
    self.automuteStore = yield* mkAutomuteStore();
    self.chatStore     = yield* mkChatStore();

    self.cmdAutomute = new CmdAutomute(self.automuteStore);
    self.cmdConvert  = new CmdConvert();
    self.cmdCrash    = new CmdCrash();
    self.cmdMedian   = new CmdMedian();
    self.cmdStreak   = new CmdStreak();

    // Connect to the site.
    self.client = new Client(Config);

    // Setup the chat bindings.
    self.client.on('join', co.wrap(function*(data) {
      yield* self.chatStore.mergeMessages(data.chat);
    }));
    self.client.on('msg', co.wrap(self.chatStore.addMessage.bind(self.chatStore)));
    self.client.on('msg', co.wrap(self.onMsg.bind(self)));

    self.setupScamComment();
    self.setupBlockchain();
  }).catch(function(err) {
    // Abort immediately when an exception is thrown on startup.
    console.error(err.stack);
    throw err;
  });
}

Shiba.prototype.setupScamComment = function() {
  let self = this;
  self.client.on('game_crash', function(data) {
    let gameInfo = self.client.getGameInfo();
    console.assert(gameInfo.hasOwnProperty('verified'));

    if (gameInfo.verified !== 'ok')
      self.client.doSay('wow. such scam. very hash failure.');
  });
};

Shiba.prototype.setupBlockchain = function() {
  this.blockchain = new Blockchain();
  this.blockchain.on('block', this.onBlock.bind(this));
};

Shiba.prototype.onBlock = function(block) {
  let newBlock =
    { height: block.height,
      hash: block.hash,
      confirmation: new Date(block.time*1000),
      notification: new Date()
    };

  let self = this;
  co(function*(){
    yield* Pg.putBlock(newBlock);

    // Check if block is indeed new and only signal in this case.
    if (newBlock.height > self.block.height) {
      self.block = newBlock;

      if (self.blockNotifyUsers.length > 0) {
        let users = self.blockNotifyUsers.join(': ') + ': ';
        let line = users + 'Block #' + newBlock.height + ' mined.';
        self.client.doSay(line);
        self.blockNotifyUsers = [];
        yield* Pg.clearBlockNotifications();
      }
    }
  }).catch(err => console.error('[ERROR] onBlock:', err));
};

Shiba.prototype.checkAutomute = function*(msg) {

  // Match entire message against the regular expressions.
  let automutes = this.automuteStore.get();
  if (automutes.find(r => msg.message.match(r))) {
    this.client.doMute(msg.username, '36h');
    return true;
  }

  // Extract a list of URLs.
  // TODO: The regular expression could be made more intelligent.
  let urls  = msg.message.match(/https?:\/\/[^\s]+/ig) || [];
  let urls2 = msg.message.match(/(\s|^)(bit.ly|vk.cc|goo.gl)\/[^\s]+/ig) || [];
  urls2     = urls2.map(x => x.replace(/^\s*/,'http:\/\/'));
  urls      = urls.concat(urls2);

  // No URLs found.
  if (urls.length === 0) return false;

  // Unshorten extracted URLs.
  urls2 = yield* Unshort.unshorts(urls);
  urls  = urls.concat(urls2 || []);

  debugautomute('Url list: ' + JSON.stringify(urls));

  for (let url of urls) {
    debugautomute('Checking url: ' + url);

    // Run the regular expressions against the unshortened url.
    let r = automutes.find(r => url.match(r));
    if (r) {
      debugautomute('URL matched ' + r);
      this.client.doMute(msg.username, '72h');
      return true;
    }
  }

  return false;
};

Shiba.prototype.checkRate = function*(msg) {
  let rates = [{count: 4, seconds: 1, mute: '15m'},
               {count: 5, seconds: 5, mute: '15m'},
               {count: 8, seconds: 12, mute: '15m'}
              ];

  let self = this;
  let rate = rates.find(rate => {
    let after    = new Date(Date.now() - rate.seconds*1000);
    let messages = self.chatStore.getChatMessages(msg.username, after);
    return messages.length > rate.count;
  });

  if (rate) {
    this.client.doMute(msg.username, rate.mute);
    return true;
  }

  return false;
};

Shiba.prototype.onMsg = function*(msg) {
  if (msg.type === 'say')
    return yield* this.onSay(msg);
};

Shiba.prototype.onSay = function*(msg) {
  if (msg.username === this.client.username) return;

  if (yield* this.checkAutomute(msg)) return;
  if (yield* this.checkRate(msg)) return;

  // Everything checked out fine so far. Continue with the command
  // processing phase.
  let cmdMatch = msg.message.match(cmdReg);
  if (cmdMatch) yield* this.onCmd(msg, cmdMatch[1], cmdMatch[2]);
};

Shiba.prototype.checkCmdRate = function*(msg) {
  let after    = new Date(Date.now() - 10*1000);
  let messages = this.chatStore.getChatMessages(msg.username, after);

  let count = 0;
  messages.forEach(m => { if (m.message.match(cmdReg)) ++count; });

  if (count >= 5) {
    this.client.doMute(msg.username, '5m');
    return true;
  } else if (count >= 4) {
    this.client.doSay('bites ' + msg.username);
    return true;
  }

  return false;
};

Shiba.prototype.onCmd = function*(msg, cmd, rest) {
  // Cmd rate limiter
  if (yield* this.checkCmdRate(msg)) return;

  switch(cmd.toLowerCase()) {
  case 'custom':
    yield* this.onCmdCustom(msg, rest);
    break;
  case 'lick':
  case 'lck':
  case 'lic':
  case 'lik':
  case 'lk':
    yield* this.onCmdLick(msg, rest);
    break;
  case 'seen':
  case 'sen':
  case 'sn':
  case 's':
    yield* this.onCmdSeen(msg, rest);
    break;
  case 'faq':
  case 'help':
    yield* this.onCmdHelp(msg, rest);
    break;
  case 'convert':
  case 'conver':
  case 'conv':
  case 'cv':
  case 'c':
    yield* this.onCmdConvert(msg, rest);
    break;
  case 'block':
  case 'blck':
  case 'blk':
  case 'bl':
    yield* this.onCmdBlock(msg, rest);
    break;
  case 'crash':
  case 'cras':
  case 'crsh':
  case 'cra':
  case 'cr':
    yield* this.cmdCrash.handle(this.client, msg, rest);
    break;
  case 'automute':
    yield* this.cmdAutomute.handle(this.client, msg, rest);
    break;
  case 'median':
  case 'med':
    yield* this.cmdMedian.handle(this.client, msg, rest);
    break;
  case 'streak':
    yield* this.cmdStreak.handle(this.client, msg, rest);
    break;
  }
};

Shiba.prototype.onCmdHelp = function*(msg, rest) {
  this.client.doSay(
      'very explanation. much insight: ' +
      'https://github.com/moneypot/shiba/wiki/');
};

Shiba.prototype.onCmdCustom = function*(msg, rest) {
  if (msg.role !== 'admin' &&
      msg.role !== 'moderator') return;

  let customReg   = /^([a-z0-9_\-]+)\s+(.*)$/i;
  let customMatch = rest.match(customReg);

  if (!customMatch) {
    this.client.doSay('wow. very usage failure. such retry');
    this.client.doSay('so example, very cool: !custom Ryan very dog lover');
    return;
  }

  let customUser  = customMatch[1];
  let customMsg   = customMatch[2];

  try {
    yield* Pg.putLick(customUser, customMsg, msg.username);
    this.client.doSay('wow. so cool. very obedient');
  } catch(err) {
    console.error('[ERROR] onCmdCustom:', err.stack);
    this.client.doSay('wow. such database fail');
  }
};

Shiba.prototype.onCmdLick = function*(msg, user) {
  user = user.toLowerCase();
  user = user.replace(/^\s+|\s+$/g,'');

  // We're cultivated and don't lick ourselves.
  if (user === this.client.username.toLowerCase()) return;

  if (profanity[user]) {
    this.client.doSay('so trollol. very annoying. such mute');
    this.client.doMute(msg.username, '5m');
    return;
  }

  try {
    // Get licks stored in the DB.
    let data     = yield* Pg.getLick(user);
    let username = data.username;
    let customs  = data.licks;

    // Add standard lick to the end.
    customs.push('licks ' + username);

    // Randomly pick a lick message with lower probability of the
    // standard one.
    let r = Math.random() * (customs.length - 0.8);
    let m = customs[Math.floor(r)];
    this.client.doSay(m);
  } catch(err) {
    if (err === 'USER_DOES_NOT_EXIST')
      this.client.doSay('very stranger. never seen');
    else
      console.error('[ERROR] onCmdLick:', err);
  }
};

Shiba.prototype.onCmdSeen = function*(msg, user) {
  user = user.toLowerCase().replace(/^\s+|\s+$/g,'');

  // Make sure the username is valid
  if (Lib.isInvalidUsername(user)) {
    this.client.doSay('such name. very invalid');
    return;
  }

  // In case a user asks for himself.
  if (user === msg.username.toLowerCase()) {
    this.client.doSay('go find a mirror @' + msg.username);
    return;
  }

  // Special treatment of block.
  if (user === 'block') {
    yield* this.onCmdBlock(msg);
    return;
  }

  // Special treatment of rape.
  if (user === 'rape') {
    yield* this.cmdCrash.handle(this.client, msg, '< 1.05');
    return;
  }

  // Somebody asks us when we've seen ourselves.
  if (user === this.client.username.toLowerCase()) {
    this.client.doSay('strange loops. much confusion.');
    return;
  }

  if (profanity[user]) {
    this.client.doSay('so trollol. very annoying. such mute');
    this.client.doMute(msg.username, '5m');
    return;
  }

  let message;
  try {
    message = yield* Pg.getLastSeen(user);
  } catch(err) {
    if (err === 'USER_DOES_NOT_EXIST')
      this.client.doSay('very stranger. never seen');
    else {
      console.error('[ERROR] onCmdSeen:', err.stack);
      this.client.doSay('wow. such database fail');
    }
    return;
  }

  if (!message.time) {
    // User exists but hasn't said a word.
    this.client.doSay('very silent. never spoken');
    return;
  }

  let diff = Date.now() - message.time;
  let line;
  if (diff < 1000) {
    line = 'Seen ' + message.username + ' just now.';
  } else {
    line = 'Seen ' + message.username + ' ';
    line += Lib.formatTimeDiff(diff);
    line += ' ago.';
  }
  this.client.doSay(line);
};

Shiba.prototype.onCmdConvert = function*(msg, conv) {
  yield* this.cmdConvert.handle(this.client, msg, conv);
};

Shiba.prototype.onCmdBlock = function*(msg) {
  let time  = this.block.notification;
  let diff  = Date.now() - time;

  let line = 'Seen block #' + this.block.height;
  if (diff < 1000) {
    line += ' just now.';
  } else {
    line += ' ';
    line += Lib.formatTimeDiff(diff);
    line += ' ago.';
  }

  // Add the user to the list of users being notified about a new block.
  if (this.blockNotifyUsers.indexOf(msg.username) < 0) {
    debugblock("Adding user '%s' to block notfy list", msg.username);
    this.blockNotifyUsers.push(msg.username);
    yield* Pg.putBlockNotification(msg.username);
  } else {
    debugblock("User '%s' is already on block notfy list", msg.username);
    line += ' ' + msg.username + ': Have patience!';
  }

  this.client.doSay(line);
};

let shiba = new Shiba();
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import {StyleSheet, requireNativeComponent, NativeModules, View, Image} from 'react-native';
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import VideoResizeMode from './VideoResizeMode.js';

const styles = StyleSheet.create({
  base: {
    overflow: 'hidden',
  },
});

export default class Video extends Component {

  constructor(props) {
    super(props);

    this.state = {
      showPoster: true,
    };
  }

  setNativeProps(nativeProps) {
    this._root.setNativeProps(nativeProps);
  }

  seek = (time) => {
    this.setNativeProps({ seek: time });
  };

  presentFullscreenPlayer = () => {
    this.setNativeProps({ fullscreen: true });
  };

  dismissFullscreenPlayer = () => {
    this.setNativeProps({ fullscreen: false });
  };

  _assignRoot = (component) => {
    this._root = component;
  };

  _onLoadStart = (event) => {
    if (this.props.onLoadStart) {
      this.props.onLoadStart(event.nativeEvent);
    }
  };

  _onLoad = (event) => {
    if (this.props.onLoad) {
      this.props.onLoad(event.nativeEvent);
    }
  };

  _onError = (event) => {
    if (this.props.onError) {
      this.props.onError(event.nativeEvent);
    }
  };

  _onProgress = (event) => {
    if (this.props.onProgress) {
      this.props.onProgress(event.nativeEvent);
    }
  };

  _onSeek = (event) => {
    if (this.state.showPoster) {
      this.setState({showPoster: false});
    }

    if (this.props.onSeek) {
      this.props.onSeek(event.nativeEvent);
    }
  };

  _onEnd = (event) => {
    if (this.props.onEnd) {
      this.props.onEnd(event.nativeEvent);
    }
  };

  _onTimedMetadata = (event) => {
    if (this.props.onTimedMetadata) {
      this.props.onTimedMetadata(event.nativeEvent);
    }
  };

  _onFullscreenPlayerWillPresent = (event) => {
    if (this.props.onFullscreenPlayerWillPresent) {
      this.props.onFullscreenPlayerWillPresent(event.nativeEvent);
    }
  };

  _onFullscreenPlayerDidPresent = (event) => {
    if (this.props.onFullscreenPlayerDidPresent) {
      this.props.onFullscreenPlayerDidPresent(event.nativeEvent);
    }
  };

  _onFullscreenPlayerWillDismiss = (event) => {
    if (this.props.onFullscreenPlayerWillDismiss) {
      this.props.onFullscreenPlayerWillDismiss(event.nativeEvent);
    }
  };

  _onFullscreenPlayerDidDismiss = (event) => {
    if (this.props.onFullscreenPlayerDidDismiss) {
      this.props.onFullscreenPlayerDidDismiss(event.nativeEvent);
    }
  };

  _onReadyForDisplay = (event) => {
    if (this.props.onReadyForDisplay) {
      this.props.onReadyForDisplay(event.nativeEvent);
    }
  };

  _onPlaybackStalled = (event) => {
    if (this.props.onPlaybackStalled) {
      this.props.onPlaybackStalled(event.nativeEvent);
    }
  };

  _onPlaybackResume = (event) => {
    if (this.props.onPlaybackResume) {
      this.props.onPlaybackResume(event.nativeEvent);
    }
  };

  _onPlaybackRateChange = (event) => {
    if (this.state.showPoster && (event.nativeEvent.playbackRate !== 0)) {
      this.setState({showPoster: false});
    }

    if (this.props.onPlaybackRateChange) {
      this.props.onPlaybackRateChange(event.nativeEvent);
    }
  };

  _onAudioBecomingNoisy = () => {
    if (this.props.onAudioBecomingNoisy) {
      this.props.onAudioBecomingNoisy();
    }
  };

  _onAudioFocusChanged = (event) => {
    if (this.props.onAudioFocusChanged) {
      this.props.onAudioFocusChanged(event.nativeEvent);
    }
  };

  _onBuffer = (event) => {
    if (this.props.onBuffer) {
      this.props.onBuffer(event.nativeEvent);
    }
  };

  render() {
    const resizeMode = this.props.resizeMode;
    const source = resolveAssetSource(this.props.source) || {};

    let uri = source.uri || '';
    if (uri && uri.match(/^\//)) {
      uri = `file://${uri}`;
    }

    const isNetwork = !!(uri && uri.match(/^https?:/));
    const isAsset = !!(uri && uri.match(/^(assets-library|file|content|ms-appx|ms-appdata):/));

    let nativeResizeMode;
    if (resizeMode === VideoResizeMode.stretch) {
      nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleToFill;
    } else if (resizeMode === VideoResizeMode.contain) {
      nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFit;
    } else if (resizeMode === VideoResizeMode.cover) {
      nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFill;
    } else {
      nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleNone;
    }

    const nativeProps = Object.assign({}, this.props);
    Object.assign(nativeProps, {
      style: [styles.base, nativeProps.style],
      resizeMode: nativeResizeMode,
      src: {
        uri,
        isNetwork,
        isAsset,
        type: source.type || '',
        mainVer: source.mainVer || 0,
        patchVer: source.patchVer || 0,
      },
      onVideoLoadStart: this._onLoadStart,
      onVideoLoad: this._onLoad,
      onVideoError: this._onError,
      onVideoProgress: this._onProgress,
      onVideoSeek: this._onSeek,
      onVideoEnd: this._onEnd,
      onVideoBuffer: this._onBuffer,
      onTimedMetadata: this._onTimedMetadata,
      onVideoFullscreenPlayerWillPresent: this._onFullscreenPlayerWillPresent,
      onVideoFullscreenPlayerDidPresent: this._onFullscreenPlayerDidPresent,
      onVideoFullscreenPlayerWillDismiss: this._onFullscreenPlayerWillDismiss,
      onVideoFullscreenPlayerDidDismiss: this._onFullscreenPlayerDidDismiss,
      onReadyForDisplay: this._onReadyForDisplay,
      onPlaybackStalled: this._onPlaybackStalled,
      onPlaybackResume: this._onPlaybackResume,
      onPlaybackRateChange: this._onPlaybackRateChange,
      onAudioFocusChanged: this._onAudioFocusChanged,
      onAudioBecomingNoisy: this._onAudioBecomingNoisy,
    });

    if (this.props.poster && this.state.showPoster) {
      const posterStyle = {
        position: 'absolute',
        left: 0,
        top: 0,
        right: 0,
        bottom: 0,
        resizeMode: 'contain',
      };

      return (
        <View style={nativeProps.style}>
          <RCTVideo
            ref={this._assignRoot}
            {...nativeProps}
          />
          <Image
            style={posterStyle}
            source={{uri: this.props.poster}}
          />
        </View>
      );
    }

    return (
      <RCTVideo
        ref={this._assignRoot}
        {...nativeProps}
      />
    );
  }
}

Video.propTypes = {
  /* Native only */
  src: PropTypes.object,
  seek: PropTypes.number,
  fullscreen: PropTypes.bool,
  onVideoLoadStart: PropTypes.func,
  onVideoLoad: PropTypes.func,
  onVideoBuffer: PropTypes.func,
  onVideoError: PropTypes.func,
  onVideoProgress: PropTypes.func,
  onVideoSeek: PropTypes.func,
  onVideoEnd: PropTypes.func,
  onTimedMetadata: PropTypes.func,
  onVideoFullscreenPlayerWillPresent: PropTypes.func,
  onVideoFullscreenPlayerDidPresent: PropTypes.func,
  onVideoFullscreenPlayerWillDismiss: PropTypes.func,
  onVideoFullscreenPlayerDidDismiss: PropTypes.func,

  /* Wrapper component */
  source: PropTypes.oneOfType([
    PropTypes.shape({
      uri: PropTypes.string
    }),
    // Opaque type returned by require('./video.mp4')
    PropTypes.number
  ]),
  resizeMode: PropTypes.string,
  poster: PropTypes.string,
  repeat: PropTypes.bool,
  paused: PropTypes.bool,
  muted: PropTypes.bool,
  volume: PropTypes.number,
  rate: PropTypes.number,
  playInBackground: PropTypes.bool,
  playWhenInactive: PropTypes.bool,
  disableFocus: PropTypes.bool,
  controls: PropTypes.bool,
  currentTime: PropTypes.number,
  progressUpdateInterval: PropTypes.number,
  onLoadStart: PropTypes.func,
  onLoad: PropTypes.func,
  onBuffer: PropTypes.func,
  onError: PropTypes.func,
  onProgress: PropTypes.func,
  onSeek: PropTypes.func,
  onEnd: PropTypes.func,
  onFullscreenPlayerWillPresent: PropTypes.func,
  onFullscreenPlayerDidPresent: PropTypes.func,
  onFullscreenPlayerWillDismiss: PropTypes.func,
  onFullscreenPlayerDidDismiss: PropTypes.func,
  onReadyForDisplay: PropTypes.func,
  onPlaybackStalled: PropTypes.func,
  onPlaybackResume: PropTypes.func,
  onPlaybackRateChange: PropTypes.func,
  onAudioFocusChanged: PropTypes.func,
  onAudioBecomingNoisy: PropTypes.func,

  /* Required by react-native */
  scaleX: PropTypes.number,
  scaleY: PropTypes.number,
  translateX: PropTypes.number,
  translateY: PropTypes.number,
  rotation: PropTypes.number,
  ...View.propTypes,
};

const RCTVideo = requireNativeComponent('RCTVideo', Video, {
  nativeOnly: {
    src: true,
    seek: true,
    fullscreen: true,
  },
});
<commit_msg>Support react native 0.46<commit_after>import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {StyleSheet, requireNativeComponent, NativeModules, View, Image, ViewPropTypes} from 'react-native';
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import VideoResizeMode from './VideoResizeMode.js';

const styles = StyleSheet.create({
  base: {
    overflow: 'hidden',
  },
});

export default class Video extends Component {

  constructor(props) {
    super(props);

    this.state = {
      showPoster: true,
    };
  }

  setNativeProps(nativeProps) {
    this._root.setNativeProps(nativeProps);
  }

  seek = (time) => {
    this.setNativeProps({ seek: time });
  };

  presentFullscreenPlayer = () => {
    this.setNativeProps({ fullscreen: true });
  };

  dismissFullscreenPlayer = () => {
    this.setNativeProps({ fullscreen: false });
  };

  _assignRoot = (component) => {
    this._root = component;
  };

  _onLoadStart = (event) => {
    if (this.props.onLoadStart) {
      this.props.onLoadStart(event.nativeEvent);
    }
  };

  _onLoad = (event) => {
    if (this.props.onLoad) {
      this.props.onLoad(event.nativeEvent);
    }
  };

  _onError = (event) => {
    if (this.props.onError) {
      this.props.onError(event.nativeEvent);
    }
  };

  _onProgress = (event) => {
    if (this.props.onProgress) {
      this.props.onProgress(event.nativeEvent);
    }
  };

  _onSeek = (event) => {
    if (this.state.showPoster) {
      this.setState({showPoster: false});
    }

    if (this.props.onSeek) {
      this.props.onSeek(event.nativeEvent);
    }
  };

  _onEnd = (event) => {
    if (this.props.onEnd) {
      this.props.onEnd(event.nativeEvent);
    }
  };

  _onTimedMetadata = (event) => {
    if (this.props.onTimedMetadata) {
      this.props.onTimedMetadata(event.nativeEvent);
    }
  };

  _onFullscreenPlayerWillPresent = (event) => {
    if (this.props.onFullscreenPlayerWillPresent) {
      this.props.onFullscreenPlayerWillPresent(event.nativeEvent);
    }
  };

  _onFullscreenPlayerDidPresent = (event) => {
    if (this.props.onFullscreenPlayerDidPresent) {
      this.props.onFullscreenPlayerDidPresent(event.nativeEvent);
    }
  };

  _onFullscreenPlayerWillDismiss = (event) => {
    if (this.props.onFullscreenPlayerWillDismiss) {
      this.props.onFullscreenPlayerWillDismiss(event.nativeEvent);
    }
  };

  _onFullscreenPlayerDidDismiss = (event) => {
    if (this.props.onFullscreenPlayerDidDismiss) {
      this.props.onFullscreenPlayerDidDismiss(event.nativeEvent);
    }
  };

  _onReadyForDisplay = (event) => {
    if (this.props.onReadyForDisplay) {
      this.props.onReadyForDisplay(event.nativeEvent);
    }
  };

  _onPlaybackStalled = (event) => {
    if (this.props.onPlaybackStalled) {
      this.props.onPlaybackStalled(event.nativeEvent);
    }
  };

  _onPlaybackResume = (event) => {
    if (this.props.onPlaybackResume) {
      this.props.onPlaybackResume(event.nativeEvent);
    }
  };

  _onPlaybackRateChange = (event) => {
    if (this.state.showPoster && (event.nativeEvent.playbackRate !== 0)) {
      this.setState({showPoster: false});
    }

    if (this.props.onPlaybackRateChange) {
      this.props.onPlaybackRateChange(event.nativeEvent);
    }
  };

  _onAudioBecomingNoisy = () => {
    if (this.props.onAudioBecomingNoisy) {
      this.props.onAudioBecomingNoisy();
    }
  };

  _onAudioFocusChanged = (event) => {
    if (this.props.onAudioFocusChanged) {
      this.props.onAudioFocusChanged(event.nativeEvent);
    }
  };

  _onBuffer = (event) => {
    if (this.props.onBuffer) {
      this.props.onBuffer(event.nativeEvent);
    }
  };

  render() {
    const resizeMode = this.props.resizeMode;
    const source = resolveAssetSource(this.props.source) || {};

    let uri = source.uri || '';
    if (uri && uri.match(/^\//)) {
      uri = `file://${uri}`;
    }

    const isNetwork = !!(uri && uri.match(/^https?:/));
    const isAsset = !!(uri && uri.match(/^(assets-library|file|content|ms-appx|ms-appdata):/));

    let nativeResizeMode;
    if (resizeMode === VideoResizeMode.stretch) {
      nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleToFill;
    } else if (resizeMode === VideoResizeMode.contain) {
      nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFit;
    } else if (resizeMode === VideoResizeMode.cover) {
      nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFill;
    } else {
      nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleNone;
    }

    const nativeProps = Object.assign({}, this.props);
    Object.assign(nativeProps, {
      style: [styles.base, nativeProps.style],
      resizeMode: nativeResizeMode,
      src: {
        uri,
        isNetwork,
        isAsset,
        type: source.type || '',
        mainVer: source.mainVer || 0,
        patchVer: source.patchVer || 0,
      },
      onVideoLoadStart: this._onLoadStart,
      onVideoLoad: this._onLoad,
      onVideoError: this._onError,
      onVideoProgress: this._onProgress,
      onVideoSeek: this._onSeek,
      onVideoEnd: this._onEnd,
      onVideoBuffer: this._onBuffer,
      onTimedMetadata: this._onTimedMetadata,
      onVideoFullscreenPlayerWillPresent: this._onFullscreenPlayerWillPresent,
      onVideoFullscreenPlayerDidPresent: this._onFullscreenPlayerDidPresent,
      onVideoFullscreenPlayerWillDismiss: this._onFullscreenPlayerWillDismiss,
      onVideoFullscreenPlayerDidDismiss: this._onFullscreenPlayerDidDismiss,
      onReadyForDisplay: this._onReadyForDisplay,
      onPlaybackStalled: this._onPlaybackStalled,
      onPlaybackResume: this._onPlaybackResume,
      onPlaybackRateChange: this._onPlaybackRateChange,
      onAudioFocusChanged: this._onAudioFocusChanged,
      onAudioBecomingNoisy: this._onAudioBecomingNoisy,
    });

    if (this.props.poster && this.state.showPoster) {
      const posterStyle = {
        position: 'absolute',
        left: 0,
        top: 0,
        right: 0,
        bottom: 0,
        resizeMode: 'contain',
      };

      return (
        <View style={nativeProps.style}>
          <RCTVideo
            ref={this._assignRoot}
            {...nativeProps}
          />
          <Image
            style={posterStyle}
            source={{uri: this.props.poster}}
          />
        </View>
      );
    }

    return (
      <RCTVideo
        ref={this._assignRoot}
        {...nativeProps}
      />
    );
  }
}

Video.propTypes = {
  /* Native only */
  src: PropTypes.object,
  seek: PropTypes.number,
  fullscreen: PropTypes.bool,
  onVideoLoadStart: PropTypes.func,
  onVideoLoad: PropTypes.func,
  onVideoBuffer: PropTypes.func,
  onVideoError: PropTypes.func,
  onVideoProgress: PropTypes.func,
  onVideoSeek: PropTypes.func,
  onVideoEnd: PropTypes.func,
  onTimedMetadata: PropTypes.func,
  onVideoFullscreenPlayerWillPresent: PropTypes.func,
  onVideoFullscreenPlayerDidPresent: PropTypes.func,
  onVideoFullscreenPlayerWillDismiss: PropTypes.func,
  onVideoFullscreenPlayerDidDismiss: PropTypes.func,

  /* Wrapper component */
  source: PropTypes.oneOfType([
    PropTypes.shape({
      uri: PropTypes.string
    }),
    // Opaque type returned by require('./video.mp4')
    PropTypes.number
  ]),
  resizeMode: PropTypes.string,
  poster: PropTypes.string,
  repeat: PropTypes.bool,
  paused: PropTypes.bool,
  muted: PropTypes.bool,
  volume: PropTypes.number,
  rate: PropTypes.number,
  playInBackground: PropTypes.bool,
  playWhenInactive: PropTypes.bool,
  disableFocus: PropTypes.bool,
  controls: PropTypes.bool,
  currentTime: PropTypes.number,
  progressUpdateInterval: PropTypes.number,
  onLoadStart: PropTypes.func,
  onLoad: PropTypes.func,
  onBuffer: PropTypes.func,
  onError: PropTypes.func,
  onProgress: PropTypes.func,
  onSeek: PropTypes.func,
  onEnd: PropTypes.func,
  onFullscreenPlayerWillPresent: PropTypes.func,
  onFullscreenPlayerDidPresent: PropTypes.func,
  onFullscreenPlayerWillDismiss: PropTypes.func,
  onFullscreenPlayerDidDismiss: PropTypes.func,
  onReadyForDisplay: PropTypes.func,
  onPlaybackStalled: PropTypes.func,
  onPlaybackResume: PropTypes.func,
  onPlaybackRateChange: PropTypes.func,
  onAudioFocusChanged: PropTypes.func,
  onAudioBecomingNoisy: PropTypes.func,

  /* Required by react-native */
  scaleX: PropTypes.number,
  scaleY: PropTypes.number,
  translateX: PropTypes.number,
  translateY: PropTypes.number,
  rotation: PropTypes.number,
  ...ViewPropTypes,
};

const RCTVideo = requireNativeComponent('RCTVideo', Video, {
  nativeOnly: {
    src: true,
    seek: true,
    fullscreen: true,
  },
});
<|endoftext|>


<commit_before>var addHandler = require('./actor.addHandler'),
    expressions = require('tribe.expressions'),
    utils = require('./utils');

var actor = module.exports = function (pubsub, definition, scope, dependencies) {
    var self = this;

    pubsub = pubsub.createLifetime({ origin: 'actor' });
    this.pubsub = pubsub;
    this.children = [];
    this.scope = scope;
    this.metadata = {
        expression: []
    };
    this.dependencies = dependencies;

    configureActor();

    this.handles = this.handles || {};
    this.topics = utils.keys(this.handles);

    function configureActor() {
        if (definition)
            if (definition.constructor === Function)
                self.instance = new definition(self);
            else
                utils.copyProperties(definition, self, ['handles', 'endsChildrenExplicitly', 'onstart', 'onresume', 'onsuspend', 'onend']);
    }
};

actor.prototype.start = function (startData) {
    if (this.onstart) this.onstart(startData, this);
    utils.each(this.handles, this.addHandler, this);
    return this;
};

actor.prototype.startChild = function (child, onstartData) {
    this.children.push(new actor(this.pubsub, child)
        .start(onstartData));
    return this;
};

actor.prototype.resume = function (data, resumeData) {
    this.data = data;
    if (this.onresume) this.onresume(resumeData, this);
    utils.each(this.handles, this.addHandler, this);
    return this;
};

actor.prototype.suspend = function (suspendData) {
    if (this.onsuspend) this.onsuspend(suspendData, this);
    this.pubsub.end();
    this.suspendChildren(suspendData);
    return this;
};

actor.prototype.end = function (endData) {
    if (this.onend) this.onend(endData, this);
    this.pubsub.end();
    this.endChildren(endData);
    return this;
};

actor.prototype.endChildren = function (data) {
    utils.each(this.children, function (child) {
        child.end(data);
    });
};

actor.prototype.suspendChildren = function (data) {
    utils.each(this.children, function (child) {
        child.suspend(data);
    });
};

actor.prototype.isScopedTo = function (property) {
    var self = this;
    utils.each(arguments, function (arg) {
        var value = (self.scope && self.scope.constructor === Object)
            ? self.scope[arg]
            : self.scope;
        self.metadata.expression.push({ p: 'data.' + arg, v: value });
    });
};

actor.prototype.replay = function (envelopes) {
    var self = this;
    self.pubsub.suspend();
    utils.each(envelopes, function (envelope) {
        if (self.handles[envelope.topic])
            self.handles[envelope.topic](envelope.data, envelope);
    });
    self.pubsub.resume();
};

actor.prototype.addHandler = addHandler;<commit_msg>Added scope metadata<commit_after>var addHandler = require('./actor.addHandler'),
    expressions = require('tribe.expressions'),
    utils = require('./utils');

var actor = module.exports = function (pubsub, definition, scope, dependencies) {
    var self = this;

    pubsub = pubsub.createLifetime({ origin: 'actor' });
    this.pubsub = pubsub;
    this.children = [];
    this.scope = scope;
    this.metadata = {
        expression: [],
        scope: []
    };
    this.dependencies = dependencies;

    configureActor();

    this.handles = this.handles || {};
    this.topics = utils.keys(this.handles);

    function configureActor() {
        if (definition)
            if (definition.constructor === Function)
                self.instance = new definition(self);
            else
                utils.copyProperties(definition, self, ['handles', 'endsChildrenExplicitly', 'onstart', 'onresume', 'onsuspend', 'onend']);
    }
};

actor.prototype.start = function (startData) {
    if (this.onstart) this.onstart(startData, this);
    utils.each(this.handles, this.addHandler, this);
    return this;
};

actor.prototype.startChild = function (child, onstartData) {
    this.children.push(new actor(this.pubsub, child)
        .start(onstartData));
    return this;
};

actor.prototype.resume = function (data, resumeData) {
    this.data = data;
    if (this.onresume) this.onresume(resumeData, this);
    utils.each(this.handles, this.addHandler, this);
    return this;
};

actor.prototype.suspend = function (suspendData) {
    if (this.onsuspend) this.onsuspend(suspendData, this);
    this.pubsub.end();
    this.suspendChildren(suspendData);
    return this;
};

actor.prototype.end = function (endData) {
    if (this.onend) this.onend(endData, this);
    this.pubsub.end();
    this.endChildren(endData);
    return this;
};

actor.prototype.endChildren = function (data) {
    utils.each(this.children, function (child) {
        child.end(data);
    });
};

actor.prototype.suspendChildren = function (data) {
    utils.each(this.children, function (child) {
        child.suspend(data);
    });
};

actor.prototype.isScopedTo = function (property) {
    var self = this;
    utils.each(arguments, function (arg) {
        var value = (self.scope && self.scope.constructor === Object)
            ? self.scope[arg]
            : self.scope;
        self.metadata.expression.push({ p: 'data.' + arg, v: value });
        self.metadata.scope.push(arg);
    });
};

actor.prototype.replay = function (envelopes) {
    var self = this;
    self.pubsub.suspend();
    utils.each(envelopes, function (envelope) {
        if (self.handles[envelope.topic])
            self.handles[envelope.topic](envelope.data, envelope);
    });
    self.pubsub.resume();
};

actor.prototype.addHandler = addHandler;<|endoftext|>


<commit_before>var express = require('express');
var socketio = require('socket.io');
var app,
    appSsl,
    server,
    serverSsl,
    io,
    ioSsl;

var objects = {};
var states = {};

var adapter = require('../../modules/adapter.js')({

    // Ein paar Attribute die jeder Adapter mitbringen muss
    name:           'admin',

    // Event-Handler für Adapter-Installation
    install: function (callback) {
        if (typeof callback === 'function') callback();
    },

    // Wird aufgerufen wenn sich ein Objekt - das via adapter.subscribeObjects aboniert wurde - ändert.
    objectChange: function (id, obj) {
        objects[id] = obj;

        if (io)     io.sockets.emit('objectChange', id, obj);
        if (ioSsl)  ioSsl.sockets.emit('objectChange', id, obj);
    },
    // Wird aufgerufen wenn sich ein Status - der via adapter.subscribeStates aboniert wurde - ändert.
    stateChange: function (id, state) {
        states[id] = state;
        if (io)     io.sockets.emit('stateChange', id, state);
        if (ioSsl)  ioSsl.sockets.emit('stateChange', id, state);
    },

    // Wird aufgerufen bevor der Adapter beendet wird - callback muss unbedingt aufgerufen werden!
    unload: function (callback) {
        try {
            if (server) {
                adapter.log.info("terminating http server");
                server.close();

            }
            if (serverSsl) {
                adapter.log.info("terminating https server");
                serverSsl.close();

            }
            callback();
        } catch (e) {
            callback();
        }
    },

    // Wird aufgerufen wenn der Adapter mit den Datenbanken verbunden ist und seine Konfiguration erhalten hat.
    // Hier einsteigen!
    ready: function () {
        main();
    }

});

function main() {

    adapter.subscribeForeignStates('*');
    adapter.subscribeForeignObjects('*');

    initWebserver();

    getData();

}

function initWebserver() {
    if (adapter.config.listenPort) {
        app = express();
        server =    require('http').createServer(app)
    }
    if (adapter.config.listenPortSsl) {
        try {
            options = {
                key: fs.readFileSync(__dirname+'/cert/privatekey.pem'),
                cert: fs.readFileSync(__dirname+'/cert/certificate.pem')
            };
        } catch(err) {
            adapter.log.error(err.message);
        }
        if (options) {
            appSsl = express();
            serverSsl = require('https').createServer(options, appSsl);
        }
    }

    if (adapter.config.cache) {
        app.use('/', express.static(__dirname + '/www', { maxAge: 30758400000 }));
    } else {
        app.use('/', express.static(__dirname + '/www'));
    }

    if (server) {
        var port = adapter.getPort(adapter.config.listenPort, function (port) {
            server.listen(port);
            adapter.log.info("http server listening on port " + port);
            io = socketio.listen(server);
            /*io.set('logger', {
                debug: function(obj) {adapter.log.debug("socket.io: "+obj)},
                info: function(obj) {adapter.log.debug("socket.io: "+obj)} ,
                error: function(obj) {adapter.log.error("socket.io: "+obj)},
                warn: function(obj) {adapter.log.warn("socket.io: "+obj)}
            });*/
            io.on('connection', initSocket);
        });

    }

    if (serverSsl) {
        var portSsl = adapter.getPort(adapter.config.listenPortSsl, function (portSsl) {
            serverSsl.listen(portSsl);
            adapter.log.info("https server listening on port " + portSsl);
            ioSsl = socketio.listen(serverSsl);
            /*io.set('logger', {
                debug: function(obj) {adapter.log.debug("socket.io: "+obj)},
                info: function(obj) {adapter.log.debug("socket.io: "+obj)} ,
                error: function(obj) {adapter.log.error("socket.io: "+obj)},
                warn: function(obj) {adapter.log.warn("socket.io: "+obj)}
            });*/
            ioSsl.on('connection', initSocket);
        });
    }

}

function getData() {
    adapter.log.info('requesting all states');
    adapter.getForeignStates('*', function (err, res) {
        adapter.log.info('received all states');
        states = res;
    });
    adapter.log.info('requesting all objects');
    adapter.objects.getObjectList({include_docs: true}, function (err, res) {
        adapter.log.info('received all objects');
        res = res.rows;
        objects = {};
        for (var i = 0; i < res.length; i++) {
            objects[res[i].doc._id] = res[i].doc;
        }
    });
}

function initSocket(socket) {

    socket.on('getStates', function (callback) {
        callback(null, states);
    });

    socket.on('getObjects', function (callback) {
        callback(null, objects);
    });

    socket.on('setState', function (id, state, callback) {
        if (typeof state !== 'object') state = {val: state};
        adapter.setForeignState(id, state, function (err, res) {
            if (typeof callback === 'function') callback(err, res);
        });
    });

}

<commit_msg>(Bluefox) use jshint with grunt to check syntax.<commit_after>var express = require('express');
var socketio = require('socket.io');
var app,
    appSsl,
    server,
    serverSsl,
    io,
    ioSsl;

var objects = {};
var states = {};

var adapter = require('../../modules/adapter.js')({

    // Ein paar Attribute die jeder Adapter mitbringen muss
    name:           'admin',

    // Event-Handler für Adapter-Installation
    install: function (callback) {
        if (typeof callback === 'function') callback();
    },

    // Wird aufgerufen wenn sich ein Objekt - das via adapter.subscribeObjects aboniert wurde - ändert.
    objectChange: function (id, obj) {
        objects[id] = obj;

        if (io)     io.sockets.emit('objectChange', id, obj);
        if (ioSsl)  ioSsl.sockets.emit('objectChange', id, obj);
    },
    // Wird aufgerufen wenn sich ein Status - der via adapter.subscribeStates aboniert wurde - ändert.
    stateChange: function (id, state) {
        states[id] = state;
        if (io)     io.sockets.emit('stateChange', id, state);
        if (ioSsl)  ioSsl.sockets.emit('stateChange', id, state);
    },

    // Wird aufgerufen bevor der Adapter beendet wird - callback muss unbedingt aufgerufen werden!
    unload: function (callback) {
        try {
            if (server) {
                adapter.log.info("terminating http server");
                server.close();

            }
            if (serverSsl) {
                adapter.log.info("terminating https server");
                serverSsl.close();

            }
            callback();
        } catch (e) {
            callback();
        }
    },

    // Wird aufgerufen wenn der Adapter mit den Datenbanken verbunden ist und seine Konfiguration erhalten hat.
    // Hier einsteigen!
    ready: function () {
        main();
    }

});

function main() {

    adapter.subscribeForeignStates('*');
    adapter.subscribeForeignObjects('*');

    initWebserver();

    getData();

}

function initWebserver() {
    if (adapter.config.listenPort) {
        app    = express();
        server = require('http').createServer(app);
    }
    if (adapter.config.listenPortSsl) {
        try {
            options = {
                key: fs.readFileSync(__dirname+'/cert/privatekey.pem'),
                cert: fs.readFileSync(__dirname+'/cert/certificate.pem')
            };
        } catch(err) {
            adapter.log.error(err.message);
        }
        if (options) {
            appSsl = express();
            serverSsl = require('https').createServer(options, appSsl);
        }
    }

    if (adapter.config.cache) {
        app.use('/', express.static(__dirname + '/www', { maxAge: 30758400000 }));
    } else {
        app.use('/', express.static(__dirname + '/www'));
    }

    if (server) {
        var port = adapter.getPort(adapter.config.listenPort, function (port) {
            server.listen(port);
            adapter.log.info("http server listening on port " + port);
            io = socketio.listen(server);
            /*io.set('logger', {
                debug: function(obj) {adapter.log.debug("socket.io: "+obj)},
                info: function(obj) {adapter.log.debug("socket.io: "+obj)} ,
                error: function(obj) {adapter.log.error("socket.io: "+obj)},
                warn: function(obj) {adapter.log.warn("socket.io: "+obj)}
            });*/
            io.on('connection', initSocket);
        });

    }

    if (serverSsl) {
        var portSsl = adapter.getPort(adapter.config.listenPortSsl, function (portSsl) {
            serverSsl.listen(portSsl);
            adapter.log.info("https server listening on port " + portSsl);
            ioSsl = socketio.listen(serverSsl);
            /*io.set('logger', {
                debug: function(obj) {adapter.log.debug("socket.io: "+obj)},
                info: function(obj) {adapter.log.debug("socket.io: "+obj)} ,
                error: function(obj) {adapter.log.error("socket.io: "+obj)},
                warn: function(obj) {adapter.log.warn("socket.io: "+obj)}
            });*/
            ioSsl.on('connection', initSocket);
        });
    }

}

function getData() {
    adapter.log.info('requesting all states');
    adapter.getForeignStates('*', function (err, res) {
        adapter.log.info('received all states');
        states = res;
    });
    adapter.log.info('requesting all objects');
    adapter.objects.getObjectList({include_docs: true}, function (err, res) {
        adapter.log.info('received all objects');
        res = res.rows;
        objects = {};
        for (var i = 0; i < res.length; i++) {
            objects[res[i].doc._id] = res[i].doc;
        }
    });
}

function initSocket(socket) {

    socket.on('getStates', function (callback) {
        callback(null, states);
    });

    socket.on('getObjects', function (callback) {
        callback(null, objects);
    });

    socket.on('setState', function (id, state, callback) {
        if (typeof state !== 'object') state = {val: state};
        adapter.setForeignState(id, state, function (err, res) {
            if (typeof callback === 'function') callback(err, res);
        });
    });

}

<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict'

const fs = require('fs')
const path = require('path')
const prettybytes = require('pretty-bytes')
const chalk = require('chalk')
const program = require('commander')

program.version('1.0.0')
  .arguments('[target]')
  .option('-s, --sizes', 'Show file sizes in tree')
  .option('-e, --exclude <s>', 'Exclude files matching a pattern')
  .option('-i, --include <s>', 'Include only files that match a pattern')
  .option('-d, --depth <n>', 'Only render the tree to a specific depth', parseInt)
  .parse(process.argv)

/**
 * Whitespace is included for easy-on-the-eyes padding
 */
const BOX_BOTTOM_LEFT = ' └── '
const BOX_INTERSECTION = ' ├── '
const BOX_VERTICAL = ' │  '
const EMPTY = '    '

const input = program.args[0] || '.';
const cwd = process.env.PWD;
const root = path.resolve(cwd, input)
const maxDepth = program.depth || Infinity
const showSize = program.sizes
const hasExcludePattern = typeof program.exclude !== 'undefined'
const hasIncludePattern = typeof program.include !== 'undefined'
const excludePattern = new RegExp(program.exclude)
const includePattern = new RegExp(program.include)

if (hasExcludePattern && hasIncludePattern) {
  throw new Error('Exclude patterns and include patterns cannot be used together.')
}

let output = '\n ' + input + '\n'

const colors = ['blue', 'magenta', 'cyan', 'red', 'white']
const depths = {}

let fileCount = 0
let directoryCount = 0
let totalFileSize = 0

/**
 * Pads filenames with either whitespace or box characters.
 * The depths map is used to track whether a character should
 * be whitespace or a vertical character. Typically it will
 * be whitespace if there are no other files at that depth
 * that need to be rendered.
 * @param {Number}  depth   the level at which this file/filder is nested
 * @param {Boolean} bottom  whether this is the last file in the folder
 */
function buildPrefix(depth, bottom) {
  let prefix = bottom ? BOX_BOTTOM_LEFT : BOX_INTERSECTION
  let spacing = []
  let spaceIndex = 0
  while(spaceIndex < depth) {
    spacing[spaceIndex] = depths[spaceIndex] ? BOX_VERTICAL : EMPTY
    spaceIndex++
  }
  return spacing.join('') + prefix
}

/**
 * Uses either the exclude or include pattern to determine
 * if a file should be shown.
 * @param {String} file   filename
 */
function shouldBeIncluded(file) {
  if (hasExcludePattern) {
    return !excludePattern.test(file)
  }
  if (hasIncludePattern) {
    return includePattern.test(file)
  }
  return true
}

/**
 * Depth-first recursive traversal utility for
 * building up the output string.
 */
function buildTree(directory, depth) {
  const files = fs.readdirSync(directory);
  const max_index = files.length - 1
  const color = chalk[colors[depth % colors.length]]

  for (let i = 0; i <= max_index; i++) {
    const file = files[i]
    depths[depth] = max_index - i
    const fullPath = path.resolve(directory, file)
    const prefix = buildPrefix(depth, i === max_index, directory)
    const stats = fs.statSync(fullPath)
    const isDirectory = stats.isDirectory()
    const size = prettybytes(stats.size)

    if (isDirectory) {
      directoryCount++
    } else {
      fileCount++
      totalFileSize += stats.size
    }

    if (!shouldBeIncluded(file)) {
      continue
    }

    const filename = color(file)
    output += prefix + filename + (!isDirectory && showSize ? ` (${size})` : '') + '\n';
    if (isDirectory && (depth + 1 < maxDepth)) {
      buildTree(path.resolve(directory, file), depth + 1)
    }
    --depths[depth]
  }
}
// Kick off the recursive printing process.
buildTree(root, 0)

output += directoryCount + ' directories, '
output += fileCount + ' files'
output += ' (' + prettybytes(totalFileSize) + ')\n'

process.stdout.write(output)<commit_msg>Add no-indent option for flat tree reporting<commit_after>#!/usr/bin/env node
'use strict'

const fs = require('fs')
const path = require('path')
const prettybytes = require('pretty-bytes')
const chalk = require('chalk')
const program = require('commander')

program.version('1.0.0')
  .arguments('[target]')
  .option('-i, --no-indent', 'Tree will not print the indentation lines', { isDefault: true })
  .option('-s, --sizes', 'Show file sizes in tree')
  .option('-e, --exclude <s>', 'Exclude files matching a pattern')
  .option('-in, --include <s>', 'Include only files that match a pattern')
  .option('-d, --depth <n>', 'Only render the tree to a specific depth', parseInt)
  .parse(process.argv)

/**
 * Whitespace is included for easy-on-the-eyes padding
 */
const BOX_BOTTOM_LEFT = ' └── '
const BOX_INTERSECTION = ' ├── '
const BOX_VERTICAL = ' │  '
const EMPTY = '    '

const input = program.args[0] || '.';
const cwd = process.env.PWD;
const root = path.resolve(cwd, input)
const maxDepth = program.depth || Infinity
const showSize = program.sizes
const shouldIndent =  typeof program.indent === 'undefined' ? true : program.indent
const hasExcludePattern = typeof program.exclude !== 'undefined'
const hasIncludePattern = typeof program.include !== 'undefined'
const excludePattern = new RegExp(program.exclude)
const includePattern = new RegExp(program.include)

if (hasExcludePattern && hasIncludePattern) {
  throw new Error('Exclude patterns and include patterns cannot be used together.')
}

let output = '\n ' + input + '\n'

const colors = ['blue', 'magenta', 'cyan', 'red', 'white']
const depths = {}

let fileCount = 0
let directoryCount = 0
let totalFileSize = 0

/**
 * Pads filenames with either whitespace or box characters.
 * The depths map is used to track whether a character should
 * be whitespace or a vertical character. Typically it will
 * be whitespace if there are no other files at that depth
 * that need to be rendered.
 * @param {Number}  depth   the level at which this file/filder is nested
 * @param {Boolean} bottom  whether this is the last file in the folder
 */
function buildPrefix(depth, bottom) {
  if (!shouldIndent) {
    return ''
  }
  let prefix = bottom ? BOX_BOTTOM_LEFT : BOX_INTERSECTION
  let spacing = []
  let spaceIndex = 0
  while(spaceIndex < depth) {
    spacing[spaceIndex] = depths[spaceIndex] ? BOX_VERTICAL : EMPTY
    spaceIndex++
  }
  return spacing.join('') + prefix
}

/**
 * Uses either the exclude or include pattern to determine
 * if a file should be shown.
 * @param {String} file   filename
 */
function shouldBeIncluded(file) {
  if (hasExcludePattern) {
    return !excludePattern.test(file)
  }
  if (hasIncludePattern) {
    return includePattern.test(file)
  }
  return true
}

/**
 * Depth-first recursive traversal utility for
 * building up the output string.
 */
function buildTree(directory, depth) {
  const files = fs.readdirSync(directory);
  const max_index = files.length - 1
  const color = shouldIndent
    ? chalk[colors[depth % colors.length]]
    : chalk[colors[0]]

  for (let i = 0; i <= max_index; i++) {
    const file = files[i]
    depths[depth] = max_index - i
    const fullPath = path.resolve(directory, file)
    const prefix = buildPrefix(depth, i === max_index, directory)
    const stats = fs.statSync(fullPath)
    const isDirectory = stats.isDirectory()
    const size = prettybytes(stats.size)

    if (isDirectory) {
      directoryCount++
    } else {
      fileCount++
      totalFileSize += stats.size
    }

    if (!shouldBeIncluded(file)) {
      continue
    }

    const filename = color(file)
    output += prefix + filename + (!isDirectory && showSize ? ` (${size})` : '') + '\n';
    if (isDirectory && (depth + 1 < maxDepth)) {
      buildTree(path.resolve(directory, file), depth + 1)
    }
    --depths[depth]
  }
}
// Kick off the recursive printing process.
buildTree(root, 0)

output += directoryCount + ' directories, '
output += fileCount + ' files'
output += ' (' + prettybytes(totalFileSize) + ')\n'

process.stdout.write(output)<|endoftext|>