<commit_before>require('shelljs/global')

var crypto = require('crypto')
var path = require('path')
var packageJson = require('./package.json')
var privateToken = path.join(__dirname, 'kite.aes')

function generateAesKey(file) {
  crypto.randomBytes(32).toString('base64').to(file)
}

function encrypt(text, token) {
  var cipher = crypto.createCipher('aes-256-cbc', cat(token))
  var crypted = cipher.update(text,'utf8','base64')
  crypted += cipher.final('base64');
  return crypted
}

function decrypt(text, token) {
  var decipher = crypto.createDecipher('aes-256-cbc', cat(token))
  var dec = decipher.update(text,'base64','utf8')
  dec += decipher.final('utf8');
  return dec;
}

//-----------------------------------------------------------------------------
// CLI Configuration
//-----------------------------------------------------------------------------

var argv = require('yargs')
  .usage('Usage: $0 <command> [options]')

  .command('generate', 'Generate new private token.', function (yargs) {
    argv = yargs.option('f', {
      alias: 'file',
      default: 'kite.aes',
      description: 'Filename to write key to.'
    })
    .help('h')
    .alias('h', 'help')
    .argv
    generateAesKey(argv.file)
  })

  .command('encrypt', 'Encrypt a file.', function (yargs) {
    argv = yargs
      .option('i', {
        alias: 'input',
        description: 'File to encrypt.'
      })
      .option('o', {
        alias: 'output',
        description: 'Name of output file.'
      })
      .option('t', {
        alias: 'token',
        description: 'Path to your token file'
      })
      .demand(['i', 'o'])
      .help('h')
      .alias('h', 'help')
      .argv
    var token = argv.token || privateToken
    encrypt(cat(argv.input), token).to(argv.output)
  })

  .command('decrypt', 'Decrypt a file.', function (yargs) {
    argv = yargs
      .option('i', {
        alias: 'input',
        description: 'File to decrypt.'
      })
      .option('o', {
        alias: 'output',
        description: 'Name of output file.'
      })
      .option('t', {
        alias: 'token',
        description: 'Path to your token file'
      })
      .demand(['i', 'o'])
      .help('h')
      .alias('h', 'help')
      .argv
    var token = argv.token || privateToken
    decrypt(cat(argv.input), token).to(argv.output)
  })

  .example('$0 generate -f kite.aes', 'Create new token file')
  .help('h')
  .alias('h', 'help')
  .epilog('Version:  ' + packageJson.version)
  .argv

<commit_msg>start adding run commands<commit_after>#!/usr/bin/env node

require('shelljs/global')

var crypto = require('crypto')
var fs = require('fs')
var path = require('path')
var EOL = require('os').EOL
var Docker = require('dockerode')
var yaml = require('js-yaml')
var packageJson = require('./package.json')

var privateToken = path.join(__dirname, 'codeship.aes')
var services, steps

function generateAesKey(file) {
  crypto.randomBytes(32).toString('base64').to(file)
}

function encrypt(text, token) {
  var cipher = crypto.createCipher('aes-256-cbc', cat(token))
  var crypted = cipher.update(text,'utf8','base64')
  crypted += cipher.final('base64')
  return crypted
}

function decrypt(text, token) {
  var decipher = crypto.createDecipher('aes-256-cbc', cat(token))
  var dec = decipher.update(text,'base64','utf8')
  dec += decipher.final('utf8')
  return dec
}

// TODO: STEPS
// x load yaml
// . run service based on yml config
// . call service by step
// . run each step
function dockerBuild() {}
function runStep(stepName) {}
function runService() {}

function loadYaml(file) {
  try {
    return yaml.safeLoad(fs.readFileSync(file, 'utf8'))
  } catch (e) {
    console.log('didnt work: ' + e)
    return ''
  }
}

function steps(serviceFile, stepsFile) {
  services = loadYaml(serviceFile)
  steps = loadYaml(stepsFile)
  console.log(services.maven)
  console.log()
  console.log(steps)
}

//-----------------------------------------------------------------------------
// CLI Configuration
//-----------------------------------------------------------------------------
var argv = require('yargs')
  .usage('Usage:' + EOL + '  $0 <command> [options]')

  .command('version', "", function () {
    console.log(packageJson.version)
  })

  //---------------------------------------------------------------------------
  // Crypto.
  //---------------------------------------------------------------------------
  .command('generate', 'Generate new private token.', function (yargs) {
    argv = yargs.option('f', {
      alias: 'file',
      default: 'codeship.aes',
      description: 'Filename to write key to.' })
    .help('h')
    .alias('h', 'help')
    .argv
    generateAesKey(argv.file)
  })

  .command('encrypt', 'Encrypt a file.', function (yargs) {
    argv = yargs
      .option('i', {
        alias: 'input',
        description: 'File to encrypt.' })
      .option('o', {
        alias: 'output',
        description: 'Name of output file.' })
      .option('t', {
        alias: 'token',
        description: 'Path to your token file' })
      .demand(['i', 'o'])
      .help('h')
      .alias('h', 'help')
      .argv
    var token = argv.token || privateToken
    encrypt(cat(argv.input), token).to(argv.output)
  })

  .command('decrypt', 'Decrypt a file.', function (yargs) {
    argv = yargs
      .option('i', {
        alias: 'input',
        description: 'File to decrypt.' })
      .option('o', {
        alias: 'output',
        description: 'Name of output file.' })
      .option('t', {
        alias: 'token',
        description: 'Path to your token file' })
      .demand(['i', 'o'])
      .help('h')
      .alias('h', 'help')
      .argv
    var token = argv.token || privateToken
    decrypt(cat(argv.input), token).to(argv.output)
  })

  //---------------------------------------------------------------------------
  // Build.
  //---------------------------------------------------------------------------
  .command('steps', 'Run your steps.', function (yargs) {
    argv = yargs.help('h').alias('h', 'help').argv
    steps('codeship-services.yml', 'codeship-steps.yml')
  })

  .command('run', 'Run a docker service.', function (yargs) {
    argv = yargs.help('h').alias('h', 'help').argv
  })

  .command('load', 'Pull an image or build a docker service.', function (yargs) {
    argv = yargs.help('h').alias('h', 'help').argv
  })

  .default('h')
  .help('h')
  .alias('h', 'help')
  .epilog('Use "kite [command] (-h|--help)" for more information about a command.')
  .argv

<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var spawn = require('child_process').spawn;
var split = require('split');
var through = require('through');
var wrapColor = require('ansi-color').set;
if (process.logging) var log = process.logging('dictator');
if (process.logStream) var logStream = process.logStream;

var DEFAULT_WAIT = 1000;

function enhance(proc) {
  var color = proc.color;
  var prefix = '[' + proc.name + '] ';
  return through(function(data) {
    this.emit('data', wrapColor(prefix + data, color) + '\n');
  });
}

exports.rule = rule;
function rule(procs, options) {
  if (!_.isEmpty(options.only) && !_.isEmpty(options.exclude)) {
    throw new Error('Cannot combine only and exclude parameters');
  }

  if (!_.isEmpty(options.only)) {
    procs = _.pick(procs, options.only);
    procs = _.mapValues(procs, function(proc) {
      proc.deps = _.intersection(proc.deps, options.only);
      return proc;
    });
  }

  if (!_.isEmpty(options.exclude)) {
    procs = _.omit(procs, options.exclude);
    procs = _.mapValues(procs, function(proc) {
      proc.deps = _.without(proc.deps, options.exclude);
      return proc;
    });
  }

  setInterval(function() {
    runReady(procs);
  }, 100);
}

exports.runReady = runReady;
function runReady(procs) {
  _.forEach(procs, function(proc, name) {
    if (proc.child || proc.exitedAt) return;

    proc.name = name;

    var dependencies = proc.deps || [];
    var readyDependencies = _.filter(dependencies, function(name) {
      var dep = procs[name];
      if(!dep) {
        throw new Error('Process '+name+' is needed for '+proc.name+' but it has not been started.');
      }

      var wait = dep.wait || DEFAULT_WAIT;
      var runningInterval = new Date() - dep.startedAt;
      return runningInterval > wait;
    });
    var dependenciesSatisfied = dependencies.length === readyDependencies.length;

    if (dependenciesSatisfied) run(proc);
  });
}

function run(proc) {
  var child = proc.child = spawn(proc.cmd, proc.args, {cwd: proc.cwd});
  proc.startedAt = new Date();
  if (log) log('%s started', proc.name);
  if (logStream) {
    child.stdout.pipe(split()).pipe(enhance(proc)).pipe(logStream);
    child.stderr.pipe(split()).pipe(enhance(proc)).pipe(logStream);
  }
  child.on('exit', function() {
    if (log) log('%s terminated', proc.name);
    proc.exitedAt = new Date();
    delete proc.child;
    process.nextTick(terminate);
  });
}

function runningProcs(procs) {
  return _.filter(procs, function(proc) {
    return 'child' in proc;
  });
}

exports.terminate = terminate;
function terminate(procs) {
  var running = runningProcs(procs);
  _.forEach(running, function(proc) { proc.child.kill('SIGKILL'); });
  if (_.isEmpty(running)) setTimeout(process.exit, 100);
}
<commit_msg>Add proper error logging (thx @grncdr & @pxlpnk)<commit_after>'use strict';

var _ = require('lodash');
var spawn = require('child_process').spawn;
var split = require('split');
var through = require('through');
var wrapColor = require('ansi-color').set;
if (process.logging) var log = process.logging('dictator');
if (process.logStream) var logStream = process.logStream;

var DEFAULT_WAIT = 1000;

function enhance(proc) {
  var color = proc.color;
  var prefix = '[' + proc.name + '] ';
  return through(function(data) {
    this.emit('data', wrapColor(prefix + data, color) + '\n');
  });
}

exports.rule = rule;
function rule(procs, options) {
  if (!_.isEmpty(options.only) && !_.isEmpty(options.exclude)) {
    throw new Error('Cannot combine only and exclude parameters');
  }

  if (!_.isEmpty(options.only)) {
    procs = _.pick(procs, options.only);
    procs = _.mapValues(procs, function(proc) {
      proc.deps = _.intersection(proc.deps, options.only);
      return proc;
    });
  }

  if (!_.isEmpty(options.exclude)) {
    procs = _.omit(procs, options.exclude);
    procs = _.mapValues(procs, function(proc) {
      proc.deps = _.without(proc.deps, options.exclude);
      return proc;
    });
  }

  setInterval(function() {
    runReady(procs);
  }, 100);
}

exports.runReady = runReady;
function runReady(procs) {
  _.forEach(procs, function(proc, name) {
    if (proc.child || proc.exitedAt) return;

    proc.name = name;

    var dependencies = proc.deps || [];
    var readyDependencies = _.filter(dependencies, function(name) {
      var dep = procs[name];
      if(!dep) {
        throw new Error('Process '+name+' is needed for '+proc.name+' but it has not been started.');
      }

      var wait = dep.wait || DEFAULT_WAIT;
      var runningInterval = new Date() - dep.startedAt;
      return runningInterval > wait;
    });
    var dependenciesSatisfied = dependencies.length === readyDependencies.length;

    if (dependenciesSatisfied) run(proc);
  });
}

function run(proc) {
  var child = proc.child = spawn(proc.cmd, proc.args, {cwd: proc.cwd});
  proc.startedAt = new Date();
  if (log) log('%s started', proc.name);
  if (logStream) {
    child.stdout.pipe(split()).pipe(enhance(proc)).pipe(logStream);
    child.stderr.pipe(split()).pipe(enhance(proc)).pipe(logStream);
  }
  child.on('error', function (e) {
    console.error('Failed to spawn', proc.cmd, proc.args, 'in', proc.cwd);
    throw e;
  });
  child.on('exit', function() {
    if (log) log('%s terminated', proc.name);
    proc.exitedAt = new Date();
    delete proc.child;
    process.nextTick(terminate);
  });
}

function runningProcs(procs) {
  return _.filter(procs, function(proc) {
    return 'child' in proc;
  });
}

exports.terminate = terminate;
function terminate(procs) {
  var running = runningProcs(procs);
  _.forEach(running, function(proc) { proc.child.kill('SIGKILL'); });
  if (_.isEmpty(running)) setTimeout(process.exit, 100);
}
<|endoftext|>


<commit_before>'use strict';

var glob = require('glob');
var path = require('path');

var cwd = process.cwd();

// https://gist.github.com/gabrieleds/dc38c958ee74481f7a12
function series (fns, done) {
    while (fns.length) {
      done = fns.pop().bind(null, done);
    }
    done();
}

function iterate(items, fn) {
    var keys = Object.keys(items);
    keys.forEach(function (key) {
        var val = items[key];
        if ('string' !== typeof val) {
          return iterate(val, fn);
        }
        fn(key, val, items);
    });
}

function clone(object) {
    return JSON.parse(JSON.stringify(object));
}

function lmao(target, tree) {
    var loaders = [];
    var args = arguments;
    var argsLen = args.length;
    var callback = args[argsLen - 1];
    var isSync = 'function' !== typeof callback;
    if (argsLen === (isSync ? 1 : 2)) {
        tree = target;
        target = {};
    }
    if ('string' === typeof tree) {
        tree = {
          _: tree
        };
    }

    var result = clone(tree);

    iterate(result, function (key, val, items) {
        function set(name, data) {
            if ('_' === key) {
                delete items[key]; // TODO: Improve this
                items[name] = data;
            } else {
                items[key][name] = data;
            }
        }
        loaders.push(function (next) {
            function done(err, files) {
                if (err) {
                  return next(err);
                }
                items[key] = {};
                files.forEach(function (file) {
                    var filename = path.basename(file, path.extname(file));
                    set(filename, require(path.join(cwd, file)));
                });
                next();
            }
            if (isSync) {
              return done(null, glob.sync(val));
            }
            glob(val, done);
        });
    });

    series(loaders, function () {
        Object.assign(target, result);
        if (!isSync) {
          callback(null, target);
        }
    });

    if (isSync) {
      return target;
    }
}
lmao.load = lmao; // Backward compatibility

module.exports = lmao;

<commit_msg>loadSync<commit_after>'use strict';

var glob = require('glob');
var path = require('path');

var cwd = process.cwd();

// https://gist.github.com/gabrieleds/dc38c958ee74481f7a12
function series (fns, done) {
    while (fns.length) {
      done = fns.pop().bind(null, done);
    }
    done();
}

function iterate(items, fn) {
    var keys = Object.keys(items);
    keys.forEach(function (key) {
        var val = items[key];
        if ('string' !== typeof val) {
          return iterate(val, fn);
        }
        fn(key, val, items);
    });
}

function clone(object) {
    return JSON.parse(JSON.stringify(object));
}

function lmao(target, tree) {
    var loaders = [];
    var args = arguments;
    var argsLen = args.length;
    var callback = args[argsLen - 1];
    var isSync = 'function' !== typeof callback;
    if (argsLen === (isSync ? 1 : 2)) {
        tree = target;
        target = {};
    }
    if ('string' === typeof tree) {
        tree = {
          _: tree
        };
    }

    var result = clone(tree);

    iterate(result, function (key, val, items) {
        function set(name, data) {
            if ('_' === key) {
                delete items[key]; // TODO: Improve this
                items[name] = data;
            } else {
                items[key][name] = data;
            }
        }
        loaders.push(function (next) {
            function done(err, files) {
                if (err) {
                  return next(err);
                }
                items[key] = {};
                files.forEach(function (file) {
                    var filename = path.basename(file, path.extname(file));
                    set(filename, require(path.join(cwd, file)));
                });
                next();
            }
            if (isSync) {
              return done(null, glob.sync(val));
            }
            glob(val, done);
        });
    });

    series(loaders, function () {
        Object.assign(target, result);
        if (!isSync) {
          callback(null, target);
        }
    });

    if (isSync) {
      return target;
    }
}
lmao.load = lmao.loadSync = lmao; // Backward compatibility

module.exports = lmao;

<|endoftext|>


<commit_before>'use strict';
const fs = require('fs');
const path = require('path');
const got = require('got');
const tempDir = require('temp-dir');
const makeDir = require('make-dir');
const ini = require('ini');
const trim = require('trim');
const CacheConf = require('cache-conf');
const utils = require('./utils');
const pkg = require('./package.json');

const PREFERRED_STREAMS = [
  {quality: 'highest', format: 'mp3'},
  {quality: 'highest', format: 'aac'},
  {quality: 'high', format: 'mp3'},
  {quality: 'high', format: 'aacp'},
  {quality: 'low', format: 'aacp'},
  {quality: 'low', format: 'mp3'}
];

const GOT_OPTS = {headers: {'user-agent': `somafm/${pkg.version} (https://github.com/uschek/somafm)`}};

const configName = (process.env.NODE_ENV === 'test' ? 'channels-test' : 'channels');
const channelsConf = new CacheConf({projectName: pkg.name, configName});

let channels = [];

function getChannels(options) {
  options = Object.assign({}, options);
  options.streams = Object.assign(PREFERRED_STREAMS, options.streams);

  return getChannelsFromAPIOrCache(options)
    .then(channels => filterChannels(channels, options.search));
}

function getChannelsFromAPIOrCache(options) {
  if (channels.length === 0) {
    channels = getCachedChannels();
  }

  if (channels.length === 0 || options.forceUpdate) {
    return getChannelsFromAPI(options)
      .then(setCachedChannels);
  }

  return Promise.resolve(channels);
}

function getChannelsFromAPI(options) {
  return got('https://api.somafm.com/channels.json', GOT_OPTS)
    .then(res => parseJSONData(res.body, options));
}

function parseJSONData(json, options) {
  const data = JSON.parse(json).channels;
  const channels = [];

  for (const channel of data) {
    const streamHighestQuality = getHighestQualityStream(channel, options.streams);

    channels.push({
      id: channel.id,
      title: channel.title,
      fullTitle: channel.title.startsWith('SomaFM') ? channel.title : `SomaFM ${channel.title}`,
      description: trim(channel.description),
      dj: channel.dj,
      genre: channel.genre.replace(/\|/g, '/'),
      lastPlaying: channel.lastPlaying,
      listeners: channel.listeners,
      stream: streamHighestQuality,
      image: channel.image
    });
  }

  if (options.sortChannels) {
    channels.sort(compareChannelObjects);
  }

  return Promise.resolve(channels);
}

function compareChannelObjects(a, b) {
  return a.title.toLowerCase().localeCompare(b.title.toLowerCase());
}

function getHighestQualityStream(channel, streams) {
  for (const stream of streams) {
    for (const playlist of channel.playlists) {
      if (playlist.quality === stream.quality && playlist.format === stream.format) {
        return {
          url: playlist.url,
          format: playlist.format,
          quality: playlist.quality
        };
      }
    }
  }

  return null;
}

function filterChannels(channels, search) {
  return new Promise(resolve => {
    if (Array.isArray(search) && search.length > 0) {
      resolve(applyFilter(channels, search));
    } else if (typeof search === 'string') {
      resolve(applyFilter(channels, search.split(' ')));
    } else {
      resolve(channels);
    }
  });
}

function applyFilter(channels, search) {
  return channels.filter(channel => utils.searchArrayMatchesAny(
    search, [channel.id, channel.title, channel.description, channel.genre, channel.dj]
  ));
}

function getChannel(id, options) {
  options = Object.assign({sortChannels: true}, options);

  return getChannels(options)
    .then(channels => getChannelById(id, channels))
    .then(channel => {
      return getStreamUrls(channel)
        .then(urls => {
          channel.stream.urls = urls;
          setCachedChannels(channels);

          return Promise.resolve(channel);
        })
        .catch(console.log);
    })
    .then(channel => {
      const imageDir = path.join(tempDir, pkg.name);
      channel.imageFile = path.join(imageDir, channel.id);

      if (!fs.existsSync(channel.imageFile)) {
        makeDir.sync(imageDir);
        got.stream(channel.image).pipe(fs.createWriteStream(channel.imageFile));
      }

      return Promise.resolve(channel);
    });
}

function getChannelById(id, channels) {
  return new Promise((resolve, reject) => {
    for (const channel of channels) {
      if (id.toLowerCase() === channel.id) {
        resolve(channel);
      }
    }

    reject(new Error('Channel not found.'));
  });
}

function getStreamUrls(channel) {
  if (channel.stream.urls) {
    return Promise.resolve(channel.stream.urls);
  }

  return getUrlsFromPlaylist(channel.stream.url);
}

function getUrlsFromPlaylist(playlistUrl) {
  return new Promise((resolve, reject) => {
    got(playlistUrl)
      .then(response => {
        const data = ini.decode(response.body);
        const res = [];

        Object.keys(data.playlist)
          .filter(x => x.match(/^File\d+$/))
          .forEach(key => {
            res.push(data.playlist[key]);
          });

        resolve(res);
      })
      .catch(reject);
  });
}

function getCachedChannels() {
  return channelsConf.get('channels') || [];
}

function setCachedChannels(data) {
  channels = data;

  // Cache channels for one minute
  channelsConf.set('channels', data, {maxAge: 60000});

  return Promise.resolve(data);
}

module.exports = {
  getChannels,
  getChannel,
  filterChannels
};
<commit_msg>Code tweaks<commit_after>'use strict';
const fs = require('fs');
const path = require('path');
const got = require('got');
const tempDir = require('temp-dir');
const makeDir = require('make-dir');
const ini = require('ini');
const trim = require('trim');
const CacheConf = require('cache-conf');
const utils = require('./utils');
const pkg = require('./package.json');

const PREFERRED_STREAMS = [
  {quality: 'highest', format: 'mp3'},
  {quality: 'highest', format: 'aac'},
  {quality: 'high', format: 'mp3'},
  {quality: 'high', format: 'aacp'},
  {quality: 'low', format: 'aacp'},
  {quality: 'low', format: 'mp3'}
];

const GOT_OPTS = {headers: {'user-agent': `somafm/${pkg.version} (https://github.com/uschek/somafm)`}};

const configName = (process.env.NODE_ENV === 'test' ? 'channels-test' : 'channels');
const channelsConf = new CacheConf({projectName: pkg.name, configName});

let channels = [];

function getChannels(options) {
  options = Object.assign({}, options);
  options.streams = Object.assign(PREFERRED_STREAMS, options.streams);

  return getChannelsFromAPIOrCache(options)
    .then(channels => filterChannels(channels, options.search));
}

function getChannelsFromAPIOrCache(options) {
  if (channels.length === 0) {
    channels = getCachedChannels();
  }

  if (channels.length === 0 || options.forceUpdate) {
    return getChannelsFromAPI(options)
      .then(setCachedChannels);
  }

  return Promise.resolve(channels);
}

function getChannelsFromAPI(options) {
  return got('https://api.somafm.com/channels.json', GOT_OPTS)
    .then(res => parseJSONData(res.body, options));
}

function parseJSONData(json, options) {
  const data = JSON.parse(json).channels;
  const channels = [];

  for (const channel of data) {
    const streamHighestQuality = getHighestQualityStream(channel, options.streams);

    channels.push({
      id: channel.id,
      title: channel.title,
      fullTitle: channel.title.startsWith('SomaFM') ? channel.title : `SomaFM ${channel.title}`,
      description: trim(channel.description),
      dj: channel.dj,
      genre: channel.genre.replace(/\|/g, '/'),
      lastPlaying: channel.lastPlaying,
      listeners: channel.listeners,
      stream: streamHighestQuality,
      image: channel.image
    });
  }

  if (options.sortChannels) {
    channels.sort(compareChannelObjects);
  }

  return Promise.resolve(channels);
}

function compareChannelObjects(a, b) {
  return a.title.toLowerCase().localeCompare(b.title.toLowerCase());
}

function getHighestQualityStream(channel, streams) {
  for (const stream of streams) {
    for (const playlist of channel.playlists) {
      if (playlist.quality === stream.quality && playlist.format === stream.format) {
        return {
          url: playlist.url,
          format: playlist.format,
          quality: playlist.quality
        };
      }
    }
  }

  return null;
}

function filterChannels(channels, search) {
  return new Promise(resolve => {
    if (Array.isArray(search) && search.length > 0) {
      resolve(applyFilter(channels, search));
    } else if (typeof search === 'string') {
      resolve(applyFilter(channels, search.split(' ')));
    } else {
      resolve(channels);
    }
  });
}

function applyFilter(channels, search) {
  return channels.filter(channel => utils.searchArrayMatchesAny(
    search, [channel.id, channel.title, channel.description, channel.genre, channel.dj]
  ));
}

function getChannel(id, options) {
  options = Object.assign({sortChannels: true}, options);

  return getChannels(options)
    .then(channels => getChannelById(id, channels))
    .then(channel => new Promise(resolve => {
      return getStreamUrls(channel)
        .then(urls => {
          channel.stream.urls = urls;

          setCachedChannels(channels)
            .then(() => {
              resolve(channel);
            });
        })
        .catch(console.log);
    }))
    .then(channel => new Promise(resolve => {
      const imageDir = path.join(tempDir, pkg.name);
      channel.imageFile = path.join(imageDir, channel.id);

      if (!fs.existsSync(channel.imageFile)) {
        makeDir.sync(imageDir);
        got.stream(channel.image).pipe(fs.createWriteStream(channel.imageFile));
      }

      resolve(channel);
    }));
}

function getChannelById(id, channels) {
  return new Promise((resolve, reject) => {
    for (const channel of channels) {
      if (id.toLowerCase() === channel.id) {
        resolve(channel);
      }
    }

    reject(new Error('Channel not found.'));
  });
}

function getStreamUrls(channel) {
  if (channel.stream.urls) {
    return Promise.resolve(channel.stream.urls);
  }

  return getUrlsFromPlaylist(channel.stream.url);
}

function getUrlsFromPlaylist(playlistUrl) {
  return new Promise((resolve, reject) => {
    got(playlistUrl)
      .then(response => {
        const data = ini.decode(response.body);
        const res = [];

        Object.keys(data.playlist)
          .filter(x => x.match(/^File\d+$/))
          .forEach(key => {
            res.push(data.playlist[key]);
          });

        resolve(res);
      })
      .catch(reject);
  });
}

function getCachedChannels() {
  return channelsConf.get('channels') || [];
}

function setCachedChannels(data) {
  return new Promise(resolve => {
    channels = data;

    // Cache channels for one minute
    channelsConf.set('channels', data, {maxAge: 60000});

    resolve(data);
  });
}

module.exports = {
  getChannels,
  getChannel,
  filterChannels
};
<|endoftext|>


<commit_before>var create     = require('./create')
var ssbKeys    = require('ssb-keys')
var path       = require('path')
var osenv      = require('osenv')
var mkdirp     = require('mkdirp')
var rimraf     = require('rimraf')
var valid      = require('./lib/validators')
var pkg        = require('./package.json')

function isString(s) { return 'string' === typeof s }
function isObject(o) { return 'object' === typeof o }
function isFunction (f) { return 'function' === typeof f }

var manifest = {
  get: 'async',
  createFeedStream: 'source',
  createLogStream: 'source',
  messagesByType: 'source',
  createHistoryStream: 'source',
  createUserStream: 'source',
  createWriteStream: 'sink',
  links: 'source',
  add: 'async',
  publish: 'async',
  getAddress: 'sync',
  getLatest: 'async',
  latest: 'source',
  latestSequence: 'async',
  whoami: 'sync',
  del: 'async',
  progress: 'sync',
  status: 'sync',
  getVectorClock: 'async',
  version: 'sync',
  help: 'sync',
  seq: 'async',
  usage: 'sync',
  clock: 'async'
}

module.exports = {
  name: 'db',
  manifest: manifest,
  permissions: {
    master: {allow: null, deny: null},
    anonymous: {allow: ['createHistoryStream'], deny: null}
  },
  init: function (api, opts) {

    // .temp: use a /tmp data directory
    // (useful for testing)
    if(opts.temp) {
      var name = isString(opts.temp) ? opts.temp : ''+Date.now()
      opts.path = path.join(osenv.tmpdir(), name)
      rimraf.sync(opts.path)
    }

    // load/create secure scuttlebutt data directory
    mkdirp.sync(opts.path)

    if(!opts.keys)
      opts.keys = ssbKeys.generate('ed25519', opts.seed && Buffer.from(opts.seed, 'base64'))

    if(!opts.path)
      throw new Error('opts.path *must* be provided, or use opts.temp=name to create a test instance')

    // main interface
    var ssb = create(opts.path, opts, opts.keys)
    //treat the main feed as remote, because it's likely handled like that by others.
    var feed = ssb.createFeed(opts.keys, {remote: true})
    var _close = api.close
    var close = function (arg, cb) {
      if('function' === typeof arg) cb = arg
      ssb.flush(function (err) {
        if(err) return cb(err)
        // override to close the SSB database
        ssb.close(function (err) {
          if (err) return cb(err)
          console.log("fallback to close")
          _close(cb) //multiserver doesn't take a callback on close.
        })
      })
    }

    function since () {
      var plugs = {}
      var sync = true
      for(var k in ssb) {
        if(ssb[k] && isObject(ssb[k]) && isFunction(ssb[k].since)) {
          plugs[k] = ssb[k].since.value
          sync = sync && (plugs[k] === ssb.since.value)
        }
      }
      return {
        since: ssb.since.value,
        plugins: plugs,
        sync: sync,
      }
    }
    var self
    return self = {
      id                       : feed.id,
      keys                     : opts.keys,

      ready                    : function () {
        return ssb.ready.value
      },

      progress                 : function () {
        return ssb.progress
      },

      status                   : function () {
        return {progress: self.progress(), db: ssb.status, sync: since() }
      },

      version                  : function () {
        return pkg.version
      },

      //temporary!
      _flumeUse                :
        function (name, flumeview) {
          ssb.use(name, flumeview)
          return ssb[name]
        },

  //    usage                    : valid.sync(usage, 'string?|boolean?'),
      close                    : close,
      del: valid.async(ssb.del, 'msgLink'),
      publish                  : valid.async(feed.add, 'string|msgContent'),
      add                      : valid.async(ssb.add, 'msg'),
      queue                      : valid.async(ssb.queue, 'msg'),
      get                      : valid.async(ssb.get, 'msgLink|number|object'),

      post                     : ssb.post,
      addMap                   : ssb.addMap,

      since                    : since,

      latest                   : ssb.latest,
      getLatest                : valid.async(ssb.getLatest, 'feedId'),
      latestSequence           : valid.async(ssb.latestSequence, 'feedId'),
      createFeed               : ssb.createFeed,
      whoami                   : function () { return { id: feed.id } },
      createFeedStream         : valid.source(ssb.createFeedStream, 'readStreamOpts?'),
      createHistoryStream      : valid.source(ssb.createHistoryStream, ['createHistoryStreamOpts'], ['feedId', 'number?', 'boolean?']),
      createLogStream          : valid.source(ssb.createLogStream, 'readStreamOpts?'),
      createUserStream         : valid.source(ssb.createUserStream, 'createUserStreamOpts'),
      links                    : valid.source(ssb.links, 'linksOpts'),
      sublevel                 : ssb.sublevel,
      messagesByType           : valid.source(ssb.messagesByType, 'string|messagesByTypeOpts'),
      createWriteStream        : ssb.createWriteStream,
      getVectorClock           : ssb.getVectorClock,
      getAtSequence            : ssb.getAtSequence,
      addUnboxer               : ssb.addUnboxer,
      box                      : ssb.box,
      help                     : function () { return require('./help') }
    }
  }
}

<commit_msg>Remove non-existed methods from muxrpc manifest<commit_after>var create     = require('./create')
var ssbKeys    = require('ssb-keys')
var path       = require('path')
var osenv      = require('osenv')
var mkdirp     = require('mkdirp')
var rimraf     = require('rimraf')
var valid      = require('./lib/validators')
var pkg        = require('./package.json')

function isString(s) { return 'string' === typeof s }
function isObject(o) { return 'object' === typeof o }
function isFunction (f) { return 'function' === typeof f }

var manifest = {
  get: 'async',
  createFeedStream: 'source',
  createLogStream: 'source',
  messagesByType: 'source',
  createHistoryStream: 'source',
  createUserStream: 'source',
  createWriteStream: 'sink',
  links: 'source',
  add: 'async',
  publish: 'async',
  getLatest: 'async',
  latest: 'source',
  latestSequence: 'async',
  whoami: 'sync',
  del: 'async',
  progress: 'sync',
  status: 'sync',
  getVectorClock: 'async',
  version: 'sync',
  help: 'sync',
}

module.exports = {
  name: 'db',
  manifest: manifest,
  permissions: {
    master: {allow: null, deny: null},
    anonymous: {allow: ['createHistoryStream'], deny: null}
  },
  init: function (api, opts) {

    // .temp: use a /tmp data directory
    // (useful for testing)
    if(opts.temp) {
      var name = isString(opts.temp) ? opts.temp : ''+Date.now()
      opts.path = path.join(osenv.tmpdir(), name)
      rimraf.sync(opts.path)
    }

    // load/create secure scuttlebutt data directory
    mkdirp.sync(opts.path)

    if(!opts.keys)
      opts.keys = ssbKeys.generate('ed25519', opts.seed && Buffer.from(opts.seed, 'base64'))

    if(!opts.path)
      throw new Error('opts.path *must* be provided, or use opts.temp=name to create a test instance')

    // main interface
    var ssb = create(opts.path, opts, opts.keys)
    //treat the main feed as remote, because it's likely handled like that by others.
    var feed = ssb.createFeed(opts.keys, {remote: true})
    var _close = api.close
    var close = function (arg, cb) {
      if('function' === typeof arg) cb = arg
      ssb.flush(function (err) {
        if(err) return cb(err)
        // override to close the SSB database
        ssb.close(function (err) {
          if (err) return cb(err)
          console.log("fallback to close")
          _close(cb) //multiserver doesn't take a callback on close.
        })
      })
    }

    function since () {
      var plugs = {}
      var sync = true
      for(var k in ssb) {
        if(ssb[k] && isObject(ssb[k]) && isFunction(ssb[k].since)) {
          plugs[k] = ssb[k].since.value
          sync = sync && (plugs[k] === ssb.since.value)
        }
      }
      return {
        since: ssb.since.value,
        plugins: plugs,
        sync: sync,
      }
    }
    var self
    return self = {
      id                       : feed.id,
      keys                     : opts.keys,

      ready                    : function () {
        return ssb.ready.value
      },

      progress                 : function () {
        return ssb.progress
      },

      status                   : function () {
        return {progress: self.progress(), db: ssb.status, sync: since() }
      },

      version                  : function () {
        return pkg.version
      },

      //temporary!
      _flumeUse                :
        function (name, flumeview) {
          ssb.use(name, flumeview)
          return ssb[name]
        },

      close                    : close,
      del: valid.async(ssb.del, 'msgLink'),
      publish                  : valid.async(feed.add, 'string|msgContent'),
      add                      : valid.async(ssb.add, 'msg'),
      queue                      : valid.async(ssb.queue, 'msg'),
      get                      : valid.async(ssb.get, 'msgLink|number|object'),

      post                     : ssb.post,
      addMap                   : ssb.addMap,

      since                    : since,

      latest                   : ssb.latest,
      getLatest                : valid.async(ssb.getLatest, 'feedId'),
      latestSequence           : valid.async(ssb.latestSequence, 'feedId'),
      createFeed               : ssb.createFeed,
      whoami                   : function () { return { id: feed.id } },
      createFeedStream         : valid.source(ssb.createFeedStream, 'readStreamOpts?'),
      createHistoryStream      : valid.source(ssb.createHistoryStream, ['createHistoryStreamOpts'], ['feedId', 'number?', 'boolean?']),
      createLogStream          : valid.source(ssb.createLogStream, 'readStreamOpts?'),
      createUserStream         : valid.source(ssb.createUserStream, 'createUserStreamOpts'),
      links                    : valid.source(ssb.links, 'linksOpts'),
      sublevel                 : ssb.sublevel,
      messagesByType           : valid.source(ssb.messagesByType, 'string|messagesByTypeOpts'),
      createWriteStream        : ssb.createWriteStream,
      getVectorClock           : ssb.getVectorClock,
      getAtSequence            : ssb.getAtSequence,
      addUnboxer               : ssb.addUnboxer,
      box                      : ssb.box,
      help                     : function () { return require('./help') }
    }
  }
}

<|endoftext|>


<commit_before>'use strict';

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { StyleSheet, Dimensions, Vibration, Animated, Easing, View, Text, Platform, TouchableWithoutFeedback, PermissionsAndroid } from 'react-native';

import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { RNCamera as Camera } from 'react-native-camera';

const CAMERA_FLASH_MODE = Camera.Constants.FlashMode;
const CAMERA_FLASH_MODES = [CAMERA_FLASH_MODE.torch, CAMERA_FLASH_MODE.on, CAMERA_FLASH_MODE.off, CAMERA_FLASH_MODE.auto];

export default class QRCodeScanner extends Component {
	static propTypes = {
		onRead: PropTypes.func.isRequired,
		vibrate: PropTypes.bool,
		reactivate: PropTypes.bool,
		reactivateTimeout: PropTypes.number,
		cameraTimeout: PropTypes.number,
		fadeIn: PropTypes.bool,
		showMarker: PropTypes.bool,
		cameraType: PropTypes.oneOf(['front', 'back']),
		customMarker: PropTypes.element,
		containerStyle: PropTypes.any,
		cameraStyle: PropTypes.any,
		markerStyle: PropTypes.any,
		topViewStyle: PropTypes.any,
		bottomViewStyle: PropTypes.any,
		topContent: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
		bottomContent: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
		notAuthorizedView: PropTypes.element,
		permissionDialogTitle: PropTypes.string,
		permissionDialogMessage: PropTypes.string,
		buttonPositive: PropTypes.string,
		checkAndroid6Permissions: PropTypes.bool,
		flashMode: PropTypes.oneOf(CAMERA_FLASH_MODES),
		cameraProps: PropTypes.object,
		cameraTimeoutView: PropTypes.element,
	};

	static defaultProps = {
		onRead: () => console.log('QR code scanned!'),
		reactivate: false,
		vibrate: true,
		reactivateTimeout: 0,
		cameraTimeout: 0,
		fadeIn: true,
		showMarker: false,
		cameraType: 'back',
		notAuthorizedView: (
			<View
				style={{
					flex: 1,
					alignItems: 'center',
					justifyContent: 'center',
				}}
			>
				<Text
					style={{
						textAlign: 'center',
						fontSize: 16,
					}}
				>
					Camera not authorized
				</Text>
			</View>
		),
		pendingAuthorizationView: (
			<View
				style={{
					flex: 1,
					alignItems: 'center',
					justifyContent: 'center',
				}}
			>
				<Text
					style={{
						textAlign: 'center',
						fontSize: 16,
					}}
				>
					...
				</Text>
			</View>
		),
		permissionDialogTitle: 'Info',
		permissionDialogMessage: 'Need camera permission',
		buttonPositive: 'OK',
		checkAndroid6Permissions: false,
		flashMode: CAMERA_FLASH_MODE.auto,
		cameraProps: {},
		cameraTimeoutView: (
			<View
				style={{
					flex: 0,
					alignItems: 'center',
					justifyContent: 'center',
					backgroundColor: 'transparent',
					height: Dimensions.get('window').width,
					width: Dimensions.get('window').width,
					backgroundColor: 'black',
				}}
			>
				<Text style={{ color: 'white' }}>Tap to activate camera</Text>
			</View>
		),
	};

	constructor(props) {
		super(props);
		this.state = {
			scanning: false,
			isCameraActivated: true,
			fadeInOpacity: new Animated.Value(0),
			isAuthorized: false,
			isAuthorizationChecked: false,
			disableVibrationByUser: false,
		};

		this._scannerTimeout = null;
		this._handleBarCodeRead = this._handleBarCodeRead.bind(this);
	}

	componentDidMount() {
		if (Platform.OS === 'ios') {
			request(PERMISSIONS.IOS.CAMERA).then(cameraStatus => {
				this.setState({
					isAuthorized: cameraStatus === RESULTS.GRANTED,
					isAuthorizationChecked: true,
				});
			});
		} else if (Platform.OS === 'android' && this.props.checkAndroid6Permissions) {
			PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
				title: this.props.permissionDialogTitle,
				message: this.props.permissionDialogMessage,
			}).then(granted => {
				const isAuthorized = granted === PermissionsAndroid.RESULTS.GRANTED;

				this.setState({ isAuthorized, isAuthorizationChecked: true });
			});
		} else {
			this.setState({ isAuthorized: true, isAuthorizationChecked: true });
		}

		if (this.props.fadeIn) {
			Animated.sequence([
				Animated.delay(1000),
				Animated.timing(this.state.fadeInOpacity, {
					toValue: 1,
					easing: Easing.inOut(Easing.quad),
					useNativeDriver: true,
				}),
			]).start();
		}
	}

	componentWillUnmount() {
		if (this._scannerTimeout !== null) {
			clearTimeout(this._scannerTimeout);
		}
		this._scannerTimeout = null;
	}

	disable() {
		this.setState({ disableVibrationByUser: true });
	}
	enable() {
		this.setState({ disableVibrationByUser: false });
	}

	_setScanning(value) {
		this.setState({ scanning: value });
	}

	_setCamera(value) {
		this.setState(
			{
				isCameraActivated: value,
				scanning: false,
				fadeInOpacity: new Animated.Value(0),
			},
			() => {
				if (value && this.props.fadeIn) {
					if (this.props.fadeIn) {
						Animated.sequence([
							Animated.delay(10),
							Animated.timing(this.state.fadeInOpacity, {
								toValue: 1,
								easing: Easing.inOut(Easing.quad),
								useNativeDriver: true,
							}),
						]).start();
					}
				}
			}
		);
	}

	_handleBarCodeRead(e) {
		if (!this.state.scanning && !this.state.disableVibrationByUser) {
			if (this.props.vibrate) {
				Vibration.vibrate();
			}
			this._setScanning(true);
			this.props.onRead(e);
			if (this.props.reactivate) {
				this._scannerTimeout = setTimeout(() => this._setScanning(false), this.props.reactivateTimeout);
			}
		}
	}

	_renderTopContent() {
		if (this.props.topContent) {
			return this.props.topContent;
		}
		return null;
	}

	_renderBottomContent() {
		if (this.props.bottomContent) {
			return this.props.bottomContent;
		}
		return null;
	}

	_renderCameraMarker() {
		if (this.props.showMarker) {
			if (this.props.customMarker) {
				return this.props.customMarker;
			} else {
				return (
					<View style={styles.rectangleContainer}>
						<View style={[styles.rectangle, this.props.markerStyle ? this.props.markerStyle : null]} />
					</View>
				);
			}
		}
		return null;
	}

	_renderCameraComponent() {
		return (
			<Camera
				androidCameraPermissionOptions={{
					title: this.props.permissionDialogTitle,
					message: this.props.permissionDialogMessage,
					buttonPositive: this.props.buttonPositive,
				}}
				style={[styles.camera, this.props.cameraStyle]}
				onBarCodeRead={this._handleBarCodeRead.bind(this)}
				type={this.props.cameraType}
				flashMode={this.props.flashMode}
				captureAudio={false}
				{...this.props.cameraProps}
			>
				{this._renderCameraMarker()}
			</Camera>
		);
	}

	_renderCamera() {
		const { notAuthorizedView, pendingAuthorizationView, cameraType, cameraTimeoutView } = this.props;

		if (!this.state.isCameraActivated) {
			return <TouchableWithoutFeedback onPress={() => this._setCamera(true)}>{cameraTimeoutView}</TouchableWithoutFeedback>;
		}

		const { isAuthorized, isAuthorizationChecked } = this.state;
		if (isAuthorized) {
			if (this.props.cameraTimeout > 0) {
				this.timer && clearTimeout(this.timer);
				this.timer = setTimeout(() => this._setCamera(false), this.props.cameraTimeout);
			}

			if (this.props.fadeIn) {
				return (
					<Animated.View
						style={{
							opacity: this.state.fadeInOpacity,
							backgroundColor: 'transparent',
							height: styles.camera.height,
						}}
					>
						{this._renderCameraComponent()}
					</Animated.View>
				);
			}
			return this._renderCameraComponent();
		} else if (!isAuthorizationChecked) {
			return pendingAuthorizationView;
		} else {
			return notAuthorizedView;
		}
	}

	reactivate() {
		this._setScanning(false);
	}

	render() {
		return (
			<View style={[styles.mainContainer, this.props.containerStyle]}>
				<View style={[styles.infoView, this.props.topViewStyle]}>{this._renderTopContent()}</View>
				{this._renderCamera()}
				<View style={[styles.infoView, this.props.bottomViewStyle]}>{this._renderBottomContent()}</View>
			</View>
		);
	}
}

const styles = StyleSheet.create({
	mainContainer: {
		flex: 1,
	},
	infoView: {
		flex: 2,
		justifyContent: 'center',
		alignItems: 'center',
		width: Dimensions.get('window').width,
	},

	camera: {
		flex: 0,
		alignItems: 'center',
		justifyContent: 'center',
		backgroundColor: 'transparent',
		height: Dimensions.get('window').width,
		width: Dimensions.get('window').width,
	},

	rectangleContainer: {
		flex: 1,
		alignItems: 'center',
		justifyContent: 'center',
		backgroundColor: 'transparent',
	},

	rectangle: {
		height: 250,
		width: 250,
		borderWidth: 2,
		borderColor: '#00FF00',
		backgroundColor: 'transparent',
	},
});
<commit_msg>Update index.js<commit_after>'use strict';

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { StyleSheet, Dimensions, Vibration, Animated, Easing, View, Text, Platform, TouchableWithoutFeedback, PermissionsAndroid } from 'react-native';

import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { RNCamera as Camera } from 'react-native-camera';

const CAMERA_FLASH_MODE = Camera.Constants.FlashMode;
const CAMERA_FLASH_MODES = [CAMERA_FLASH_MODE.torch, CAMERA_FLASH_MODE.on, CAMERA_FLASH_MODE.off, CAMERA_FLASH_MODE.auto];

export default class QRCodeScanner extends Component {
	static propTypes = {
		onRead: PropTypes.func.isRequired,
		vibrate: PropTypes.bool,
		reactivate: PropTypes.bool,
		reactivateTimeout: PropTypes.number,
		cameraTimeout: PropTypes.number,
		fadeIn: PropTypes.bool,
		showMarker: PropTypes.bool,
		cameraType: PropTypes.oneOf(['front', 'back']),
		customMarker: PropTypes.element,
		containerStyle: PropTypes.any,
		cameraStyle: PropTypes.any,
		markerStyle: PropTypes.any,
		topViewStyle: PropTypes.any,
		bottomViewStyle: PropTypes.any,
		topContent: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
		bottomContent: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
		notAuthorizedView: PropTypes.element,
		permissionDialogTitle: PropTypes.string,
		permissionDialogMessage: PropTypes.string,
		buttonPositive: PropTypes.string,
		checkAndroid6Permissions: PropTypes.bool,
		flashMode: PropTypes.oneOf(CAMERA_FLASH_MODES),
		cameraProps: PropTypes.object,
		cameraTimeoutView: PropTypes.element,
	};

	static defaultProps = {
		onRead: () => console.log('QR code scanned!'),
		reactivate: false,
		vibrate: true,
		reactivateTimeout: 0,
		cameraTimeout: 0,
		fadeIn: true,
		showMarker: false,
		cameraType: 'back',
		notAuthorizedView: (
			<View
				style={{
					flex: 1,
					alignItems: 'center',
					justifyContent: 'center',
				}}
			>
				<Text
					style={{
						textAlign: 'center',
						fontSize: 16,
					}}
				>
					Camera not authorized
				</Text>
			</View>
		),
		pendingAuthorizationView: (
			<View
				style={{
					flex: 1,
					alignItems: 'center',
					justifyContent: 'center',
				}}
			>
				<Text
					style={{
						textAlign: 'center',
						fontSize: 16,
					}}
				>
					...
				</Text>
			</View>
		),
		permissionDialogTitle: 'Info',
		permissionDialogMessage: 'Need camera permission',
		buttonPositive: 'OK',
		checkAndroid6Permissions: false,
		flashMode: CAMERA_FLASH_MODE.auto,
		cameraProps: {},
		cameraTimeoutView: (
			<View
				style={{
					flex: 0,
					alignItems: 'center',
					justifyContent: 'center',
					height: Dimensions.get('window').width,
					width: Dimensions.get('window').width,
					backgroundColor: 'black',
				}}
			>
				<Text style={{ color: 'white' }}>Tap to activate camera</Text>
			</View>
		),
	};

	constructor(props) {
		super(props);
		this.state = {
			scanning: false,
			isCameraActivated: true,
			fadeInOpacity: new Animated.Value(0),
			isAuthorized: false,
			isAuthorizationChecked: false,
			disableVibrationByUser: false,
		};

		this._scannerTimeout = null;
		this._handleBarCodeRead = this._handleBarCodeRead.bind(this);
	}

	componentDidMount() {
		if (Platform.OS === 'ios') {
			request(PERMISSIONS.IOS.CAMERA).then(cameraStatus => {
				this.setState({
					isAuthorized: cameraStatus === RESULTS.GRANTED,
					isAuthorizationChecked: true,
				});
			});
		} else if (Platform.OS === 'android' && this.props.checkAndroid6Permissions) {
			PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
				title: this.props.permissionDialogTitle,
				message: this.props.permissionDialogMessage,
			}).then(granted => {
				const isAuthorized = granted === PermissionsAndroid.RESULTS.GRANTED;

				this.setState({ isAuthorized, isAuthorizationChecked: true });
			});
		} else {
			this.setState({ isAuthorized: true, isAuthorizationChecked: true });
		}

		if (this.props.fadeIn) {
			Animated.sequence([
				Animated.delay(1000),
				Animated.timing(this.state.fadeInOpacity, {
					toValue: 1,
					easing: Easing.inOut(Easing.quad),
					useNativeDriver: true,
				}),
			]).start();
		}
	}

	componentWillUnmount() {
		if (this._scannerTimeout !== null) {
			clearTimeout(this._scannerTimeout);
		}
		this._scannerTimeout = null;
	}

	disable() {
		this.setState({ disableVibrationByUser: true });
	}
	enable() {
		this.setState({ disableVibrationByUser: false });
	}

	_setScanning(value) {
		this.setState({ scanning: value });
	}

	_setCamera(value) {
		this.setState(
			{
				isCameraActivated: value,
				scanning: false,
				fadeInOpacity: new Animated.Value(0),
			},
			() => {
				if (value && this.props.fadeIn) {
					if (this.props.fadeIn) {
						Animated.sequence([
							Animated.delay(10),
							Animated.timing(this.state.fadeInOpacity, {
								toValue: 1,
								easing: Easing.inOut(Easing.quad),
								useNativeDriver: true,
							}),
						]).start();
					}
				}
			}
		);
	}

	_handleBarCodeRead(e) {
		if (!this.state.scanning && !this.state.disableVibrationByUser) {
			if (this.props.vibrate) {
				Vibration.vibrate();
			}
			this._setScanning(true);
			this.props.onRead(e);
			if (this.props.reactivate) {
				this._scannerTimeout = setTimeout(() => this._setScanning(false), this.props.reactivateTimeout);
			}
		}
	}

	_renderTopContent() {
		if (this.props.topContent) {
			return this.props.topContent;
		}
		return null;
	}

	_renderBottomContent() {
		if (this.props.bottomContent) {
			return this.props.bottomContent;
		}
		return null;
	}

	_renderCameraMarker() {
		if (this.props.showMarker) {
			if (this.props.customMarker) {
				return this.props.customMarker;
			} else {
				return (
					<View style={styles.rectangleContainer}>
						<View style={[styles.rectangle, this.props.markerStyle ? this.props.markerStyle : null]} />
					</View>
				);
			}
		}
		return null;
	}

	_renderCameraComponent() {
		return (
			<Camera
				androidCameraPermissionOptions={{
					title: this.props.permissionDialogTitle,
					message: this.props.permissionDialogMessage,
					buttonPositive: this.props.buttonPositive,
				}}
				style={[styles.camera, this.props.cameraStyle]}
				onBarCodeRead={this._handleBarCodeRead.bind(this)}
				type={this.props.cameraType}
				flashMode={this.props.flashMode}
				captureAudio={false}
				{...this.props.cameraProps}
			>
				{this._renderCameraMarker()}
			</Camera>
		);
	}

	_renderCamera() {
		const { notAuthorizedView, pendingAuthorizationView, cameraType, cameraTimeoutView } = this.props;

		if (!this.state.isCameraActivated) {
			return <TouchableWithoutFeedback onPress={() => this._setCamera(true)}>{cameraTimeoutView}</TouchableWithoutFeedback>;
		}

		const { isAuthorized, isAuthorizationChecked } = this.state;
		if (isAuthorized) {
			if (this.props.cameraTimeout > 0) {
				this.timer && clearTimeout(this.timer);
				this.timer = setTimeout(() => this._setCamera(false), this.props.cameraTimeout);
			}

			if (this.props.fadeIn) {
				return (
					<Animated.View
						style={{
							opacity: this.state.fadeInOpacity,
							backgroundColor: 'transparent',
							height: styles.camera.height,
						}}
					>
						{this._renderCameraComponent()}
					</Animated.View>
				);
			}
			return this._renderCameraComponent();
		} else if (!isAuthorizationChecked) {
			return pendingAuthorizationView;
		} else {
			return notAuthorizedView;
		}
	}

	reactivate() {
		this._setScanning(false);
	}

	render() {
		return (
			<View style={[styles.mainContainer, this.props.containerStyle]}>
				<View style={[styles.infoView, this.props.topViewStyle]}>{this._renderTopContent()}</View>
				{this._renderCamera()}
				<View style={[styles.infoView, this.props.bottomViewStyle]}>{this._renderBottomContent()}</View>
			</View>
		);
	}
}

const styles = StyleSheet.create({
	mainContainer: {
		flex: 1,
	},
	infoView: {
		flex: 2,
		justifyContent: 'center',
		alignItems: 'center',
		width: Dimensions.get('window').width,
	},

	camera: {
		flex: 0,
		alignItems: 'center',
		justifyContent: 'center',
		backgroundColor: 'transparent',
		height: Dimensions.get('window').width,
		width: Dimensions.get('window').width,
	},

	rectangleContainer: {
		flex: 1,
		alignItems: 'center',
		justifyContent: 'center',
		backgroundColor: 'transparent',
	},

	rectangle: {
		height: 250,
		width: 250,
		borderWidth: 2,
		borderColor: '#00FF00',
		backgroundColor: 'transparent',
	},
});
<|endoftext|>


<commit_before>var DomHandler = require("domhandler").Handler,
    DomUtils = require("domhandler").Utils,
    CSSselect = require("CSSselect");

function Handler(options){
	if(!(this instanceof Handler)) return new Handler(options);
	var that = this;
	this._handler = DomHandler.call(this, function(dom){
		that.emit("dom", dom);
	}, options, function(elem){
		that.emit("element", elem);
	});
}

require("util").inherits(Handler, require("events").EventEmitter);
Object.getOwnPropertyNames(DomHandler.prototype).forEach(function(name){
	Handler.prototype[name] = DomHandler.prototype[name];
});

Handler.prototype.select = function(selector, cb){
	if(typeof selector === "string"){
		selector = CSSselect.parse(selector);
	}

	this.on("element", function(elem){
		if(selector(elem)) cb(elem);
	});
};

Handler.prototype.remove = function(selector){
	this.select(selector, DomUtils.removeElement);
};

module.exports = Handler;<commit_msg>removed useless ._handler reference<commit_after>var DomHandler = require("domhandler").Handler,
    DomUtils = require("domhandler").Utils,
    CSSselect = require("CSSselect");

function Handler(options){
	if(!(this instanceof Handler)) return new Handler(options);
	var that = this;
	DomHandler.call(this, function(dom){
		that.emit("dom", dom);
	}, options, function(elem){
		that.emit("element", elem);
	});
}

require("util").inherits(Handler, require("events").EventEmitter);
Object.getOwnPropertyNames(DomHandler.prototype).forEach(function(name){
	Handler.prototype[name] = DomHandler.prototype[name];
});

Handler.prototype.select = function(selector, cb){
	if(typeof selector === "string"){
		selector = CSSselect.parse(selector);
	}

	this.on("element", function(elem){
		if(selector(elem)) cb(elem);
	});
};

Handler.prototype.remove = function(selector){
	this.select(selector, DomUtils.removeElement);
};

module.exports = Handler;<|endoftext|>


<commit_before>const Discord = require('discord.js');
const https = require("https");
const tokens = require("./token.js");
const client = new Discord.Client();

client.on('ready', () => {
	client.user.setGame("on Shotbow");
	console.log("Successfully logged in!");
});

client.on('message', message => {
	if (message.channel.name !== "shoutbox") return;
	if (message.content.substr(0, 1) !== "!") return;
	var separate = message.content.split(" ");
	var command = separate[0].substr(1).toLowerCase();
	var argument = 1 in separate ? separate[1].toLowerCase() :  null;
	if (getListOfCommands().indexOf(command) > -1 || getListOfHiddenCommands().indexOf(command) > -1) {
		executeCommand(command, message, argument);
	} else if (command in getListOfAliases()) {
		executeCommand(getListOfAliases()[command], message, argument);
	}
});

client.login(tokens.getToken());

function getListOfCommands() {
	return [
		'commands',
		'rules',
		'banned',
		'report',
		'staff',
		'social',
		'stuck',
		'about',
		'bug',
		'ts',
		'ip',
		'vote',
		'mcstatus',
		'radio',
		'why',
		'contact'
	];
}

function getListOfAliases() {
	return {
		'twitter': 'social',
		'facebook': 'social',
		'youtube': 'social',
		'googleplus': 'social',
		'gplus': 'social',
		'youku': 'social',
		'playerme': 'social',
		'instagram': 'social',
		'tumblr': 'social',
		'contactus': 'contact',
		'command': 'commands',
		'help': 'commands',
		'bugs': 'bug',
		'bugreport': 'bug',
		'address': 'ip',
		'teamspeak': 'ts',
		'mumble': 'ts',
		'hacker': 'report'
	};
}

function getListOfHiddenCommands() {
	return [
		'fry',
		'xp'
	];
}

function executeCommand(command, message, argument) {
	switch (command) {
		case 'commands':
			var commands = getListOfCommands();
			for (var i = 0; i < commands.length; i ++) commands[i] = `\`!${commands[i]}\``;
			message.channel.send(`All available commands: ${commands.join(", ")}`);
			break;
		case 'rules':
			message.channel.send("Please read our rules here: <https://shotbow.net/forum/p/rules/>");
			break;
		case 'banned':
			message.channel.send("We do not discuss bans in the chatroom. If you are banned, please post a ban appeal — it is the fastest way to get your ban handled! <https://shotbow.net/forum/forums/banappeals/>");
			break;
		case 'report':
			message.channel.send("To report a malicious player, follow our Report a Player guidelines here: <https://shotbow.net/forum/threads/report-a-player-guidelines-read-me-before-posting.344869/>\n\nAre they in game right now?  Type `/report <name>` to report them to our currently online staff!");
			break;
		case 'staff':
			message.channel.send("View the official Shotbow Staff List here: <https://shotbow.net/forum/wiki/shotbow-staff>");
			break;
		case 'social':
			var profiles = {
	            'Facebook': 'https://facebook.com/TheShotbowNetwork',
	            'Twitter': 'https://twitter.com/ShotbowNetwork',
	            'Google+': 'https://google.com/+TheShotbowNetwork',
	            'YouTube': 'https://gaming.youtube.com/user/ShotBowNetwork',
	            'Player.me': 'https://player.me/?invite=shotbow',
	            'Instagram': 'https://instagram.com/shotbownetworkmc/',
	            'Tumblr': 'http://tumblr.shotbow.net/',
	            'Youku': 'http://i.youku.com/shotbow',
	        };
	        var URLProfiles = [];
	        for (var key in profiles) URLProfiles.push(`${key}: <${profiles[key]}>`);
			message.channel.send(`You can follow us online at the following links:\n${URLProfiles.join("\n")}`);
			break;
		case 'stuck':
			message.channel.send("Stuck in a block or area on MineZ? Request to be moved here: <https://shotbow.net/forum/threads/stuck-in-a-block-or-area.266338/>");
			break;
		case 'about':
			message.channel.send("Hi there! My name is **Chat Bot** and I'm an open-sourced Discord bot made specifically for the Shotbow Shoutbox. You can view my code and contribute to me here: <https://github.com/Shotbow/DiscordChatBot>");
			break;
		case 'bug':
			message.channel.send("Help keep our games stable by reporting bugs here: <https://shotbow.net/forum/p/bugs/>");
			break;
		case 'ts':
			message.channel.send("You can connect to our TeamSpeak at `ts.shotbow.net` (but, by personal preference, Discord is better).");
			break;
		case 'ip':
			message.channel.send("Come join me on Shotbow using the IP `play.shotbow.net`!");
			break;
		case 'vote':
	        var votes = {
            	'Planet Minecraft': 'http://www.planetminecraft.com/server/minez-1398788/',
            	'Minecraft Forum': 'http://minecraftforum.net/servers/160-shotbow',
            	'MinecraftServers.org': 'http://minecraftservers.org/server/267066'
        	};
        	var URLVotes = [];
        	for (var key in votes) URLVotes.push(`${key}: <${votes[key]}>`);
			message.channel.send(`Support Shotbow for free by voting for us on the following sites:\n${URLVotes.join("\n")}`);
			break;
		case 'mcstatus':
			https.get("https://status.mojang.com/check", res => {
				var status = "";
				res.setEncoding("utf8");
				res.on("data", data => {
					status += data;
				});
				res.on("end", () => {
					status = JSON.parse(status);
					var formattedStatus = {};
					for (var i = 0; i < status.length; i ++) {
						for (var key in status[i]) {
							formattedStatus[key] = status[i][key];
						}
					}
					var errors = [];
					for (var key in formattedStatus) {
						if (formattedStatus[key] !== "green") {
							if (formattedStatus[key] == "yellow") errors.push(`- \`${key}\` is having some issues`);
							else if (formattedStatus[key] == "red") errors.push(`- \`${key}\` is completely down`);
						}
					}
					if (errors.length == 0) message.channel.send("I just checked Mojang's servers, and everything seems to be working fine!");
					else message.channel.send(`I just checked Mojang's servers, and there appear to be some issues:\n${errors.join("\n")}`);
				});
			});
			break;
		case 'radio':
			message.channel.send("Did you know we have our own radio? Listen to Mine Theft Auto's radio here: <https://www.minetheftauto.com/radio/>");
			break;
		case 'why':
			message.channel.send("That's a good question. Why *does* Shotbow have chat? Chat is not for live admin assistance, it's to help foster the wonderful Shotbow community. Ask questions, and if staff is around they will answer. You can also type `!commands` to see what other tricks I have and the information I can give you!");
			break;
		case 'contact':
			message.channel.send("Some issues, like rank or payment issues, can only be fixed by contacting support through the \"Contact Us\" link, which can be found here: <https://shotbow.net/forum/contact>\n\nPlease allow two business days for a response.");
			break;
		case 'fry':
			// If the person sending the message is an admin OR is Mistri
			if (message.member.roles.has(message.guild.roles.find("name", "Administrator").id)|| message.author.id == "173556767383355392") {
				if (argument == null) message.channel.send("Yes master... but who?");
				else message.channel.send("I must obey my masters...\n\n*zaps " + argument + " with 10,000 volts of electricity*");
			// If the person sending the message is a mini admin or moderator
			} else if (message.member.roles.has(message.guild.roles.find("name", "Mini-Admin").id) || message.member.roles.has(message.guild.roles.find("name", "Moderator").id)) {
				message.channel.send("I'm sorry, but you're not yet strong enough... only masters can fry. However, I like you, so I won't punish you...");
			} else {
				message.channel.send("I try not to be violent... but you all just keep pushing me...\n\n*zaps " + message.author.toString() + " with an electric shock!*");
			}
			break;
		case 'xp':
			message.channel.send("We have a **special** XP code for people who ask for one! Try `IASKEDFORXP`!");
			break;
	}
}<commit_msg>Fun commands in #shoutbox only, the rest everywhere<commit_after>const Discord = require('discord.js');
const https = require("https");
const tokens = require("./token.js");
const client = new Discord.Client();

client.on('ready', () => {
	client.user.setGame("on Shotbow");
	console.log("Successfully logged in!");
});

client.on('message', message => {
	if (message.content.substr(0, 1) !== "!") return;
	var separate = message.content.split(" ");
	var command = separate[0].substr(1).toLowerCase();
	var argument = 1 in separate ? separate[1].toLowerCase() :  null;
	if (getListOfCommands().indexOf(command) > -1) {
		executeCommand(command, message, argument);
	} else if (getListOfHiddenCommands().indexOf(command) > -1) {
		if (message.channel.name !== "shoutbox") return;
		executeCommand(command, message, argument);
	} else if (command in getListOfAliases()) {
		executeCommand(getListOfAliases()[command], message, argument);
	}
});

client.login(tokens.getToken());

function getListOfCommands() {
	return [
		'commands',
		'rules',
		'banned',
		'report',
		'staff',
		'social',
		'stuck',
		'about',
		'bug',
		'ts',
		'ip',
		'vote',
		'mcstatus',
		'radio',
		'why',
		'contact'
	];
}

function getListOfAliases() {
	return {
		'twitter': 'social',
		'facebook': 'social',
		'youtube': 'social',
		'googleplus': 'social',
		'gplus': 'social',
		'youku': 'social',
		'playerme': 'social',
		'instagram': 'social',
		'tumblr': 'social',
		'contactus': 'contact',
		'command': 'commands',
		'help': 'commands',
		'bugs': 'bug',
		'bugreport': 'bug',
		'address': 'ip',
		'teamspeak': 'ts',
		'mumble': 'ts',
		'hacker': 'report'
	};
}

function getListOfHiddenCommands() {
	return [
		'fry',
		'xp'
	];
}

function executeCommand(command, message, argument) {
	switch (command) {
		case 'commands':
			var commands = getListOfCommands();
			for (var i = 0; i < commands.length; i ++) commands[i] = `\`!${commands[i]}\``;
			message.channel.send(`All available commands: ${commands.join(", ")}`);
			break;
		case 'rules':
			message.channel.send("Please read our rules here: <https://shotbow.net/forum/p/rules/>");
			break;
		case 'banned':
			message.channel.send("We do not discuss bans in the chatroom. If you are banned, please post a ban appeal — it is the fastest way to get your ban handled! <https://shotbow.net/forum/forums/banappeals/>");
			break;
		case 'report':
			message.channel.send("To report a malicious player, follow our Report a Player guidelines here: <https://shotbow.net/forum/threads/report-a-player-guidelines-read-me-before-posting.344869/>\n\nAre they in game right now?  Type `/report <name>` to report them to our currently online staff!");
			break;
		case 'staff':
			message.channel.send("View the official Shotbow Staff List here: <https://shotbow.net/forum/wiki/shotbow-staff>");
			break;
		case 'social':
			var profiles = {
	            'Facebook': 'https://facebook.com/TheShotbowNetwork',
	            'Twitter': 'https://twitter.com/ShotbowNetwork',
	            'Google+': 'https://google.com/+TheShotbowNetwork',
	            'YouTube': 'https://gaming.youtube.com/user/ShotBowNetwork',
	            'Player.me': 'https://player.me/?invite=shotbow',
	            'Instagram': 'https://instagram.com/shotbownetworkmc/',
	            'Tumblr': 'http://tumblr.shotbow.net/',
	            'Youku': 'http://i.youku.com/shotbow',
	        };
	        var URLProfiles = [];
	        for (var key in profiles) URLProfiles.push(`${key}: <${profiles[key]}>`);
			message.channel.send(`You can follow us online at the following links:\n${URLProfiles.join("\n")}`);
			break;
		case 'stuck':
			message.channel.send("Stuck in a block or area on MineZ? Request to be moved here: <https://shotbow.net/forum/threads/stuck-in-a-block-or-area.266338/>");
			break;
		case 'about':
			message.channel.send("Hi there! My name is **Chat Bot** and I'm an open-sourced Discord bot made specifically for the Shotbow Shoutbox. You can view my code and contribute to me here: <https://github.com/Shotbow/DiscordChatBot>");
			break;
		case 'bug':
			message.channel.send("Help keep our games stable by reporting bugs here: <https://shotbow.net/forum/p/bugs/>");
			break;
		case 'ts':
			message.channel.send("You can connect to our TeamSpeak at `ts.shotbow.net` (but, by personal preference, Discord is better).");
			break;
		case 'ip':
			message.channel.send("Come join me on Shotbow using the IP `play.shotbow.net`!");
			break;
		case 'vote':
	        var votes = {
            	'Planet Minecraft': 'http://www.planetminecraft.com/server/minez-1398788/',
            	'Minecraft Forum': 'http://minecraftforum.net/servers/160-shotbow',
            	'MinecraftServers.org': 'http://minecraftservers.org/server/267066'
        	};
        	var URLVotes = [];
        	for (var key in votes) URLVotes.push(`${key}: <${votes[key]}>`);
			message.channel.send(`Support Shotbow for free by voting for us on the following sites:\n${URLVotes.join("\n")}`);
			break;
		case 'mcstatus':
			https.get("https://status.mojang.com/check", res => {
				var status = "";
				res.setEncoding("utf8");
				res.on("data", data => {
					status += data;
				});
				res.on("end", () => {
					status = JSON.parse(status);
					var formattedStatus = {};
					for (var i = 0; i < status.length; i ++) {
						for (var key in status[i]) {
							formattedStatus[key] = status[i][key];
						}
					}
					var errors = [];
					for (var key in formattedStatus) {
						if (formattedStatus[key] !== "green") {
							if (formattedStatus[key] == "yellow") errors.push(`- \`${key}\` is having some issues`);
							else if (formattedStatus[key] == "red") errors.push(`- \`${key}\` is completely down`);
						}
					}
					if (errors.length == 0) message.channel.send("I just checked Mojang's servers, and everything seems to be working fine!");
					else message.channel.send(`I just checked Mojang's servers, and there appear to be some issues:\n${errors.join("\n")}`);
				});
			});
			break;
		case 'radio':
			message.channel.send("Did you know we have our own radio? Listen to Mine Theft Auto's radio here: <https://www.minetheftauto.com/radio/>");
			break;
		case 'why':
			message.channel.send("That's a good question. Why *does* Shotbow have chat? Chat is not for live admin assistance, it's to help foster the wonderful Shotbow community. Ask questions, and if staff is around they will answer. You can also type `!commands` to see what other tricks I have and the information I can give you!");
			break;
		case 'contact':
			message.channel.send("Some issues, like rank or payment issues, can only be fixed by contacting support through the \"Contact Us\" link, which can be found here: <https://shotbow.net/forum/contact>\n\nPlease allow two business days for a response.");
			break;
		case 'fry':
			// If the person sending the message is an admin OR is Mistri
			if (message.member.roles.has(message.guild.roles.find("name", "Administrator").id)|| message.author.id == "173556767383355392") {
				if (argument == null) message.channel.send("Yes master... but who?");
				else message.channel.send("I must obey my masters...\n\n*zaps " + argument + " with 10,000 volts of electricity*");
			// If the person sending the message is a mini admin or moderator
			} else if (message.member.roles.has(message.guild.roles.find("name", "Mini-Admin").id) || message.member.roles.has(message.guild.roles.find("name", "Moderator").id)) {
				message.channel.send("I'm sorry, but you're not yet strong enough... only masters can fry. However, I like you, so I won't punish you...");
			} else {
				message.channel.send("I try not to be violent... but you all just keep pushing me...\n\n*zaps " + message.author.toString() + " with an electric shock!*");
			}
			break;
		case 'xp':
			message.channel.send("We have a **special** XP code for people who ask for one! Try `IASKEDFORXP`!");
			break;
	}
}<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var fs = require('fs');
var path = require('path');
var os = require('os');

var debug = require('debug');
// Define some debug logging functions for easy and readable debug messages.
var log = {
  main: debug('HLW'),
  gameStart: debug('HLW:game-start'),
  zoneChange: debug('HLW:zone-change'),
  gameOver: debug('HLW:game-over')
};

// Determine the location of the config and log files.
var configFile, logFile;
if (/^win/.test(os.platform())) {
  log.main('Windows platform detected.');
  var programFiles = 'Program Files';
  if (/64/.test(os.arch())) {
    programFiles += '(x86)';
  }
  logFile = path.join('C:', programFiles, 'Hearthstone', 'Hearthstone_Data', 'output_log.txt');
  configFile = path.join(process.env.LOCALAPPDATA, 'Blizzard', 'Hearthstone', 'log.config');
} else {
  log.main('OS X platform detected.');
  logFile = path.join(process.env.HOME, 'Library', 'Logs', 'Unity', 'Player.log');
  configFile = path.join(process.env.HOME, 'Library', 'Preferences', 'Blizzard', 'Hearthstone', 'log.config');
}
log.main('config file path: %s', configFile);
log.main('log file path: %s', logFile);

// Copy local config file to the correct location.
// We're just gonna do this every time.
var localConfigFile = path.join(__dirname, 'log.config');
fs.createReadStream(localConfigFile).pipe(fs.createWriteStream(configFile));
log.main('Copied log.config file to force Hearthstone to write to its log file.');

// The watcher is an event emitter so we can emit events based on what we parse in the log.
function LogWatcher() {
}
inherits(LogWatcher, EventEmitter);

LogWatcher.prototype.start = function () {
  var self = this;

  log.main('Log watcher started.');
  // Begin watching the Hearthstone log file.
  var fileSize = fs.statSync(logFile).size;
  var players = [];
  var gameOverCount = 0;
  var watcher = fs.watch(logFile, function (event, filename) {

    // We're only going to read the portion of the file that we have not read so far.
    var newFileSize = fs.statSync(logFile).size;
    var sizeDiff = newFileSize - fileSize;
    if (sizeDiff <= 0) {
      fileSize = newFileSize;
      return;
    }
    var buffer = new Buffer(sizeDiff);
    var fileDescriptor = fs.openSync(logFile, 'r');
    fs.readSync(fileDescriptor, buffer, 0, sizeDiff, fileSize + 1);
    fs.closeSync(fileDescriptor);
    fileSize = newFileSize;

    // Iterate over each line in the buffer.
    buffer.toString().split('\n').forEach(function (line) {

      // Check if a card is changing zones.
      var zoneChangeRegex = /name=(.*) id=(\d+).*to (FRIENDLY|OPPOSING) (.*)$/;
      if (zoneChangeRegex.test(line)) {
        var parts = zoneChangeRegex.exec(line);
        var data = {
          cardName: parts[1],
          cardId: parseInt(parts[2]),
          team: parts[3],
          zone: parts[4]
        };
        log.zoneChange('%s moved to %s %s.', data.cardName, data.team, data.zone)
        self.emit('zone-change', data);
      }

      // Check for players entering play and track their team IDs.
      var newPlayerRegex = /Entity=(.*) tag=TEAM_ID value=(.)$/;
      if (newPlayerRegex.test(line)) {
        var parts = newPlayerRegex.exec(line);
        players.push({
          name: parts[1],
          teamId: parseInt(parts[2])
        });
      }

      // Look for mulligan status line that only shows for the local FRIENDLY player.
      // Compare the ID to the team ID and set player zones appropriately.
      var mulliganCountRegex = /id=(\d) ChoiceType=MULLIGAN Cancelable=False CountMin=0 CountMax=\d$/;
      if (mulliganCountRegex.test(line)) {
        var parts = mulliganCountRegex.exec(line);
        var teamId = parseInt(parts[1]);
        players.forEach(function (player) {
          if (teamId === player.teamId) {
            player.team = 'FRIENDLY';
          } else {
            player.team = 'OPPOSING';
          }
        });
        log.gameStart('A game has started.')
        self.emit('game-start', players);
      }

      // Check if the game is over.
      var gameOverRegex = /Entity=(.*) tag=PLAYSTATE value=(LOST|WON)$/;
      if (gameOverRegex.test(line)) {
        var parts = gameOverRegex.exec(line);
        // Set the status for the appropriate player.
        players.forEach(function (player) {
          if (player.name === parts[1]) {
            player.status = parts[2];
          }
        });
        gameOverCount++;
        // When both players have lost, emit a game-over event.
        if (gameOverCount === 2) {
          log.gameOver('The current game has ended.');
          self.emit('game-over', players);
          gameOverCount = 0;
          players = [];
        }
      }

    });

  });

  self.stop = function () {
    watcher.close();
    self.stop = function () {};
  };
};

LogWatcher.prototype.stop = function () {};

// Set the entire module to our emitter.
module.exports = LogWatcher;
<commit_msg>add config and log paths as options to constructor<commit_after>var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var fs = require('fs');
var path = require('path');
var os = require('os');

var debug = require('debug');
// Define some debug logging functions for easy and readable debug messages.
var log = {
  main: debug('HLW'),
  gameStart: debug('HLW:game-start'),
  zoneChange: debug('HLW:zone-change'),
  gameOver: debug('HLW:game-over')
};

// Determine the default location of the config and log files.
var configFile, logFile;
if (/^win/.test(os.platform())) {
  log.main('Windows platform detected.');
  var programFiles = 'Program Files';
  if (/64/.test(os.arch())) {
    programFiles += '(x86)';
  }
  logFile = path.join('C:', programFiles, 'Hearthstone', 'Hearthstone_Data', 'output_log.txt');
  configFile = path.join(process.env.LOCALAPPDATA, 'Blizzard', 'Hearthstone', 'log.config');
} else {
  log.main('OS X platform detected.');
  logFile = path.join(process.env.HOME, 'Library', 'Logs', 'Unity', 'Player.log');
  configFile = path.join(process.env.HOME, 'Library', 'Preferences', 'Blizzard', 'Hearthstone', 'log.config');
}

// The watcher is an event emitter so we can emit events based on what we parse in the log.
function LogWatcher(options) {
    options = options || {};

    this.configFile = options.configFile || configFile;
    this.logFile = options.logFile || logFile;

    log.main('config file path: %s', this.configFile);
    log.main('log file path: %s', this.logFile);

    // Copy local config file to the correct location.
    // We're just gonna do this every time.
    var localConfigFile = path.join(__dirname, 'log.config');
    fs.createReadStream(localConfigFile).pipe(fs.createWriteStream(this.configFile));
    log.main('Copied log.config file to force Hearthstone to write to its log file.');
}
inherits(LogWatcher, EventEmitter);

LogWatcher.prototype.start = function () {
  var self = this;

  log.main('Log watcher started.');
  // Begin watching the Hearthstone log file.
  var fileSize = fs.statSync(this.logFile).size;
  var players = [];
  var gameOverCount = 0;
  var watcher = fs.watch(this.logFile, function (event, filename) {

    // We're only going to read the portion of the file that we have not read so far.
    var newFileSize = fs.statSync(self.logFile).size;
    var sizeDiff = newFileSize - fileSize;
    if (sizeDiff <= 0) {
      fileSize = newFileSize;
      return;
    }
    var buffer = new Buffer(sizeDiff);
    var fileDescriptor = fs.openSync(self.logFile, 'r');
    fs.readSync(fileDescriptor, buffer, 0, sizeDiff, fileSize + 1);
    fs.closeSync(fileDescriptor);
    fileSize = newFileSize;

    // Iterate over each line in the buffer.
    buffer.toString().split('\n').forEach(function (line) {

      // Check if a card is changing zones.
      var zoneChangeRegex = /name=(.*) id=(\d+).*to (FRIENDLY|OPPOSING) (.*)$/;
      if (zoneChangeRegex.test(line)) {
        var parts = zoneChangeRegex.exec(line);
        var data = {
          cardName: parts[1],
          cardId: parseInt(parts[2]),
          team: parts[3],
          zone: parts[4]
        };
        log.zoneChange('%s moved to %s %s.', data.cardName, data.team, data.zone)
        self.emit('zone-change', data);
      }

      // Check for players entering play and track their team IDs.
      var newPlayerRegex = /Entity=(.*) tag=TEAM_ID value=(.)$/;
      if (newPlayerRegex.test(line)) {
        var parts = newPlayerRegex.exec(line);
        players.push({
          name: parts[1],
          teamId: parseInt(parts[2])
        });
      }

      // Look for mulligan status line that only shows for the local FRIENDLY player.
      // Compare the ID to the team ID and set player zones appropriately.
      var mulliganCountRegex = /id=(\d) ChoiceType=MULLIGAN Cancelable=False CountMin=0 CountMax=\d$/;
      if (mulliganCountRegex.test(line)) {
        var parts = mulliganCountRegex.exec(line);
        var teamId = parseInt(parts[1]);
        players.forEach(function (player) {
          if (teamId === player.teamId) {
            player.team = 'FRIENDLY';
          } else {
            player.team = 'OPPOSING';
          }
        });
        log.gameStart('A game has started.')
        self.emit('game-start', players);
      }

      // Check if the game is over.
      var gameOverRegex = /Entity=(.*) tag=PLAYSTATE value=(LOST|WON)$/;
      if (gameOverRegex.test(line)) {
        var parts = gameOverRegex.exec(line);
        // Set the status for the appropriate player.
        players.forEach(function (player) {
          if (player.name === parts[1]) {
            player.status = parts[2];
          }
        });
        gameOverCount++;
        // When both players have lost, emit a game-over event.
        if (gameOverCount === 2) {
          log.gameOver('The current game has ended.');
          self.emit('game-over', players);
          gameOverCount = 0;
          players = [];
        }
      }

    });

  });

  self.stop = function () {
    watcher.close();
    self.stop = function () {};
  };
};

LogWatcher.prototype.stop = function () {};

// Set the entire module to our emitter.
module.exports = LogWatcher;
<|endoftext|>


<commit_before>/* form elements */
export { default as AutoSuggest } from './lib/AutoSuggest';
export { default as Badge } from './lib/Badge';
export { default as Button } from './lib/Button';
export { default as ButtonGroup } from './lib/ButtonGroup';
export { default as Checkbox } from './lib/Checkbox';
export { default as Datepicker, Calendar } from './lib/Datepicker';
export { default as DateRangeWrapper } from './lib/DateRangeWrapper';
export { default as RadioButton } from './lib/RadioButton';
export { default as RadioButtonGroup } from './lib/RadioButtonGroup';
export { default as SegmentedControl } from './lib/SegmentedControl';
export { default as Select } from './lib/Select';
export { default as TabButton } from './lib/TabButton';
export { default as TextArea } from './lib/TextArea';
export { default as TextField } from './lib/TextField';
export { default as Timepicker } from './lib/Timepicker';
export { default as MultiSelection } from './lib/MultiSelection';
export { default as RepeatableField } from './lib/RepeatableField';

/* data containers */
export { default as KeyValue } from './lib/KeyValue';
export { default as MultiColumnList } from './lib/MultiColumnList';
export { default as List } from './lib/List';

/* layout containers */
export { default as Pane } from './lib/Pane';
export { default as PaneHeader } from './lib/PaneHeader';
export { default as PaneSubheader } from './lib/PaneSubheader';
export { default as PaneMenu } from './lib/PaneMenu';
export { default as Paneset } from './lib/Paneset';
export { default as Layer } from './lib/Layer';
export { Grid, Row, Col } from './lib/LayoutGrid';
export { default as Layout } from './lib/Layout';
export { default as LayoutBox } from './lib/LayoutBox';
export { default as LayoutHeader } from './lib/LayoutHeader';
export {
  Accordion,
  AccordionSet,
  DefaultAccordionHeader,
  FilterAccordionHeader,
  ExpandAllButton,
  expandAllFunction
} from './lib/Accordion';

/* misc */
export { default as Icon } from './lib/Icon';
export { default as IconButton } from './lib/IconButton';
export { default as Modal } from './lib/Modal';
export { default as ModalFooter } from './lib/ModalFooter';
export { default as AppIcon } from './lib/AppIcon';
export { default as Avatar } from './lib/Avatar';
export { default as Callout, CalloutElement } from './lib/Callout';
export { Dropdown, UncontrolledDropdown } from './lib/Dropdown';
export { default as DropdownMenu } from './lib/DropdownMenu';
export { default as MenuSection } from './lib/MenuSection';
export { default as EntrySelector } from './lib/EntrySelector';
export { default as FocusLink } from './lib/FocusLink';
export { default as Headline } from './lib/Headline';
export { HotKeys, FocusTrap, HotKeyMapMixin } from './lib/HotKeys';
export { default as IfInterface } from './lib/IfInterface';
export { default as IfPermission } from './lib/IfPermission';
export { default as MenuItem } from './lib/MenuItem';
export { default as MetaSection } from './lib/MetaSection';
export { default as NavList } from './lib/NavList';
export { default as NavListItem } from './lib/NavListItem';
export { default as NavListSection } from './lib/NavListSection';
export { default as Popover } from './lib/Popover';
export { default as Selection, OptionSegment } from './lib/Selection';
export { default as SRStatus } from './lib/SRStatus';
export { default as PasswordStrength } from './lib/PasswordStrength';

/* structures */
export { default as ConfirmationModal } from './lib/ConfirmationModal';
export { default as InfoPopover } from './lib/InfoPopover';
export { default as SearchField } from './lib/SearchField';

/* specific use */
export { default as FilterPane } from './lib/FilterPane';
export { default as FilterGroups, filterState, filters2cql, onChangeFilter } from './lib/FilterGroups';
export { default as FilterControlGroup } from './lib/FilterControlGroup';
export { default as FilterPaneSearch } from './lib/FilterPaneSearch';
export { default as ExportCsv } from './lib/ExportCsv';

/* utilities */
export { default as omitProps } from './util/omitProps';
<commit_msg>Export Pane link button components<commit_after>/* form elements */
export { default as AutoSuggest } from './lib/AutoSuggest';
export { default as Badge } from './lib/Badge';
export { default as Button } from './lib/Button';
export { default as ButtonGroup } from './lib/ButtonGroup';
export { default as Checkbox } from './lib/Checkbox';
export { default as Datepicker, Calendar } from './lib/Datepicker';
export { default as DateRangeWrapper } from './lib/DateRangeWrapper';
export { default as RadioButton } from './lib/RadioButton';
export { default as RadioButtonGroup } from './lib/RadioButtonGroup';
export { default as SegmentedControl } from './lib/SegmentedControl';
export { default as Select } from './lib/Select';
export { default as TabButton } from './lib/TabButton';
export { default as TextArea } from './lib/TextArea';
export { default as TextField } from './lib/TextField';
export { default as Timepicker } from './lib/Timepicker';
export { default as MultiSelection } from './lib/MultiSelection';
export { default as RepeatableField } from './lib/RepeatableField';

/* data containers */
export { default as KeyValue } from './lib/KeyValue';
export { default as MultiColumnList } from './lib/MultiColumnList';
export { default as List } from './lib/List';

/* layout containers */
export { default as Pane } from './lib/Pane';
export { default as PaneBackLink } from './lib/PaneBackLink';
export { default as PaneCloseLink } from './lib/PaneCloseLink';
export { default as PaneHeader } from './lib/PaneHeader';
export { default as PaneSubheader } from './lib/PaneSubheader';
export { default as PaneMenu } from './lib/PaneMenu';
export { default as Paneset } from './lib/Paneset';
export { default as Layer } from './lib/Layer';
export { Grid, Row, Col } from './lib/LayoutGrid';
export { default as Layout } from './lib/Layout';
export { default as LayoutBox } from './lib/LayoutBox';
export { default as LayoutHeader } from './lib/LayoutHeader';
export {
  Accordion,
  AccordionSet,
  DefaultAccordionHeader,
  FilterAccordionHeader,
  ExpandAllButton,
  expandAllFunction
} from './lib/Accordion';

/* misc */
export { default as Icon } from './lib/Icon';
export { default as IconButton } from './lib/IconButton';
export { default as Modal } from './lib/Modal';
export { default as ModalFooter } from './lib/ModalFooter';
export { default as AppIcon } from './lib/AppIcon';
export { default as Avatar } from './lib/Avatar';
export { default as Callout, CalloutElement } from './lib/Callout';
export { Dropdown, UncontrolledDropdown } from './lib/Dropdown';
export { default as DropdownMenu } from './lib/DropdownMenu';
export { default as MenuSection } from './lib/MenuSection';
export { default as EntrySelector } from './lib/EntrySelector';
export { default as FocusLink } from './lib/FocusLink';
export { default as Headline } from './lib/Headline';
export { HotKeys, FocusTrap, HotKeyMapMixin } from './lib/HotKeys';
export { default as IfInterface } from './lib/IfInterface';
export { default as IfPermission } from './lib/IfPermission';
export { default as MenuItem } from './lib/MenuItem';
export { default as MetaSection } from './lib/MetaSection';
export { default as NavList } from './lib/NavList';
export { default as NavListItem } from './lib/NavListItem';
export { default as NavListSection } from './lib/NavListSection';
export { default as Popover } from './lib/Popover';
export { default as Selection, OptionSegment } from './lib/Selection';
export { default as SRStatus } from './lib/SRStatus';
export { default as PasswordStrength } from './lib/PasswordStrength';

/* structures */
export { default as ConfirmationModal } from './lib/ConfirmationModal';
export { default as InfoPopover } from './lib/InfoPopover';
export { default as SearchField } from './lib/SearchField';

/* specific use */
export { default as FilterPane } from './lib/FilterPane';
export { default as FilterGroups, filterState, filters2cql, onChangeFilter } from './lib/FilterGroups';
export { default as FilterControlGroup } from './lib/FilterControlGroup';
export { default as FilterPaneSearch } from './lib/FilterPaneSearch';
export { default as ExportCsv } from './lib/ExportCsv';

/* utilities */
export { default as omitProps } from './util/omitProps';
<|endoftext|>