yamadapc/dit

View on GitHub
lib/reddit.js

Summary

Maintainability
A
1 hr
Test Coverage
'use strict';
var events = require('events');
var util = require('util');
var Promise = require('bluebird');
var _ = require('lodash');
var request = require('superagent');

Promise.promisifyAll(request.Request.prototype);

/**
 * @external Promise
 * @see {@link https://github.com/petkaantonov/bluebird/blob/master/API.md#core Promise}
 */

/**
 * Represents a reddit API session.
 *
 * @constructor Reddit
 *
 * @param {String} user_agent The API client's user agent
 * @param {Object} [config] An optional configuration object
 */

var Reddit = function(user_agent, config) {
  this._user_agent = user_agent;
  this._agent = request.agent();
  this._host = 'https://en.reddit.com/';

  /**
   * The current configuration object. The necessary values for
   * persisting/loading a session from disk.
   *
   * @property {Object} config
   */

  if(config) {
    this.config = config;
  } else {
    this.config = {};
  }

  events.EventEmitter.call(this);
};
util.inherits(Reddit, events.EventEmitter);

/**
 * Logs-in a user into Reddit
 *
 * @param {Object} creds Reddit credentials
 * @param {String} creds.user
 * @param {String} creds.passwd
 * @return {external:Promise} loginP
 */

Reddit.prototype.login = function(creds) {
  creds.api_type = 'json';
  creds.rem = true;

  return this.post('api/login.json', creds)
    .endAsync()
    .bind(this)
    .then(this._checkErrors)
    .then(function(res) {
      this.config.cookie = res.body.json.data.cookie;
      this.config.modhash = res.body.json.data.modhash;
      this.config.user = creds.user;
      return this.config;
    });
};

/**
 * Returns true if the reddit contains a valid session cookie and false
 * otherwise.
 *
 * @return {Boolean}
 */

Reddit.prototype.isLoggedIn = function() {
  return this.config && this.config.cookie && this.config.modhash &&
         this.config.user;
  // TODO && this.config.cookie_expiry > new Date()
};

/**
 * Checks a reddit api response for errors and throws them if so.
 *
 * @param {Object} res - The reddit API's response object
 * @throws Will throw an error if the response has erroed.
 * @return {Object} res
 */

Reddit.prototype._checkErrors = function(res) {
  var json   = res.body && (res.body.json || {}),
      errors = json.errors;

  if(errors && errors.length) {
    throw new Error(
      'API errored with: ' + errors.map(serializeError).join(', ')
    );
  } else if(res.body.error) {
    throw Error('API erroed with: ' + res.body.error);
  } else return res;
};

/**
 * Gets an user's saved reddit links.
 *
 * @api public
 *
 * @param {String} [after] A reddit link id. If provided, will query the reddit
 * API for links after it
 * @return {Promise} A promise to the result of fetching all saved posts
 * recursively
 */

Reddit.prototype.saved = function(after, saved_links) {
  if(!saved_links) {
    saved_links = [];
  }

  var req = this.get('user/' + this.config.user + '/saved.json');

  if(after) {
    req = req
      .query({
        after: after
      });
  }

  var _this = this;

  return req
    .endAsync()
    .then(this._checkErrors)
    .then(function(res) {
      var posts = _.map(res.body.data.children, function(child) {
        var post = {
          url: child.data.url,
          title: child.data.title || child.data.name,
        };

        _this.emit('post.new', post);

        return post;
      });

      saved_links = saved_links.concat(posts);

      if(!res.body.data.after) {
        return saved_links;
      }

      return _this.saved(res.body.data.after, saved_links);
    });
};

/**
 * Posts some json `body` to the reddit API's `path`.
 * @param {String} path - The path to POST to, under the reddit API's host
 * @param {Object} body - The JSON body to POST
 * @return {external:Promise} resP - A promise to the reddit API's response
 */

Reddit.prototype.post = function(path, body) {
  var req = this._agent.post(this._host + path)
    .send(body)
    .set('Content-Type', 'application/x-www-form-urlencoded')
    .set('User-Agent', this._user_agent)
    .set('X-Modhash', this.config.modhash || '');
  return req;
};

/**
 * Gets a JSON response to the reddit API's `path`, passing `query` as a
 * querystring.
 * @param {String} path - The path to GET, under the reddit API's host
 * @param {Object} [query] - A 'querystringable' object to send as query
 * @return {external:Promise} resP - A promise to the reddit API's response
 */

Reddit.prototype.get = function(path, query) {
  var req = this._agent.get(this._host + path)
    .query(query)
    .set('Content-Type', 'application/x-www-form-urlencoded')
    .set('Cookie', 'reddit_session=' + this.config.cookie)
    .set('X-Modhash', this.config.modhash || '')
    .set('User-Agent', this._user_agent);
  return req;
};

/**
 * Serializes an errors Array into an error message of format: 'CODE: MESSAGE'.
 * @param {Array} err
 * @param {{String|Number}} err[0] - The error code
 * @param {String} err[1] - The error message
 */

function serializeError(err) {
  return err[0] + ': ' + err[1]; // CODE: MESSAGE
}

// expose the Reddit class
module.exports = Reddit;