Source: sources/photo_source_reddit.js

/*
 *  Copyright (c) 2015-2017, Michael A. Updike All rights reserved.
 *  Licensed under the BSD-3-Clause
 *  https://opensource.org/licenses/BSD-3-Clause
 *  https://github.com/opus1269/photo-screen-saver/blob/master/LICENSE.md
 */
(function() {
  'use strict';
  window.app = window.app || {};

  new ExceptionHandler();

  /**
   * Extension's redirect uri
   * @type {string}
   * @const
   * @private
   * @memberOf app.RedditSource
   */
  const _REDIRECT_URI =
      `https://${chrome.runtime.id}.chromiumapp.org/reddit`;

  /**
   * Reddit rest API authorization key
   * @type {string}
   * @const
   * @private
   * @memberOf app.RedditSource
   */
  const _KEY = 'bATkDOUNW_tOlg';

  /**
   * Max photos to return
   * @type {int}
   * @const
   * @default
   * @private
   * @memberOf app.RedditSource
   */
  const _MAX_PHOTOS = 100;

  /**
   * Min size of photo to use
   * @type {int}
   * @const
   * @default
   * @private
   * @memberOf app.RedditSource
   */
  const _MIN_SIZE = 750;

  /**
   * Max size of photo to use
   * @type {int}
   * @const
   * @default
   * @private
   * @memberOf app.RedditSource
   */
  const _MAX_SIZE = 3500;

  /**
   * Expose reddit API
   * @type {Function}
   * @private
   * @memberOf app.RedditSource
   */
  let _snoocore;

  /**
   * A potential source of photos from reddit
   * @alias app.RedditSource
   */
  app.RedditSource = class extends app.PhotoSource {

    /**
     * Create a new photo source
     * @param {string} useKey - The key for if the source is selected
     * @param {string} photosKey - The key for the collection of photos
     * @param {string} type - A descriptor of the photo source
     * @param {string} desc - A human readable description of the source
     * @param {boolean} isDaily - Should the source be updated daily
     * @param {boolean} isArray - Is the source an Array of photo Arrays
     * @param {?Object} [loadArg=null] - optional arg for load function
     * @constructor
     */
    constructor(useKey, photosKey, type, desc, isDaily, isArray,
                loadArg = null) {
      super(useKey, photosKey, type, desc, isDaily, isArray, loadArg);
    }

    /**
     * Parse the size from the submission title.
     * this is the old way reddit did it
     * @param {string} title - submission title
     * @returns {{width: int, height: int}} Photo size
     * @private
     */
    static _getSize(title) {
      const ret = {width: -1, height: -1};
      const regex = /\[(\d*)\D*(\d*)\]/;
      const res = title.match(regex);
      if (res) {
        ret.width = parseInt(res[1], 10);
        ret.height = parseInt(res[2], 10);
      }
      return ret;
    }

    /**
     * Build the list of photos for one page of items
     * @param {Array} children - Array of objects from reddit
     * @returns {app.PhotoSource.Photo[]} Array of photos
     * @private
     */
    static _processChildren(children) {
      const photos = [];
      let url;
      let width = 1;
      let height = 1;

      for (const child of children) {
        const data = child.data;
        if (!data.over_18) {
          // skip NSFW
          if (data.preview && data.preview.images) {
            // new way. has full size image and array of reduced
            // resolutions
            let item = data.preview.images[0];
            url = item.source.url;
            width = parseInt(item.source.width, 10);
            height = parseInt(item.source.height, 10);
            if (Math.max(width, height) > _MAX_SIZE) {
              // too big. get the largest reduced resolution image
              item = item.resolutions[item.resolutions.length - 1];
              url = item.url.replace(/&/g, '&');
              width = parseInt(item.width, 10);
              height = parseInt(item.height, 10);
            }
          } else if (data.title) {
            // old way of specifying images - parse size from title
            const size = app.RedditSource._getSize(data.title);
            url = data.url;
            width = size.width;
            height = size.height;
          }
        }

        const asp = width / height;
        const author = data.author;
        if (asp && !isNaN(asp) && (Math.max(width, height) >= _MIN_SIZE) &&
            (Math.max(width, height) <= _MAX_SIZE)) {
          app.PhotoSource.addPhoto(photos, url, author, asp, data.url);
        }
      }
      return photos;
    }

    /**
     * Fetch the photos for this source
     * @returns {Promise<app.PhotoSource.Photo[]>} Array of photos
     */
    fetchPhotos() {
      let photos = [];
      if (!_snoocore) {
        return Promise.reject(new Error('Snoocore library failed to load'));
      }
      
      return _snoocore(`${this._loadArg}hot`).listing({
        limit: _MAX_PHOTOS,
      }).then((slice) => {
        photos =
            photos.concat(app.RedditSource._processChildren(slice.children));
        return slice.next();
      }).then((slice) => {
        photos =
            photos.concat(app.RedditSource._processChildren(slice.children));
        return Promise.resolve(photos);
      }).catch((err) => {
        let msg = err.message;
        if (msg) {
          // extract first sentence
          const idx = msg.indexOf('.');
          if (idx !== -1) {
            msg = msg.substring(0, idx + 1);
          }
        } else {
          msg = 'Unknown Error';
        }
        return Promise.reject(new Error(msg));
      });
    }
  };

  /**
   * Event: called when document and resources are loaded
   * @private
   * @memberOf Background
   */
  function _onLoad() {
    try {
      const Snoocore = window.Snoocore;
      if (typeof Snoocore !== 'undefined') {
        _snoocore = new Snoocore({
          userAgent: 'photo-screen-saver',
          throttle: 0,
          oauth: {
            type: 'implicit',
            key: _KEY,
            redirectUri: _REDIRECT_URI,
            scope: ['read'],
          },
        });
      }
    } catch (ex) {
      Chrome.GA.exception(ex, 'Snoocore library failed to load', false);
      _snoocore = null;
    }
  }

  // listen for document and resources loaded
  window.addEventListener('load', _onLoad);
})(window);