konsumer/tvinfo

View on GitHub
index.js

Summary

Maintainability
B
4 hrs
Test Coverage
if (!Promise){
  var Promise = require('bluebird');
}

var xml = require('xml2js').parseString,
  titleCase = require('to-title-case'),
  path = require('path'),
  http = require('http'),
  csv = require('csv-parse'),
  Agent = require('agentkeepalive');

var keepaliveAgent = new Agent({
  maxSockets: 10,
  maxFreeSockets: 10,
  keepAlive: true,
  keepAliveMsecs: 30000 // keepalive for 30 seconds
});

function get(endpoint, raw, options){
  return new Promise(function(resolve, reject){
    options = options || {
      host: 'services.tvrage.com',
      port: 80,
      method: 'GET',
      path: endpoint,
      agent: keepaliveAgent
    };
    var req = http.request(options, function (res) {
      var chunks = [];
      
      res.on('data', function (chunk) {
        chunks.push(chunk);
      });
      
      res.on('end', function () {
        if (raw){ return resolve(chunks.join()); }
        xml(chunks.join(), {strict:false, normalizeTags:true, normalize:true, mergeAttrs:true, explicitArray:false}, function(err, x){
          if (err){ return reject(err); }
          resolve(x);
        });
      });
    });

    req.on('error', reject);
    req.end();
  });
}

/**
 * Search for a show
 * @param  {String}  show_name the show to sarch for
 * @param  {Boolean} full      return large records?
 * @return {Promise}
 */
exports.search = function(show_name, full){
  var s = encodeURIComponent(show_name);
  var sr = function(i){ return i.results.show; };
  return full ?
    get('/feeds/full_search.php?show=' + s).then(sr) :
    get('/feeds/search.php?show=' + s).then(sr);
};

/**
 * Get current upcoming schedule
 * @param  {String} country (optional) US, UK, NL
 * @param  {Boolean} full   return large records?
 * @return {Promise}
 */
exports.schedule = function(country){
  var url = '/feeds/fullschedule.php';
  if (country){
    url += '?country=' + country;
  }
  var re = function(a){
    return a;
  };
  return get(url).then(function(i){
    return i.schedule.day.map(function(d){
      if (!d || !d.time || !d.time.length) { return; }
      return d.time.map(function(t){
        if (!t || !t.show){ return; }
        t.show.time = t.ATTR;
        t.show.day = d.ATTR;
        t.show.name = t.show.NAME;
        delete t.show.NAME;
        return t.show;
      }).filter(re);
    }).filter(re);
  });
};

/**
 * Get full list of shows
 * @return {Promise}
 */
exports.shows = function(){
  return new Promise(function(resolve, reject){
    // cached copy of same data
    var options = {
      host: 'epguides.com',
      port: 80,
      method: 'GET',
      path: '/common/allshows.txt',
      agent: keepaliveAgent
    };
    get(null, true, options).then(function(str){
      csv(str, {relax:true,skip_empty_lines:true,trim:true,auto_parse:true,columns:["title", "directory", "id", "start_date", "end_date", "episode_count", "runtime", "network", "country"]}, function(err, data){
        if (err) { return reject(err); }
        data.shift();
        resolve(data);
      });
    });
  });
};

/**
 * Get info about a single show
 * @param  {String}  sid  show-id
 * @param  {Boolean} full return large records?
 * @return {Promise}
 */
exports.show = function(sid, full){
  return full ?
    get('/feeds/full_show_info.php?sid=' + sid).then(function(i){ return i.show; }) : 
    get('/feeds/showinfo.php?sid=' + sid).then(function(i){
      for (var k in i.showinfo){
        if (k.substr(0,4)==='show'){
          i.showinfo[ k.substr(4) ] = i.showinfo[k];
          delete i.showinfo[k];
        }
      }
      return i.showinfo;
    });
};

/**
 * Get episodes for a show
 * @param  {String}  sid  show-id
 * @return {Promise}
 */
exports.episodes = function(sid){
  return get('/feeds/episode_list.php?sid=' + sid).then(function(i){
    return i.show.episodelist.season.map(function(e){
      return e.episode;
    });
  });
};

/**
 * Get info about a single episode
 * @param  {String} show_name the show to sarch for
 * @param  {Number} season     Season number
 * @param  {Number} episode   Episode number
 * @param  {Boolean} exact    Is show_name exact or a search?
 * @return {Promise}
 */
exports.episode = function(show_name, season, episode, exact){
  exact = exact ? 1 : 0;
  var url = '/feeds/episodeinfo.php?show=' + encodeURIComponent(show_name) + '&exact=' + exact + '&ep=' + season + 'x' + episode;
  return get(url).then(function(i){ return i.show; });
};

/**
 * Given a video filename, try to guess TV info
 * big time ripoff: https://www.npmjs.com/package/episoder
 * @param  {String} filename the filename you are trying to guess things from
 * @param  {Object} options  options from episoder
 * @return {Object}          TV info
 */
exports.filename = function(filename, options) {
  var ext = path.extname(filename).toLowerCase(),
  // the following regex should match:
  //   Community S01E04.mp4
  //   Community s01e04.mp4
  //   Community 1x04.mp4
  //   Community 1-04.mp4
  //   Community/S01E04.mp4
  //   Community/s01e04.mp4
  //   Community/1x04.mp4
  //   Community/1-04.mp4
  //   Community/Season 1/Episode 4.mp4
    re = /(.*)\D(\d{1,2})[ex\-](\d{1,2})/i,
    searchResults = filename.match(re),
    show,
    season,
    episode,
    offset,
    episodeObject = {};

  options = options || {};

  offset = options.offset || 0;
  if (searchResults === null) {
    // this regex should match:
    //   Community Season 1 Episode 4.mp4
    // (case insensitive)
    re = /(.*)Season.*?(\d{1,2}).*Episode\D*?(\d{1,2})/i;
    searchResults = filename.match(re);
  }

  if (searchResults === null) {
    // this regex should match:
    //   Community 104.mp4
    re = /(.*)\D(\d)(\d\d)\D/;
    searchResults = filename.match(re);
  }

  if (searchResults === null && options.season) {
    // this regex should match:
    //   Community 04.mp4
    // but only if we've specified a season with season flag
    re = /(.*)\D(\d+)\D/;
    searchResults = filename.match(re);
  }

  if (searchResults === null && options.season && options.show) {
    // this regex should match:
    //   04.mp4
    // but only if we've specified a season and show with flags
    re = /(\d+)\D/;
    searchResults = filename.match(re);
  }

  try {
    show = options.show || searchResults[1];
  } catch (e) {
    return null;
  }
  show = titleCase(show
      // remove hanging characters
      .replace(/^[\-.\s]+|[\-.\s\/]+$/g, "")
      .trim());

  if (options.episode) {
    episode = options.episode + offset;
    if (searchResults !== null) {
      searchResults.pop();
    }
  } else {
    try {
      episode = Number(searchResults.pop()) + offset;
    } catch (e) {
      return null;
    }
  }

  season = options.season || Number(searchResults.pop());

  episodeObject = {
    originalFilename: filename,
    name: show,
    season: season,
    episode: episode,
    extension: ext
  };
  return episodeObject;
};