<commit_before>gpf.require.define({}, function () {
    "use strict";

    //region Source and SourceArray definitions
    return gpf.define({
        $class: "Source",

        constructor: function (array, source, dependencies) {
            this._array = array;
            this._name = source.name;
            this._index = array.getLength();
            if (source.load !== false) {
                this._load = true;
            }
            if (source.test !== false) {
                this._test = true;
            }
            if (source.tags) {
                this._tags = source.tags.split(" ");
            }
            if (dependencies) {
                this._processDependencies(dependencies);
            }
        },

        /**
         * Source array
         *
         * @type {SourceArray}
         */
        _array: null,

        /** Source name */
        _name: "",

        /** @gpf:read _name */
        getName: function () {
            return this._name;
        },

        /** Source index */
        _index: -1,

        /** @gpf:read _index */
        getIndex: function () {
            return this._index;
        },

        /** @gpf:write _index */
        setIndex: function (value) {
            this._index = value;
        },

        /** Source is loaded */
        _load: false,

        /** @gpf:read _load */
        getLoad: function () {
            return this._load;
        },

        /** Source has a test counterpart */
        _test: false,

        /** @gpf:read _test */
        getTest: function () {
            return this._test;
        },

        /** Tags */
        _tags: [],

        /** @gpf:read _tags */
        getTags: function () {
            return this._tags;
        },

        /**
         * List of dependencies (boot is excluded)
         *
         * @type {String[]}
         */
        _dependsOn: [],

        /** @gpf:read _dependsOn */
        getDependencies: function () {
            return this._dependsOn;
        },

        /**
         * List of module dependencies
         *
         * @type {String[]}
         */
        _dependencyOf: [],

        /** @gpf:read _dependencyOf */
        getDependencyOf: function () {
            return this._dependencyOf;
        },

        /**
         * Extract from the dependencies dictionary
         *
         * @param {Object} dependencies dictionary
         */
        _processDependencies: function (dependencies) {
            this._dependsOn = dependencies[this._name] || [];
            this._dependencyOf = [];
            Object.keys(dependencies).forEach(function (name) {
                var nameDependencies = dependencies[name];
                if (nameDependencies.indexOf(this._name) > -1) {
                    this._dependencyOf.push(name);
                }
            }, this);
        },

        /**
         * Change the load setting
         *
         * @param {Boolean} value New value for the setting
         * @return {Boolean} Update done
         */
        _setLoad: function (value) {
            if (!value) {
                // Allow only if all dependencies are not loaded
                if (this._dependencyOf.some(function (name) {
                    return this._array.byName(name).getLoad();
                }, this)) {
                    return false;
                }
            }
            this._load = value;
            return true;
        },

        /**
         * Change the load setting
         *
         * @param {Boolean} value New value for test
         * @return {Boolean} Update done
         */
        _setTest: function (value) {
            this._test = value;
            return true;
        },

        /** Checked state (exists, obsolete, new) */
        _checkedState: "",

        /**
         * Create the exported version of the source
         *
         * @return {Object} Object to be converted to JSON and saved
         */
        "export": function () {
            var result = {
                name: this._name
            };
            if (this._load) {
                if (!this._test) {
                    result.test = false;
                }
            } else {
                result.load = false;
            }
            if (this._tags.length) {
                result.tags = this._tags.join(" ");
            }
            return result;
        },

        isReadOnly: function () {
            return this._name === "boot";
        },

        testProperty: function (propertyName) {
            if (!this._load) {
                return false;
            }
            if (propertyName === "test") {
                return this._test;
            }
            return true;
        },

        setProperty: function (propertyName, value) {
            if (propertyName === "load") {
                return this._setLoad(value);
            }
            if (propertyName === "test") {
                return this._setTest(value);
            }
        },

        hasDependencies: function () {
            return this._dependsOn.length > 0;
        },

        isReferenced: function () {
            return this._dependencyOf.length > 0;
        },

        setCheckedState: function (checkedState) {
            this._checkedState = checkedState;
        },

        getCheckedState: function () {
            return this._checkedState;
        }

    });

});
<commit_msg>no-magic-numbers (#280)<commit_after>gpf.require.define({}, function () {
    "use strict";

    var NOT_FOUND = -1;

    //region Source and SourceArray definitions
    return gpf.define({
        $class: "Source",

        constructor: function (array, source, dependencies) {
            this._array = array;
            this._name = source.name;
            this._index = array.getLength();
            if (source.load !== false) {
                this._load = true;
            }
            if (source.test !== false) {
                this._test = true;
            }
            if (source.tags) {
                this._tags = source.tags.split(" ");
            }
            if (dependencies) {
                this._processDependencies(dependencies);
            }
        },

        /**
         * Source array
         *
         * @type {SourceArray}
         */
        _array: null,

        /** Source name */
        _name: "",

        /** @gpf:read _name */
        getName: function () {
            return this._name;
        },

        /** Source index */
        _index: NOT_FOUND,

        /** @gpf:read _index */
        getIndex: function () {
            return this._index;
        },

        /** @gpf:write _index */
        setIndex: function (value) {
            this._index = value;
        },

        /** Source is loaded */
        _load: false,

        /** @gpf:read _load */
        getLoad: function () {
            return this._load;
        },

        /** Source has a test counterpart */
        _test: false,

        /** @gpf:read _test */
        getTest: function () {
            return this._test;
        },

        /** Tags */
        _tags: [],

        /** @gpf:read _tags */
        getTags: function () {
            return this._tags;
        },

        /**
         * List of dependencies (boot is excluded)
         *
         * @type {String[]}
         */
        _dependsOn: [],

        /** @gpf:read _dependsOn */
        getDependencies: function () {
            return this._dependsOn;
        },

        /**
         * List of module dependencies
         *
         * @type {String[]}
         */
        _dependencyOf: [],

        /** @gpf:read _dependencyOf */
        getDependencyOf: function () {
            return this._dependencyOf;
        },

        /**
         * Extract from the dependencies dictionary
         *
         * @param {Object} dependencies dictionary
         */
        _processDependencies: function (dependencies) {
            this._dependsOn = dependencies[this._name] || [];
            this._dependencyOf = [];
            Object.keys(dependencies).forEach(function (name) {
                var nameDependencies = dependencies[name];
                if (nameDependencies.indexOf(this._name) !== NOT_FOUND) {
                    this._dependencyOf.push(name);
                }
            }, this);
        },

        /**
         * Change the load setting
         *
         * @param {Boolean} value New value for the setting
         * @return {Boolean} Update done
         */
        _setLoad: function (value) {
            if (!value) {
                // Allow only if all dependencies are not loaded
                if (this._dependencyOf.some(function (name) {
                    return this._array.byName(name).getLoad();
                }, this)) {
                    return false;
                }
            }
            this._load = value;
            return true;
        },

        /**
         * Change the load setting
         *
         * @param {Boolean} value New value for test
         * @return {Boolean} Update done
         */
        _setTest: function (value) {
            this._test = value;
            return true;
        },

        /** Checked state (exists, obsolete, new) */
        _checkedState: "",

        /**
         * Create the exported version of the source
         *
         * @return {Object} Object to be converted to JSON and saved
         */
        "export": function () {
            var result = {
                name: this._name
            };
            if (this._load) {
                if (!this._test) {
                    result.test = false;
                }
            } else {
                result.load = false;
            }
            if (this._tags.length) {
                result.tags = this._tags.join(" ");
            }
            return result;
        },

        isReadOnly: function () {
            return this._name === "boot";
        },

        testProperty: function (propertyName) {
            if (!this._load) {
                return false;
            }
            if (propertyName === "test") {
                return this._test;
            }
            return true;
        },

        setProperty: function (propertyName, value) {
            if (propertyName === "load") {
                return this._setLoad(value);
            }
            if (propertyName === "test") {
                return this._setTest(value);
            }
        },

        hasDependencies: function () {
            return Boolean(this._dependsOn.length);
        },

        isReferenced: function () {
            return Boolean(this._dependencyOf.length);
        },

        setCheckedState: function (checkedState) {
            this._checkedState = checkedState;
        },

        getCheckedState: function () {
            return this._checkedState;
        }

    });

});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2017 Ryan Hughes
 *
 * This file is part of CoursePro.
 *
 * CoursePro is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import request from 'request-promise-native';
import URI from 'urijs';
import fs from 'fs-promise';
import asyncjs from 'async';
import dns from 'dns-then';
import mkdirp from 'mkdirp-promise';
import objectHash from 'object-hash';
import path from 'path';
import htmlparser from 'htmlparser2';

import utils from './utils';
import macros from './macros';

// This file is a transparent wrapper around the request library that changes some default settings so scraping is a lot faster.
// This file adds:
// Automatic retry, with a delay in between reqeusts.
// Limit the max number of simultaneous requests (this used to be done with d3-queue, but is now done with agent.maxSockets)
// Application layer DNS cacheing. The request library (and the built in http library) will do a separate DNS lookup for each request
//    This DNS cacheing will do one DNS lookup per hostname (www.ccis.northeastern.edu, wl11gp.neu.edu) and cache the result.
//    This however, does change the URL that is sent to the request library (hostname -> pre-fetched ip), which means that the https verificaton will be comparing
//    the URL in the cert with the IP in the url, which will fail. Need to disable https verification for this to work.
// Keep-alive connections. Keep TCP connections open between requests. This significantly speeds up scraping speeds (1hr -> 20min)
// Ignore invalid HTTPS certificates and outdated ciphers. Some school sites have really old and outdated sites. We want to scrape them even if their https is misconfigured.
// Saves all pages to disk in development so parsers are faster and don't need to hit actuall websites to test updates for scrapers
// ignores request cookies when matching request for caching
// see the request function for details about input (same as request input + some more stuff) and output (same as request 'response' object + more stuff)

// TODO: improve getBaseHost to use the list of top level domains

// This object must be created once per process
// Attributes are added to this object when it is used
// This is the total number of requests per host
// https://github.com/request/request
const separateReqDefaultPool = { maxSockets: 100, keepAlive: true, maxFreeSockets: 100 };

// Specific limits for some sites. CCIS has active measures against one IP making too many requests
// and will reject request if too many are made too quickly.
// Some other schools' servers will crash/slow to a crawl if too many requests are sent too quickly.
const separateReqPools = {
  'www.ccis.northeastern.edu': { maxSockets: 8, keepAlive: true, maxFreeSockets: 8 },

  // Needed for https://www.northeastern.edu/cssh/faculty
  // Looks like northeastern.edu is just a request redirector and sends any requests for /cssh to another server
  // This is the server that was crashing when tons of requests were sent to /cssh
  // So only requests to /cssh would 500, and not all of northeastern.edu.
  'www.northeastern.edu': { maxSockets: 70, keepAlive: true, maxFreeSockets: 70 },
};

const MAX_RETRY_COUNT = 35;

// These numbers are in ms.
const RETRY_DELAY = 20000;
const RETRY_DELAY_DELTA = 15000;

class Request {

  constructor() {
    this.openRequests = 0;

    this.dnsPromises = {};
  }

  // Gets the base hostname from a url.
  // fafjl.google.com -> google.com
  // subdomain.bob.co -> bob.co
  // bob.co -> bob.co
  getBaseHost(url) {
    var homepage = new URI(url).hostname();
    if (!homepage || homepage == '') {
      elog('ERROR: could not find homepage of', url);
      return;
    }

    var match = homepage.match(/[^.]+\.[^.]+$/i);
    if (!match) {
      console.log('ERROR: homepage match failed...', homepage);
      return;
    }
    return match[0];
  }

  // Transforms a HTML string into a htmlparser2 DOM
  // Don't use for new code, only here for legacy coursepro code.
  handleRequestResponce(body, callback) {
    var handler = new htmlparser.DomHandler(callback);
    var parser = new htmlparser.Parser(handler);
    parser.write(body);
    parser.done();
  };

  // By default, needle and nodejs does a DNS lookup for each request.
  // Avoid that by only doing a dns lookup once per domain
  async getDns(hostname) {
    if (this.dnsPromises[hostname]) {
      return this.dnsPromises[hostname];
    }

    utils.verbose('Hitting dns lookup for', hostname);

    // Just the host + subdomains are needed, eg blah.google.com
    if (hostname.startsWith('http://') || hostname.startsWith('https://')) {
      utils.error(hostname);
    }

    const promise = dns.lookup(hostname, {
      all: true,
      family: 4,
    });

    this.dnsPromises[hostname] = promise;

    const result = await promise;

    if (result.length > 1) {
      console.log('INFO: more than 1 dns result', result, hostname);
    }

    return result;
  }

  standardizeInputConfig(config, method = 'GET') {
    if (typeof config === 'string') {
      config = {
        method: method,
        url: config,
      };
    }

    if (!config.headers) {
      config.headers = {};
    }

    return config;
  }

  async fireRequest(config) {
    config = this.standardizeInputConfig(config);

    // Default to JSON for POST bodies
    if (config.method === 'POST' && !config.headers['Content-Type']) {
      config.headers['Content-Type'] = 'application/json';
    }

    const urlParsed = new URI(config.url);

    const hostname = urlParsed.hostname();

    const dnsResults = await this.getDns(urlParsed.hostname());

    let ip;
    if (dnsResults.length === 0) {
      utils.error('DNS lookup returned 0 results!', JSON.stringify(config));
      return null;
    } else if (dnsResults.length === 1) {
      ip = dnsResults[0].address;
    } else {
      const index = Math.floor(Math.random() * dnsResults.length);
      ip = dnsResults[index].address;
    }

    // Make the start of the new url with the ip from the DNS lookup and the protocol from the url
    const urlStart = new URI(ip).protocol(urlParsed.protocol()).toString();

    // Then add on everything after the host
    const urlWithIp = new URI(urlParsed.resource()).absoluteTo(urlStart).port(urlParsed.port()).toString();

    // Setup the default config
    // Change some settings from the default request settings for
    const defaultConfig = {
      headers: {},
    };

    // Default to JSON for POST bodies
    if (config.method === 'POST') {
      defaultConfig.headers['Content-Type'] = 'application/json';
    }

    // Enable keep-alive to make sequential requests faster
    if (separateReqPools[hostname]) {
      defaultConfig.pool = separateReqPools[hostname];
    } else {
      defaultConfig.pool = separateReqDefaultPool;
    }

    //Ten min
    defaultConfig.timeout = 60 * 10000;

    defaultConfig.resolveWithFullResponse = true;

    // Allow fallback to old depreciated insecure SSL ciphers. Some school websites are really old  :/
    // We don't really care abouzt security (hence the rejectUnauthorized:false), and will accept anything.
    // Additionally, this is needed when doing application layer dns caching because the url no longer matches the url in the cert
    defaultConfig.rejectUnauthorized = false;
    defaultConfig.ciphers = 'ALL';

    // Set the host in the header to the hostname on the url.
    // This is not done automatically because of the application layer dns caching (it would be set to the ip instead)
    defaultConfig.headers.Host = hostname;

    defaultConfig.headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:24.0) Gecko/20100101 Firefox/24.0';

    //trololololol
    //Needed on some old sites that will redirect/block requests when this is not set
    //when a user is requesting a page that is not the entry page of the site
    //temple, etc
    defaultConfig.headers.Referer = config.url;

    // Merge the default config and the input config
    // Need to merge headers and output separately because config.headers object would totally override
    // defaultConfig.headers if merged as one object (Object.assign does shallow merge and not deep merge)
    const output = {};
    const headers = {};

    Object.assign(headers, defaultConfig.headers, config.headers);
    Object.assign(output, defaultConfig, config);

    output.url = urlWithIp;
    output.headers = headers;

    utils.verbose('Firing request to', output.url);

    this.openRequests++;
    let response;
    try {
      response = await request(output);
    } catch (e) {
      this.openRequests --;
      throw e;
    }
    this.openRequests --;
    return response;
  }

  doAnyStringsInArray(array, body) {
    for (let i = 0; i < array.length; i++) {
      if (body.includes(array[i])) {
        return true;
      }
    }
    return false;
  }

  // Outputs a response object. Get the body of this object with ".body".
  async request(config) {
    config = this.standardizeInputConfig(config);

    utils.verbose('Request hitting', config);

    const urlParsed = new URI(config.url);

    let folder;
    let filename;
    let filePath;

  if (macros.DEV) {

      folder = path.join('request_cache', urlParsed.hostname());

      // Make a new requeset without the cookies
      let headersWithoutCookie = {};
      Object.assign(headersWithoutCookie, config.headers)
      headersWithoutCookie.Cookie = undefined

      let configToHash = {}
      Object.assign(configToHash, config)
      configToHash.headers = headersWithoutCookie

      // Ensure only letters and numbers and dots and limit char length
      filename = urlParsed.path().replace(/[^A-Za-z0-9.]/gi, '_').trim().slice(0, 25) + objectHash(configToHash);

      await mkdirp(folder);

      filePath = path.join(folder, filename);

      const exists = await fs.exists(filePath);

      if (exists) {
        let body = await fs.readFile(filePath)
        if (body.length === 0) {
          console.log('Warning, empty cache file, skipping!', filePath)
        }
        else {
          const contents = JSON.parse((body).toString());
          utils.verbose('Loaded ', contents.body.length, 'from cache', config.url);
          return contents;
        }
      }
    }

    let tryCount = 0;

    return new Promise((resolve, reject) => {
      asyncjs.retry({
        times: MAX_RETRY_COUNT,
        interval: RETRY_DELAY + Math.round(Math.random() * RETRY_DELAY_DELTA),
      }, async (callback) => {
        let response;
        tryCount++;
        try {
          response = await this.fireRequest(config);
        } catch (err) {
          // Most sites just give a ECONNRESET or ETIMEDOUT, but dccc also gives a EPROTO and ECONNREFUSED.
          // This will retry for any error code.
          console.log('Try#:', tryCount, 'Code:', err.statusCode || err.RequestError || err.Error || err.message || err, ' Open request count: ', this.openRequests, 'Url:', config.url);
          if (err.response) {
            utils.verbose(err.response.body)
          }
          else {
            utils.verbose(err.message)
          }

          callback(err);
          return;
        }

        // Ensure that body contains given string.
        if (config.requiredInBody && !this.doAnyStringsInArray(config.requiredInBody, response.body)) {
          console.log('Try#:', tryCount, 'Warning, body did not contain specified text', response.body.length, response.statusCode, this.openRequests, config.url);
          callback('Body missing required text.');
          return;
        } else if (response.body.length < 4000 && !config.shortBodyWarning === false) {
          console.log('Warning, short body', config.url, response.body, this.openRequests);
        }

        callback(null, response);
      }, async (err, response) => {
        if (err) {
          reject(err);
          return;
        }

        // Save the response to a file for development
        if (macros.DEV) {
          await fs.writeFile(filePath, JSON.stringify(response.toJSON()));
        }

        // console.log('Parsed', response.body.length, 'from ', config.url);
        console.log('Parsed', response.body.length);
        resolve(response);
      });
    });
  }

  // Helpers for get and post
  async get(config) {
    if (!config) {
      console.log('Warning, request called with no config');
      return null;
    }
    if (typeof config === 'string') {
      return this.request({
        url: config,
        method: 'GET',
      });
    }

    config.method = 'GET';
    return this.request(config);
  }

  async post(config) {
    if (!config) {
      console.log('Warning, request called with no config');
      return null;
    }
    if (typeof config === 'string') {
      return this.request({
        url: config,
        method: 'POST',
      });
    }

    config.method = 'POST';
    return this.request(config);
  }

  async head(config) {
    if (!config) {
      console.log('Warning, request called with no config');
      return null;
    }
    if (typeof config === 'string') {
      return this.request({
        url: config,
        method: 'HEAD',
      });
    }

    config.method = 'HEAD';
    return this.request(config);
  }

  // Do a head request. If that fails, do a get request. If that fails, the site is down and return false
  // need to turn off high retry count
  async isPageUp() {
    throw new Error('This does not work yet');
    // this.head(config)
  }

}

// async function test() {
//   const it = new Request();

//   try {
//     const d = await it.request({
//       url:'http://google.com',
//       headers: {
//         Cookie: 'jfjdklasjfldkasjlkf'
//       }
//     });

//     console.log(d.body, 'NO CRASH');
//   } catch (e) {
//     console.log(e, 'HERE');
//   }
// }

// test()

const instance = new Request();

export default instance;
<commit_msg>revert the max socket change, added analytics for request<commit_after>/*
 * Copyright (c) 2017 Ryan Hughes
 *
 * This file is part of CoursePro.
 *
 * CoursePro is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import request from 'request-promise-native';
import URI from 'urijs';
import fs from 'fs-promise';
import asyncjs from 'async';
import dns from 'dns-then';
import mkdirp from 'mkdirp-promise';
import objectHash from 'object-hash';
import path from 'path';
import htmlparser from 'htmlparser2';

import utils from './utils';
import macros from './macros';

// This file is a transparent wrapper around the request library that changes some default settings so scraping is a lot faster.
// This file adds:
// Automatic retry, with a delay in between reqeusts.
// Limit the max number of simultaneous requests (this used to be done with d3-queue, but is now done with agent.maxSockets)
// Application layer DNS cacheing. The request library (and the built in http library) will do a separate DNS lookup for each request
//    This DNS cacheing will do one DNS lookup per hostname (www.ccis.northeastern.edu, wl11gp.neu.edu) and cache the result.
//    This however, does change the URL that is sent to the request library (hostname -> pre-fetched ip), which means that the https verificaton will be comparing
//    the URL in the cert with the IP in the url, which will fail. Need to disable https verification for this to work.
// Keep-alive connections. Keep TCP connections open between requests. This significantly speeds up scraping speeds (1hr -> 20min)
// Ignore invalid HTTPS certificates and outdated ciphers. Some school sites have really old and outdated sites. We want to scrape them even if their https is misconfigured.
// Saves all pages to disk in development so parsers are faster and don't need to hit actuall websites to test updates for scrapers
// ignores request cookies when matching request for caching
// see the request function for details about input (same as request input + some more stuff) and output (same as request 'response' object + more stuff)

// TODO: improve getBaseHost to use the list of top level domains

// This object must be created once per process
// Attributes are added to this object when it is used
// This is the total number of requests per host
// https://github.com/request/request
const separateReqDefaultPool = { maxSockets: 10000, keepAlive: true, maxFreeSockets: 10000 };

// Specific limits for some sites. CCIS has active measures against one IP making too many requests
// and will reject request if too many are made too quickly.
// Some other schools' servers will crash/slow to a crawl if too many requests are sent too quickly.
const separateReqPools = {
  'www.ccis.northeastern.edu': { maxSockets: 8, keepAlive: true, maxFreeSockets: 8 },

  // Needed for https://www.northeastern.edu/cssh/faculty
  // Looks like northeastern.edu is just a request redirector and sends any requests for /cssh to another server
  // This is the server that was crashing when tons of requests were sent to /cssh
  // So only requests to /cssh would 500, and not all of northeastern.edu.
  'www.northeastern.edu': { maxSockets: 70, keepAlive: true, maxFreeSockets: 70 },
};

const MAX_RETRY_COUNT = 35;

// These numbers are in ms.
const RETRY_DELAY = 20000;
const RETRY_DELAY_DELTA = 15000;

class Request {

  constructor() {
    this.openRequests = 0;

    this.dnsPromises = {};

    // Stuff for analytics
    this.analytics = {
      totalBytesDownloaded: 0,
      totalErrors: 0,
      totalGoodRequests: 0,
      startTime: null
    }

    // Log the progress of things every 5 seconds
    this.timer = null;
  }

  onInterval() {

    utils.log(JSON.stringify(this.analytics, null, 4))

    if (this.openRequests === 0) {
      clearInterval(this.timer)
    }
  }

  // Gets the base hostname from a url.
  // fafjl.google.com -> google.com
  // subdomain.bob.co -> bob.co
  // bob.co -> bob.co
  getBaseHost(url) {
    var homepage = new URI(url).hostname();
    if (!homepage || homepage == '') {
      elog('ERROR: could not find homepage of', url);
      return;
    }

    var match = homepage.match(/[^.]+\.[^.]+$/i);
    if (!match) {
      console.log('ERROR: homepage match failed...', homepage);
      return;
    }
    return match[0];
  }

  // Transforms a HTML string into a htmlparser2 DOM
  // Don't use for new code, only here for legacy coursepro code.
  handleRequestResponce(body, callback) {
    var handler = new htmlparser.DomHandler(callback);
    var parser = new htmlparser.Parser(handler);
    parser.write(body);
    parser.done();
  };

  // By default, needle and nodejs does a DNS lookup for each request.
  // Avoid that by only doing a dns lookup once per domain
  async getDns(hostname) {
    if (this.dnsPromises[hostname]) {
      return this.dnsPromises[hostname];
    }

    utils.verbose('Hitting dns lookup for', hostname);

    // Just the host + subdomains are needed, eg blah.google.com
    if (hostname.startsWith('http://') || hostname.startsWith('https://')) {
      utils.error(hostname);
    }

    const promise = dns.lookup(hostname, {
      all: true,
      family: 4,
    });

    this.dnsPromises[hostname] = promise;

    const result = await promise;

    if (result.length > 1) {
      console.log('INFO: more than 1 dns result', result, hostname);
    }

    return result;
  }

  standardizeInputConfig(config, method = 'GET') {
    if (typeof config === 'string') {
      config = {
        method: method,
        url: config,
      };
    }

    if (!config.headers) {
      config.headers = {};
    }

    return config;
  }

  async fireRequest(config) {
    config = this.standardizeInputConfig(config);

    // Default to JSON for POST bodies
    if (config.method === 'POST' && !config.headers['Content-Type']) {
      config.headers['Content-Type'] = 'application/json';
    }

    const urlParsed = new URI(config.url);

    const hostname = urlParsed.hostname();

    const dnsResults = await this.getDns(urlParsed.hostname());

    let ip;
    if (dnsResults.length === 0) {
      utils.error('DNS lookup returned 0 results!', JSON.stringify(config));
      return null;
    } else if (dnsResults.length === 1) {
      ip = dnsResults[0].address;
    } else {
      const index = Math.floor(Math.random() * dnsResults.length);
      ip = dnsResults[index].address;
    }

    // Make the start of the new url with the ip from the DNS lookup and the protocol from the url
    const urlStart = new URI(ip).protocol(urlParsed.protocol()).toString();

    // Then add on everything after the host
    const urlWithIp = new URI(urlParsed.resource()).absoluteTo(urlStart).port(urlParsed.port()).toString();

    // Setup the default config
    // Change some settings from the default request settings for
    const defaultConfig = {
      headers: {},
    };

    // Default to JSON for POST bodies
    if (config.method === 'POST') {
      defaultConfig.headers['Content-Type'] = 'application/json';
    }

    // Enable keep-alive to make sequential requests faster
    if (separateReqPools[hostname]) {
      defaultConfig.pool = separateReqPools[hostname];
    } else {
      defaultConfig.pool = separateReqDefaultPool;
    }

    //Ten min
    defaultConfig.timeout = 60 * 10000;

    defaultConfig.resolveWithFullResponse = true;

    // Allow fallback to old depreciated insecure SSL ciphers. Some school websites are really old  :/
    // We don't really care abouzt security (hence the rejectUnauthorized:false), and will accept anything.
    // Additionally, this is needed when doing application layer dns caching because the url no longer matches the url in the cert
    defaultConfig.rejectUnauthorized = false;
    defaultConfig.ciphers = 'ALL';

    // Set the host in the header to the hostname on the url.
    // This is not done automatically because of the application layer dns caching (it would be set to the ip instead)
    defaultConfig.headers.Host = hostname;

    defaultConfig.headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:24.0) Gecko/20100101 Firefox/24.0';

    //trololololol
    //Needed on some old sites that will redirect/block requests when this is not set
    //when a user is requesting a page that is not the entry page of the site
    //temple, etc
    defaultConfig.headers.Referer = config.url;

    // Merge the default config and the input config
    // Need to merge headers and output separately because config.headers object would totally override
    // defaultConfig.headers if merged as one object (Object.assign does shallow merge and not deep merge)
    const output = {};
    const headers = {};

    Object.assign(headers, defaultConfig.headers, config.headers);
    Object.assign(output, defaultConfig, config);

    output.url = urlWithIp;
    output.headers = headers;

    utils.verbose('Firing request to', output.url);

    // If there are not any open requests right now, start the interval
    if (this.openRequests === 0) {
      clearInterval(this.timer)
      this.analytics.startTime = new Date().getTime()
      this.timer = setInterval(this.onInterval.bind(this), 5000)
    }

    this.openRequests++;
    let response;
    let error;
    try {
      response = await request(output);
    } catch (e) {
      error = e;
    }
    this.openRequests --;

    if (this.openRequests === 0) {
      clearInterval(this.timer)
    }

    if (error) {
      throw error;
    }

    return response;
  }

  doAnyStringsInArray(array, body) {
    for (let i = 0; i < array.length; i++) {
      if (body.includes(array[i])) {
        return true;
      }
    }
    return false;
  }

  // Outputs a response object. Get the body of this object with ".body".
  async request(config) {
    config = this.standardizeInputConfig(config);

    utils.verbose('Request hitting', config);

    const urlParsed = new URI(config.url);

    let folder;
    let filename;
    let filePath;

  if (macros.DEV) {

      folder = path.join('request_cache', urlParsed.hostname());

      // Make a new requeset without the cookies
      let headersWithoutCookie = {};
      Object.assign(headersWithoutCookie, config.headers)
      headersWithoutCookie.Cookie = undefined

      let configToHash = {}
      Object.assign(configToHash, config)
      configToHash.headers = headersWithoutCookie

      // Ensure only letters and numbers and dots and limit char length
      filename = urlParsed.path().replace(/[^A-Za-z0-9.]/gi, '_').trim().slice(0, 25) + objectHash(configToHash);

      await mkdirp(folder);

      filePath = path.join(folder, filename);

      const exists = await fs.exists(filePath);

      if (exists) {
        let body = await fs.readFile(filePath)
        if (body.length === 0) {
          console.log('Warning, empty cache file, skipping!', filePath)
        }
        else {
          const contents = JSON.parse((body).toString());
          utils.verbose('Loaded ', contents.body.length, 'from cache', config.url);
          return contents;
        }
      }
    }

    let tryCount = 0;

    return new Promise((resolve, reject) => {
      asyncjs.retry({
        times: MAX_RETRY_COUNT,
        interval: RETRY_DELAY + Math.round(Math.random() * RETRY_DELAY_DELTA),
      }, async (callback) => {
        let response;
        tryCount++;
        try {
          response = await this.fireRequest(config);
          this.analytics.totalGoodRequests++
        } catch (err) {

          // Most sites just give a ECONNRESET or ETIMEDOUT, but dccc also gives a EPROTO and ECONNREFUSED.
          // This will retry for any error code.

          this.analytics.totalErrors++;
          console.log('Try#:', tryCount, 'Code:', err.statusCode || err.RequestError || err.Error || err.message || err, ' Open request count: ', this.openRequests, 'Url:', config.url);
          if (err.response) {
            utils.verbose(err.response.body)
          }
          else {
            utils.verbose(err.message)
          }

          callback(err);
          return;
        }

        // Ensure that body contains given string.
        if (config.requiredInBody && !this.doAnyStringsInArray(config.requiredInBody, response.body)) {
          console.log('Try#:', tryCount, 'Warning, body did not contain specified text', response.body.length, response.statusCode, this.openRequests, config.url);
          callback('Body missing required text.');
          return;
        } else if (response.body.length < 4000 && !config.shortBodyWarning === false) {
          console.log('Warning, short body', config.url, response.body, this.openRequests);
        }

        callback(null, response);
      }, async (err, response) => {
        if (err) {
          reject(err);
          return;
        }

        // Save the response to a file for development
        if (macros.DEV) {
          await fs.writeFile(filePath, JSON.stringify(response.toJSON()));
        }

        // Don't log this on travis because it causes more than 4 MB to be logged and travis will kill the job
        this.analytics.totalBytesDownloaded += response.body.length;
        if (!process.env.CI) {
          console.log('Parsed', response.body.length, 'from ', config.url);
        }

        resolve(response);
      });
    });
  }

  // Helpers for get and post
  async get(config) {
    if (!config) {
      console.log('Warning, request called with no config');
      return null;
    }
    if (typeof config === 'string') {
      return this.request({
        url: config,
        method: 'GET',
      });
    }

    config.method = 'GET';
    return this.request(config);
  }

  async post(config) {
    if (!config) {
      console.log('Warning, request called with no config');
      return null;
    }
    if (typeof config === 'string') {
      return this.request({
        url: config,
        method: 'POST',
      });
    }

    config.method = 'POST';
    return this.request(config);
  }

  async head(config) {
    if (!config) {
      console.log('Warning, request called with no config');
      return null;
    }
    if (typeof config === 'string') {
      return this.request({
        url: config,
        method: 'HEAD',
      });
    }

    config.method = 'HEAD';
    return this.request(config);
  }

  // Do a head request. If that fails, do a get request. If that fails, the site is down and return false
  // need to turn off high retry count
  async isPageUp() {
    throw new Error('This does not work yet');
    // this.head(config)
  }

}

// async function test() {
//   const it = new Request();

//   try {
//     const d = await it.request({
//       url:'http://google.com',
//       headers: {
//         Cookie: 'jfjdklasjfldkasjlkf'
//       }
//     });

//     console.log(d.body, 'NO CRASH');
//   } catch (e) {
//     console.log(e, 'HERE');
//   }
// }

// test()

const instance = new Request();

export default instance;
<|endoftext|>


<commit_before>var d = Date();
var dv = d.valueOf();
var log = d;
var questionsLoaded = false;
var loadQwhenPt = 9;
var loadNwhenPt = 9;
function saveTextAsFile()
{
	var textToWrite = log;
	var textFileAsBlob = new Blob([textToWrite], {type:'text/plain'});
	var fileNameToSaveAs = "classtrisLog" + dv + ".csv";

	var downloadLink = document.createElement("a");
	downloadLink.download = fileNameToSaveAs;
	downloadLink.innerHTML = "Download File";
	if (window.webkitURL != null)
	{
		// Chrome allows the link to be clicked
		// without actually adding it to the DOM.
		downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
	}
	else
	{
		// Firefox requires the link to be added to the DOM
		// before it can be clicked.
		downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
		downloadLink.onclick = destroyClickedElement;
		downloadLink.style.display = "none";
		document.body.appendChild(downloadLink);
	}

	downloadLink.click();
}

function destroyClickedElement(event)
{
	document.body.removeChild(event.target);
}

function checkUndefined(stud) {
    return stud != undefined;
}

function checkBlank(stud) {
    return stud != "";
}

function loadClassList()
{
	var fileToLoad = document.getElementById("fileToLoad").files[0];

	var fileReader = new FileReader();
	fileReader.onload = function(fileLoadedEvent)
	{
		var textFromFileLoaded = fileLoadedEvent.target.result;
		currentClass = textFromFileLoaded .split("\n");

		currentClass = currentClass.filter(checkUndefined);
		currentClass = currentClass.filter(checkBlank);

		document.getElementById("inputTextToSave").value = textFromFileLoaded;
		dynamicClass = currentClass;
		dynamicClass.sort(function(a, b){return 0.5 - Math.random()});
		document.getElementById("inputTextToSave").value = dynamicClass;
		up = [];

		// teams
		team1 = [];
		team2 = [];
		team3 = [];
		team4 = [];

		// team points
		tp = [0,0,0,0];

		var teamN = 0;
		for (i = 0; i < dynamicClass.length; i++) {
			teamN = i%4;
			switch(i%4) {
				case 1:
					team1.push(dynamicClass[i]);
					break;
				case 2:
					team2.push(dynamicClass[i]);
					break;
				case 3:
					team3.push(dynamicClass[i]);
					break;
				case 0:
					team4.push(dynamicClass[i]);
					break;
			}
		}
		/*
		document.getElementById('team1').innerHTML = team1;
		document.getElementById('team2').innerHTML = team2;
		document.getElementById('team3').innerHTML = team3;
		document.getElementById('team4').innerHTML = team4;
		*/

		for (i = 0; i < 4; i++) {
			up[i] = dynamicClass[i];
		}
		q = 0;
		document.getElementById('up').innerHTML = up[0];
		document.getElementById('next').innerHTML = up[1];
		document.getElementById('then1').innerHTML = up[2];
		document.getElementById('then2').value = up[3];

		if (up[1] === undefined) {
			cycleNext();
		}

	}
	fileReader.readAsText(fileToLoad, "UTF-8");
	log += '\n' + Date();
}

function loadQuestions(mode)
{
	var fileToLoad = document.getElementById("questionLoad").files[0];

	var fileReader = new FileReader();
	fileReader.onload = function(fileLoadedEvent)
	{
		var textFromFileLoaded = fileLoadedEvent.target.result;
		currentQuest = textFromFileLoaded .split("\n");

		currentQuest = currentQuest.filter(checkUndefined);
		currentQuest = currentQuest.filter(checkBlank);

		document.getElementById("inputTextToSave").value = textFromFileLoaded;
		dynamicQuest = currentQuest;
		if (mode == "random") {
			dynamicQuest.sort(function(a, b){return 0.5 - Math.random()});
		}
		document.getElementById("inputTextToSave").value = dynamicQuest;
		question = dynamicQuest[0];
		qs = 0;
		document.getElementById('question').innerHTML = question;
	}
	fileReader.readAsText(fileToLoad, "UTF-8");
	questionsLoaded = true;
	//log += '/n' + question;
}

function nextQuestion() {
		qs += 1;

		n = dynamicQuest.length;
		if (qs >= n-2) {
			dynamicQuest.sort(function(a, b){return 0.5 - Math.random()});
			qs = 0;
		}

		question = dynamicQuest[qs];
		document.getElementById('question').innerHTML = question;
		//log = log + '\n' + question;
	}

	function cycleNext() {
		/*
		if (questionsLoaded) {
			nextQuestion();
		}
		/*
		var i = currentClass.indexOf(up[0])
		xp[i] += Number(xpee);
		dynamicClass.push(up[0] + xp[i]);
		*/
		q++
		up.shift();

		up[3] = dynamicClass[q];

		n = dynamicClass.length;
		if (q >= n-2) {
			dynamicClass.sort(function(a, b){return 0.5 - Math.random()});
			q = 0;
		}
		document.getElementById('up').innerHTML = up[0];
		document.getElementById('next').innerHTML = up[1];
		document.getElementById('then1').innerHTML = up[2];
		document.getElementById('then2').value = up[3];
		//document.getElementById('q').innerHTML = q;

	}

	function cycleThen1() {
		/*
		if (questionsLoaded) {
			nextQuestion();
		}

		var i = currentClass.indexOf(up[0])
		xp[i] += Number(xpee);
		dynamicClass.push(up[0] + xp[i]);
		*/
		q++
		up[0] = up[2];
		up[2] = up[3];
		up[3] = dynamicClass[q];

		n = dynamicClass.length;
		if (q >= n-2) {
			dynamicClass.sort(function(a, b){return 0.5 - Math.random()});
			q = 0;
		}
		document.getElementById('up').innerHTML = up[0];
		document.getElementById('next').innerHTML = up[1];
		document.getElementById('then1').innerHTML = up[2];
		document.getElementById('then2').value = up[3];

		// debug script
		//document.getElementById('q').innerHTML = q;
		/*
		if (up[1] === undefined) {
			cycleNext();
		}
		*/
	}

	function cycleThen2() {
		/*
		if (questionsLoaded) {
			nextQuestion();
		}

		var i = currentClass.indexOf(up[0])
		xp[i] += Number(xpee);
		dynamicClass.push(up[0] + xp[i]);
		*/
		//q++
		//up[0] = up[1];
		//up[1] = up[2];
		//up[2] = up[3];
		dynamicClass.unshift(up[3];
		up[3] = document.getElementById('then2').value;

		log += '\n' + up[3] + ", manually added";
		document.getElementById("inputTextToSave").value = log;

		n = dynamicClass.length;
		if (q >= n-2) {
			dynamicClass.sort(function(a, b){return 0.5 - Math.random()});
			q = 0;
		}
		document.getElementById('up').innerHTML = up[0];
		document.getElementById('next').innerHTML = up[1];
		document.getElementById('then1').innerHTML = up[2];
		document.getElementById('then2').value = (up[3] + " get ready!");
		//document.getElementById('q').innerHTML = q;
		/*
		if (up[1] === undefined) {
			cycleNext();
		}
		*/
	}

	function checkUp(player) {
		return player == up[0];
	}

	function score(pt) {
		if (team1.some(checkUp)) {
			tp[1] += pt;
		} else if (team2.some(checkUp)) {
			tp[2] += pt;
		} else if (team3.some(checkUp)) {
			tp[3] += pt;
		} else if (team4.some(checkUp)) {
			tp[0] += pt;
		}
		/*
		document.getElementById('t1p').innerHTML = tp[1];
		document.getElementById('t2p').innerHTML = tp[2];
		document.getElementById('t3p').innerHTML = tp[3];
		document.getElementById('t4p').innerHTML = tp[0];
		*/
		log += '\n' + up[0] + "," + pt;
		document.getElementById("inputTextToSave").value = log;

		if (pt >= loadQwhenPt) {
			if (questionsLoaded) {
				nextQuestion();
			}
		}
		if (pt >= loadNwhenPt) {
			cycleNext();
		}

	}

function clickedUP() {
		pt = 1;
		if (team1.some(checkUp)) {
			tp[1] += pt;
		} else if (team2.some(checkUp)) {
			tp[2] += pt;
		} else if (team3.some(checkUp)) {
			tp[3] += pt;
		} else if (team4.some(checkUp)) {
			tp[0] += pt;
		}
		/*
		document.getElementById('t1p').innerHTML = tp[1];
		document.getElementById('t2p').innerHTML = tp[2];
		document.getElementById('t3p').innerHTML = tp[3];
		document.getElementById('t4p').innerHTML = tp[0];
		*/
		log += '\n' + up[0] + "," + pt;
		document.getElementById("inputTextToSave").value = log;
	}

	function gameOver() {
		highScore = Math.max(tp[0],tp[1],tp[2],tp[3]);
   		teamN = tp.indexOf(highScore);
		switch(teamN) {
			case 1:
				winners = team1;
				break;
			case 2:
				winners = team2;
				break;
			case 3:
				winners = team3;
				break;
			case 0:
				winners = team4;
				break;
		}
		congrats = "Congratulations " + winners.join(", ") + "!";
		document.getElementById('question').innerHTML = congrats;
		//log += '\n' + congrats;
	}

	function setModeQP(mode) {
		loadQwhenPt = mode;
	}
	function setModeNP(mode) {
		loadNwhenPt = mode;
	}
<commit_msg>Update loadStuff.js<commit_after>var d = Date();
var dv = d.valueOf();
var log = d;
var questionsLoaded = false;
var loadQwhenPt = 9;
var loadNwhenPt = 9;
function saveTextAsFile()
{
	var textToWrite = log;
	var textFileAsBlob = new Blob([textToWrite], {type:'text/plain'});
	var fileNameToSaveAs = "classtrisLog" + dv + ".csv";

	var downloadLink = document.createElement("a");
	downloadLink.download = fileNameToSaveAs;
	downloadLink.innerHTML = "Download File";
	if (window.webkitURL != null)
	{
		// Chrome allows the link to be clicked
		// without actually adding it to the DOM.
		downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
	}
	else
	{
		// Firefox requires the link to be added to the DOM
		// before it can be clicked.
		downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
		downloadLink.onclick = destroyClickedElement;
		downloadLink.style.display = "none";
		document.body.appendChild(downloadLink);
	}

	downloadLink.click();
}

function destroyClickedElement(event)
{
	document.body.removeChild(event.target);
}

function checkUndefined(stud) {
    return stud != undefined;
}

function checkBlank(stud) {
    return stud != "";
}

function loadClassList()
{
	var fileToLoad = document.getElementById("fileToLoad").files[0];

	var fileReader = new FileReader();
	fileReader.onload = function(fileLoadedEvent)
	{
		var textFromFileLoaded = fileLoadedEvent.target.result;
		currentClass = textFromFileLoaded .split("\n");

		currentClass = currentClass.filter(checkUndefined);
		currentClass = currentClass.filter(checkBlank);

		document.getElementById("inputTextToSave").value = textFromFileLoaded;
		dynamicClass = currentClass;
		dynamicClass.sort(function(a, b){return 0.5 - Math.random()});
		document.getElementById("inputTextToSave").value = dynamicClass;
		up = [];

		// teams
		team1 = [];
		team2 = [];
		team3 = [];
		team4 = [];

		// team points
		tp = [0,0,0,0];

		var teamN = 0;
		for (i = 0; i < dynamicClass.length; i++) {
			teamN = i%4;
			switch(i%4) {
				case 1:
					team1.push(dynamicClass[i]);
					break;
				case 2:
					team2.push(dynamicClass[i]);
					break;
				case 3:
					team3.push(dynamicClass[i]);
					break;
				case 0:
					team4.push(dynamicClass[i]);
					break;
			}
		}
		/*
		document.getElementById('team1').innerHTML = team1;
		document.getElementById('team2').innerHTML = team2;
		document.getElementById('team3').innerHTML = team3;
		document.getElementById('team4').innerHTML = team4;
		*/

		for (i = 0; i < 4; i++) {
			up[i] = dynamicClass[i];
		}
		q = 0;
		document.getElementById('up').innerHTML = up[0];
		document.getElementById('next').innerHTML = up[1];
		document.getElementById('then1').innerHTML = up[2];
		document.getElementById('then2').value = up[3];

		if (up[1] === undefined) {
			cycleNext();
		}

	}
	fileReader.readAsText(fileToLoad, "UTF-8");
	log += '\n' + Date();
}

function loadQuestions(mode)
{
	var fileToLoad = document.getElementById("questionLoad").files[0];

	var fileReader = new FileReader();
	fileReader.onload = function(fileLoadedEvent)
	{
		var textFromFileLoaded = fileLoadedEvent.target.result;
		currentQuest = textFromFileLoaded .split("\n");

		currentQuest = currentQuest.filter(checkUndefined);
		currentQuest = currentQuest.filter(checkBlank);

		document.getElementById("inputTextToSave").value = textFromFileLoaded;
		dynamicQuest = currentQuest;
		if (mode == "random") {
			dynamicQuest.sort(function(a, b){return 0.5 - Math.random()});
		}
		document.getElementById("inputTextToSave").value = dynamicQuest;
		question = dynamicQuest[0];
		qs = 0;
		document.getElementById('question').innerHTML = question;
	}
	fileReader.readAsText(fileToLoad, "UTF-8");
	questionsLoaded = true;
	//log += '/n' + question;
}

function nextQuestion() {
		qs += 1;

		n = dynamicQuest.length;
		if (qs >= n-2) {
			dynamicQuest.sort(function(a, b){return 0.5 - Math.random()});
			qs = 0;
		}

		question = dynamicQuest[qs];
		document.getElementById('question').innerHTML = question;
		//log = log + '\n' + question;
	}

	function cycleNext() {
		/*
		if (questionsLoaded) {
			nextQuestion();
		}
		/*
		var i = currentClass.indexOf(up[0])
		xp[i] += Number(xpee);
		dynamicClass.push(up[0] + xp[i]);
		*/
		q++
		up.shift();

		up[3] = dynamicClass[q];

		n = dynamicClass.length;
		if (q >= n-2) {
			dynamicClass.sort(function(a, b){return 0.5 - Math.random()});
			q = 0;
		}
		document.getElementById('up').innerHTML = up[0];
		document.getElementById('next').innerHTML = up[1];
		document.getElementById('then1').innerHTML = up[2];
		document.getElementById('then2').value = up[3];
		//document.getElementById('q').innerHTML = q;

	}

	function cycleThen1() {
		/*
		if (questionsLoaded) {
			nextQuestion();
		}

		var i = currentClass.indexOf(up[0])
		xp[i] += Number(xpee);
		dynamicClass.push(up[0] + xp[i]);
		*/
		q++
		up[0] = up[2];
		up[2] = up[3];
		up[3] = dynamicClass[q];

		n = dynamicClass.length;
		if (q >= n-2) {
			dynamicClass.sort(function(a, b){return 0.5 - Math.random()});
			q = 0;
		}
		document.getElementById('up').innerHTML = up[0];
		document.getElementById('next').innerHTML = up[1];
		document.getElementById('then1').innerHTML = up[2];
		document.getElementById('then2').value = up[3];

		// debug script
		//document.getElementById('q').innerHTML = q;
		/*
		if (up[1] === undefined) {
			cycleNext();
		}
		*/
	}

	function cycleThen2() {
		/*
		if (questionsLoaded) {
			nextQuestion();
		}

		var i = currentClass.indexOf(up[0])
		xp[i] += Number(xpee);
		dynamicClass.push(up[0] + xp[i]);
		*/
		//q++
		//up[0] = up[1];
		//up[1] = up[2];
		//up[2] = up[3];
		dynamicClass.unshift(up[3]);
		up[3] = document.getElementById('then2').value;

		log += '\n' + up[3] + ", manually added";
		document.getElementById("inputTextToSave").value = log;

		n = dynamicClass.length;
		if (q >= n-2) {
			dynamicClass.sort(function(a, b){return 0.5 - Math.random()});
			q = 0;
		}
		document.getElementById('up').innerHTML = up[0];
		document.getElementById('next').innerHTML = up[1];
		document.getElementById('then1').innerHTML = up[2];
		document.getElementById('then2').value = (up[3] + " get ready!");
		//document.getElementById('q').innerHTML = q;
		/*
		if (up[1] === undefined) {
			cycleNext();
		}
		*/
	}

	function checkUp(player) {
		return player == up[0];
	}

	function score(pt) {
		if (team1.some(checkUp)) {
			tp[1] += pt;
		} else if (team2.some(checkUp)) {
			tp[2] += pt;
		} else if (team3.some(checkUp)) {
			tp[3] += pt;
		} else if (team4.some(checkUp)) {
			tp[0] += pt;
		}
		/*
		document.getElementById('t1p').innerHTML = tp[1];
		document.getElementById('t2p').innerHTML = tp[2];
		document.getElementById('t3p').innerHTML = tp[3];
		document.getElementById('t4p').innerHTML = tp[0];
		*/
		log += '\n' + up[0] + "," + pt;
		document.getElementById("inputTextToSave").value = log;

		if (pt >= loadQwhenPt) {
			if (questionsLoaded) {
				nextQuestion();
			}
		}
		if (pt >= loadNwhenPt) {
			cycleNext();
		}

	}

function clickedUP() {
		pt = 1;
		if (team1.some(checkUp)) {
			tp[1] += pt;
		} else if (team2.some(checkUp)) {
			tp[2] += pt;
		} else if (team3.some(checkUp)) {
			tp[3] += pt;
		} else if (team4.some(checkUp)) {
			tp[0] += pt;
		}
		/*
		document.getElementById('t1p').innerHTML = tp[1];
		document.getElementById('t2p').innerHTML = tp[2];
		document.getElementById('t3p').innerHTML = tp[3];
		document.getElementById('t4p').innerHTML = tp[0];
		*/
		log += '\n' + up[0] + "," + pt;
		document.getElementById("inputTextToSave").value = log;
	}

	function gameOver() {
		highScore = Math.max(tp[0],tp[1],tp[2],tp[3]);
   		teamN = tp.indexOf(highScore);
		switch(teamN) {
			case 1:
				winners = team1;
				break;
			case 2:
				winners = team2;
				break;
			case 3:
				winners = team3;
				break;
			case 0:
				winners = team4;
				break;
		}
		congrats = "Congratulations " + winners.join(", ") + "!";
		document.getElementById('question').innerHTML = congrats;
		//log += '\n' + congrats;
	}

	function setModeQP(mode) {
		loadQwhenPt = mode;
	}
	function setModeNP(mode) {
		loadNwhenPt = mode;
	}
<|endoftext|>


<commit_before>(function () {
  "use strict";

  let RegExpTokenizer = function() { };
  let prototype = RegExpTokenizer.prototype;

  RegExpTokenizer.ESCAPE_CHAR_TYPES = [
    "ControlEscape",
    "ControlLetterEscape",
    "HexEscapeSequence",
    "UnicodeEscapeSequence",
    "IdentityEscape",
    "CharacterClassEscape"
  ];

  RegExpTokenizer.ESCAPE_CHARS = {
    "f": 0,
    "n": 0,
    "r": 0,
    "t": 0,
    "v": 0,
    "c": 1,
    "x": 2,
    "u": 3,
    "d": 5,
    "D": 5,
    "s": 5,
    "S": 5,
    "w": 5,
    "W": 5
  };

  RegExpTokenizer.ESCACPE_CHAR_CODES = {
    "0" : 0,
    "t" : 9,
    "n" : 10,
    "v" : 11,
    "f" : 12,
    "r" : 13
  };

  prototype.token = null;
  prototype.capturingGroups = [];

  prototype.tokenize = function(str) {
    function token(type, start, end, additionalProps) {
      this.type = type;
      if (additionalProps) {
        let self = this;
        Object.getOwnPropertyNames(additionalProps).forEach(function mapPropNames(name) {
          self[name] = additionalProps[name];
        });
      }
      this.loc = {
        start: start,
        end: end || start + 1
      }
    }

    this.token = null;
    this.capturingGroups = [];

    let length = str.length;
    let i = 0;
    let ast = new token("RegularExpression", i, length, {body: []});
    let parentToken = ast;
    let currentToken = ast;
    let stack = [];
    let charClass = false;
    while (i < length) {
      let char = str.charAt(i);
      let addToParent = true;
      let newParentToken = null;

      if (char === "(" && !charClass) {
        currentToken = new token("", i, null, {error: "noClosingBracket", body: []});
        this.parseGroup(str, currentToken);

        if (currentToken.type === "CapturingGroup") {
          this.capturingGroups.push(currentToken);
        }

        stack.push(parentToken);
        newParentToken = currentToken;
      } else if (char === "[") {
        let negated = (str[i + 1] === "^");
        charClass = true;
        currentToken = new token("CharacterClass", i, i + (negated ? 2 : 1),
            {negated: negated, error: "noClosingBracket", body: []});
        stack.push(parentToken);

        newParentToken = currentToken;
      } else if (char === "-" && charClass) {
        currentToken = new token("Range", currentToken.loc.start, i + 1,
            {left: currentToken, right: null});
        let parent = parentToken.hasOwnProperty("right") ? "right" : "body";
        parentToken[parent].pop();
        stack.push(parentToken);
        newParentToken = currentToken;
      } else if ((char === ")" && !charClass) || (char === "]" && charClass)) {
        if (char === "]" && charClass) {
          charClass = false;
        }

        if (stack.length > 0) {
          while (parentToken.type === "Alternation") {
            parentToken.loc.end = parentToken.right.length > 0 ?
                parentToken.right[parentToken.right.length - 1].loc.end :
                parentToken.loc.start + 1;
            parentToken = stack.pop();
          }

          let stackToken = stack.pop();
          currentToken = parentToken;
          if (currentToken.error === "noClosingBracket")
            delete currentToken.error;
          currentToken.loc.end = i + 1;
          newParentToken = stackToken;
          addToParent = false;
        } else {
          currentToken = new token("Literal", i, null, {value: char,
              error: "additionalClosingBracket"});
        }
      } else if (char === "\\") {
        currentToken = new token("", i);
        this.parseEscapeSequence(str, currentToken);
      } else if (char === "." && !charClass) {
        currentToken = new token("AnyCharacter", i);
      } else if ("?*+{".contains(char) && !charClass) {
        let quantifierToken = new token("", i);
        this.parseQuantifier(str, quantifierToken);
        if (quantifierToken.type === "") {
          currentToken.value += char;
          currentToken.loc.end++;
          addToParent = false;
        } else {
          currentToken = quantifierToken;
        }
      } else if ((char === "^" || char === "$") && !charClass) {
        currentToken = new token((char === "^" ? "Start" : "End") + "Anchor", i);
      } else if (char === "|" && !charClass) {
        let parent = parentToken.hasOwnProperty("right") ? "right" : "body";
        let startLoc = parentToken[parent][0] ? parentToken[parent][0].loc.start : i;
        currentToken = new token("Alternation", startLoc, i + 1,
            {left: parentToken[parent], right: []});
        parentToken[parent] = [];
        stack.push(parentToken);
        newParentToken = currentToken;
      } else {
        if (char === "/" && !charClass) {
          currentToken = new token("Literal", i, null, {value: char, error: "unescapedSlash"});
        } else if (currentToken && currentToken.type === "Literal" && !charClass && !currentToken.error) {
          currentToken.value += char;
          currentToken.loc.end++;
          addToParent = false;
        } else {
          currentToken = new token("Literal", i, null, {value: char});
        }
      }

      if (parentToken.type === "Range" && parentToken !== currentToken) {
        parentToken.loc.end = currentToken.loc.end;
        newParentToken = stack.pop();
      }

      if (addToParent) {
        let parent = parentToken.hasOwnProperty("right") ? "right" : "body";
        if (Array.isArray(parentToken[parent])) {
          parentToken[parent].push(currentToken);
        } else {
          parentToken[parent] = currentToken;
        }
      }

      if (parentToken.type === "Range" && parentToken.right &&
          parentToken.left.value.charCodeAt(0) > parentToken.right.value.charCodeAt(0)) {
        parentToken.error = "invalidRange";
      }

      if (newParentToken) {
        parentToken = newParentToken;
      }

      i = currentToken.loc.end;
    }

    if (stack.length > 0) {
      parentToken.loc.end = currentToken.loc.end;

      while (stack.length > 0) {
        currentToken = parentToken;
        parentToken = stack.pop();
        parentToken.loc.end = currentToken.loc.end;
      }
    }

    this.token = ast;

    return ast;
  };

  prototype.parseGroup = function(str, token) {
    let match = str.substr(token.loc.start + 1, 3).match(/\?(?::|<?[!=])/);
    if (match) {
      if (match[0].contains(":")) {
        token.type = "NonCapturingGroup";
      } else {
        token.type = match[0].contains("=") ? "Positive" : "Negative";
        token.type += "Look" + (match[0].contains("<") ? "Behind" : "Ahead");

        if (match[0].contains("<"))
          token.error = "lookbehind";
      }

      token.loc.end += match[0].length;
    } else {
      token.type = "CapturingGroup";
    }
  };

  prototype.parseQuantifier = function(str, token) {
    switch (str[token.loc.start]) {
      case "?":
        token.type = "OptionalQuantifier";
        break;

      case "*":
        token.type = "ZeroOrMoreQuantifier";
        break;

      case "+":
        token.type = "OneOrMoreQuantifier";
        break;

      case "{":
        let repetitions = str.substr(token.loc.start).match(/^\{(\d*)(?:,(\d*))?\}/);

        // If no repetition is found, the following characters are parsed normally without error
        if (!repetitions || (!repetitions[1] && !repetitions[2])) {
          return;
        }

        token.type = "RepetitionQuantifier";
        token.loc.end += repetitions[0].length - 1;

        if (repetitions[2] !== undefined) {
          token.type = "Variable" + token.type;
          token.min = repetitions[1] !== "" ? Number(repetitions[1]) : null;
          token.max = repetitions[2] !== "" ? Number(repetitions[2]) : null;

          if (token.min > token.max && token.max !== null) {
            token.error = "invalidRepetitionQuantifier";
          }
        } else {
          token.type = "Fixed" + token.type;
          token.repetitions = Number(repetitions[1]);
        }
    }

    if (str[token.loc.end] === "?") {
      token.lazy = true;
      token.loc.end++;
    } else {
      token.lazy = false;
    }
  };

  prototype.parseEscapeSequence = function(str, token) {
    let escapeCharType = RegExpTokenizer.ESCAPE_CHARS[str[token.loc.end]];
    if (escapeCharType === undefined)
      escapeCharType = 4;
    token.type = RegExpTokenizer.ESCAPE_CHAR_TYPES[escapeCharType];
    switch (token.type) {
      case "ControlEscape":
      case "IdentityEscape":
      case "CharacterClassEscape":
        token.char = str[token.loc.end];
        if (token.type === "ControlEscape") {
          token.value = RegExpTokenizer.ESCACPE_CHAR_CODES[token.char];
        }

        // Catch backslashes standing at the end of the regexp
        if (token.loc.end === str.length) {
          token.error = "danglingBackslash";
        } else {
          token.loc.end++;
        }
        break;

      case "ControlLetterEscape":
        token.char = str[token.loc.end + 1];
        token.value = token.char.charCodeAt(0) - 64;
        token.loc.end += 2;
        break;

      case "HexEscapeSequence":
        token.sequence = str.substr(token.loc.end + 1, 2);
        token.loc.end += 3;
        break;

      case "UnicodeEscapeSequence":
        token.sequence = str.substr(token.loc.end + 1, 4);
        token.loc.end += 5;
        break;
    }

    return;
  };

  window.RegExpTokenizer = RegExpTokenizer;
})();<commit_msg>Renamed invalid ranges to clarify their difference<commit_after>(function () {
  "use strict";

  let RegExpTokenizer = function() { };
  let prototype = RegExpTokenizer.prototype;

  RegExpTokenizer.ESCAPE_CHAR_TYPES = [
    "ControlEscape",
    "ControlLetterEscape",
    "HexEscapeSequence",
    "UnicodeEscapeSequence",
    "IdentityEscape",
    "CharacterClassEscape"
  ];

  RegExpTokenizer.ESCAPE_CHARS = {
    "f": 0,
    "n": 0,
    "r": 0,
    "t": 0,
    "v": 0,
    "c": 1,
    "x": 2,
    "u": 3,
    "d": 5,
    "D": 5,
    "s": 5,
    "S": 5,
    "w": 5,
    "W": 5
  };

  RegExpTokenizer.ESCACPE_CHAR_CODES = {
    "0" : 0,
    "t" : 9,
    "n" : 10,
    "v" : 11,
    "f" : 12,
    "r" : 13
  };

  prototype.token = null;
  prototype.capturingGroups = [];

  prototype.tokenize = function(str) {
    function token(type, start, end, additionalProps) {
      this.type = type;
      if (additionalProps) {
        let self = this;
        Object.getOwnPropertyNames(additionalProps).forEach(function mapPropNames(name) {
          self[name] = additionalProps[name];
        });
      }
      this.loc = {
        start: start,
        end: end || start + 1
      }
    }

    this.token = null;
    this.capturingGroups = [];

    let length = str.length;
    let i = 0;
    let ast = new token("RegularExpression", i, length, {body: []});
    let parentToken = ast;
    let currentToken = ast;
    let stack = [];
    let charClass = false;
    while (i < length) {
      let char = str.charAt(i);
      let addToParent = true;
      let newParentToken = null;

      if (char === "(" && !charClass) {
        currentToken = new token("", i, null, {error: "noClosingBracket", body: []});
        this.parseGroup(str, currentToken);

        if (currentToken.type === "CapturingGroup") {
          this.capturingGroups.push(currentToken);
        }

        stack.push(parentToken);
        newParentToken = currentToken;
      } else if (char === "[") {
        let negated = (str[i + 1] === "^");
        charClass = true;
        currentToken = new token("CharacterClass", i, i + (negated ? 2 : 1),
            {negated: negated, error: "noClosingBracket", body: []});
        stack.push(parentToken);

        newParentToken = currentToken;
      } else if (char === "-" && charClass) {
        currentToken = new token("Range", currentToken.loc.start, i + 1,
            {left: currentToken, right: null});
        let parent = parentToken.hasOwnProperty("right") ? "right" : "body";
        parentToken[parent].pop();
        stack.push(parentToken);
        newParentToken = currentToken;
      } else if ((char === ")" && !charClass) || (char === "]" && charClass)) {
        if (char === "]" && charClass) {
          charClass = false;
        }

        if (stack.length > 0) {
          while (parentToken.type === "Alternation") {
            parentToken.loc.end = parentToken.right.length > 0 ?
                parentToken.right[parentToken.right.length - 1].loc.end :
                parentToken.loc.start + 1;
            parentToken = stack.pop();
          }

          let stackToken = stack.pop();
          currentToken = parentToken;
          if (currentToken.error === "noClosingBracket")
            delete currentToken.error;
          currentToken.loc.end = i + 1;
          newParentToken = stackToken;
          addToParent = false;
        } else {
          currentToken = new token("Literal", i, null, {value: char,
              error: "additionalClosingBracket"});
        }
      } else if (char === "\\") {
        currentToken = new token("", i);
        this.parseEscapeSequence(str, currentToken);
      } else if (char === "." && !charClass) {
        currentToken = new token("AnyCharacter", i);
      } else if ("?*+{".contains(char) && !charClass) {
        let quantifierToken = new token("", i);
        this.parseQuantifier(str, quantifierToken);
        if (quantifierToken.type === "") {
          currentToken.value += char;
          currentToken.loc.end++;
          addToParent = false;
        } else {
          currentToken = quantifierToken;
        }
      } else if ((char === "^" || char === "$") && !charClass) {
        currentToken = new token((char === "^" ? "Start" : "End") + "Anchor", i);
      } else if (char === "|" && !charClass) {
        let parent = parentToken.hasOwnProperty("right") ? "right" : "body";
        let startLoc = parentToken[parent][0] ? parentToken[parent][0].loc.start : i;
        currentToken = new token("Alternation", startLoc, i + 1,
            {left: parentToken[parent], right: []});
        parentToken[parent] = [];
        stack.push(parentToken);
        newParentToken = currentToken;
      } else {
        if (char === "/" && !charClass) {
          currentToken = new token("Literal", i, null, {value: char, error: "unescapedSlash"});
        } else if (currentToken && currentToken.type === "Literal" && !charClass && !currentToken.error) {
          currentToken.value += char;
          currentToken.loc.end++;
          addToParent = false;
        } else {
          currentToken = new token("Literal", i, null, {value: char});
        }
      }

      if (parentToken.type === "Range" && parentToken !== currentToken) {
        parentToken.loc.end = currentToken.loc.end;
        newParentToken = stack.pop();
      }

      if (addToParent) {
        let parent = parentToken.hasOwnProperty("right") ? "right" : "body";
        if (Array.isArray(parentToken[parent])) {
          parentToken[parent].push(currentToken);
        } else {
          parentToken[parent] = currentToken;
        }
      }

      if (parentToken.type === "Range" && parentToken.right &&
          parentToken.left.value.charCodeAt(0) > parentToken.right.value.charCodeAt(0)) {
        parentToken.error = "invalidCharRange";
      }

      if (newParentToken) {
        parentToken = newParentToken;
      }

      i = currentToken.loc.end;
    }

    if (stack.length > 0) {
      parentToken.loc.end = currentToken.loc.end;

      while (stack.length > 0) {
        currentToken = parentToken;
        parentToken = stack.pop();
        parentToken.loc.end = currentToken.loc.end;
      }
    }

    this.token = ast;

    return ast;
  };

  prototype.parseGroup = function(str, token) {
    let match = str.substr(token.loc.start + 1, 3).match(/\?(?::|<?[!=])/);
    if (match) {
      if (match[0].contains(":")) {
        token.type = "NonCapturingGroup";
      } else {
        token.type = match[0].contains("=") ? "Positive" : "Negative";
        token.type += "Look" + (match[0].contains("<") ? "Behind" : "Ahead");

        if (match[0].contains("<"))
          token.error = "lookbehind";
      }

      token.loc.end += match[0].length;
    } else {
      token.type = "CapturingGroup";
    }
  };

  prototype.parseQuantifier = function(str, token) {
    switch (str[token.loc.start]) {
      case "?":
        token.type = "OptionalQuantifier";
        break;

      case "*":
        token.type = "ZeroOrMoreQuantifier";
        break;

      case "+":
        token.type = "OneOrMoreQuantifier";
        break;

      case "{":
        let repetitions = str.substr(token.loc.start).match(/^\{(\d*)(?:,(\d*))?\}/);

        // If no repetition is found, the following characters are parsed normally without error
        if (!repetitions || (!repetitions[1] && !repetitions[2])) {
          return;
        }

        token.type = "RepetitionQuantifier";
        token.loc.end += repetitions[0].length - 1;

        if (repetitions[2] !== undefined) {
          token.type = "Variable" + token.type;
          token.min = repetitions[1] !== "" ? Number(repetitions[1]) : null;
          token.max = repetitions[2] !== "" ? Number(repetitions[2]) : null;

          if (token.min > token.max && token.max !== null) {
            token.error = "invalidRepetitionRange";
          }
        } else {
          token.type = "Fixed" + token.type;
          token.repetitions = Number(repetitions[1]);
        }
    }

    if (str[token.loc.end] === "?") {
      token.lazy = true;
      token.loc.end++;
    } else {
      token.lazy = false;
    }
  };

  prototype.parseEscapeSequence = function(str, token) {
    let escapeCharType = RegExpTokenizer.ESCAPE_CHARS[str[token.loc.end]];
    if (escapeCharType === undefined)
      escapeCharType = 4;
    token.type = RegExpTokenizer.ESCAPE_CHAR_TYPES[escapeCharType];
    switch (token.type) {
      case "ControlEscape":
      case "IdentityEscape":
      case "CharacterClassEscape":
        token.char = str[token.loc.end];
        if (token.type === "ControlEscape") {
          token.value = RegExpTokenizer.ESCACPE_CHAR_CODES[token.char];
        }

        // Catch backslashes standing at the end of the regexp
        if (token.loc.end === str.length) {
          token.error = "danglingBackslash";
        } else {
          token.loc.end++;
        }
        break;

      case "ControlLetterEscape":
        token.char = str[token.loc.end + 1];
        token.value = token.char.charCodeAt(0) - 64;
        token.loc.end += 2;
        break;

      case "HexEscapeSequence":
        token.sequence = str.substr(token.loc.end + 1, 2);
        token.loc.end += 3;
        break;

      case "UnicodeEscapeSequence":
        token.sequence = str.substr(token.loc.end + 1, 4);
        token.loc.end += 5;
        break;
    }

    return;
  };

  window.RegExpTokenizer = RegExpTokenizer;
})();<|endoftext|>


<commit_before>$(document).ready(function() {

    var currUser;

    function initialize() {
        // currUser.get("name") := First and last name
        // currUser.get("picture") := String url of profile picture
    }

    Parse.initialize("VXRx5pZQAr263FPLmgqY2FHEa66zEOLIuK3I2rl6", "OQkMhfc7hMHcBBkiUoClnxAfrF8gpmKaC3jNKq5V");

    window.fbAsyncInit = function() {
        Parse.FacebookUtils.init({
          appId      : '789662264445561',
          xfbml      : true,
          version    : 'v2.3'
        });

        Parse.FacebookUtils.logIn("user_likes,email", {
            success: function(user) {
                currUser = user;

                FB.api("/me", function(response) {
                    user.set("name", response.name);
                    user.save();
                });

                FB.api("/me/picture?type=large", function(response) {
                    user.set("picture", response.data.url);
                    user.save();
                });

                FB.api("/me/friends", function(response) {
                    console.log("oh");
                    console.log(response);
                    user.save();
                });

                initialize();
            },
            error: function(user, error) {
                alert("You must sign into Facebook to use this app");
            }
        });
    };

    (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "http://connect.facebook.net/en_US/sdk.js";
     fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));

});<commit_msg>Testing friend lists<commit_after>$(document).ready(function() {

    var currUser;

    function initialize() {
        // currUser.get("name") := First and last name
        // currUser.get("picture") := String url of profile picture
    }

    Parse.initialize("VXRx5pZQAr263FPLmgqY2FHEa66zEOLIuK3I2rl6", "OQkMhfc7hMHcBBkiUoClnxAfrF8gpmKaC3jNKq5V");

    window.fbAsyncInit = function() {
        Parse.FacebookUtils.init({
          appId      : '789662264445561',
          xfbml      : true,
          version    : 'v2.3'
        });

        Parse.FacebookUtils.logIn("user_friends", {
            success: function(user) {
                currUser = user;

                FB.api("/me", function(response) {
                    user.set("name", response.name);
                    user.save();
                });

                FB.api("/me/picture?type=large", function(response) {
                    user.set("picture", response.data.url);
                    user.save();
                });

                FB.api("/me/friends", function(response) {
                    console.log("oh");
                    console.log(response);
                    user.save();
                });

                initialize();
            },
            error: function(user, error) {
                alert("You must sign into Facebook to use this app");
            }
        });
    };

    (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "http://connect.facebook.net/en_US/sdk.js";
     fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));

});<|endoftext|>


<commit_before>/*global setTimeout:true, clearTimeout:true */

/**
Shifty - A teeny tiny tweening engine in JavaScript.
By Jeremy Kahn - jeremyckahn@gmail.com
  v0.1.0

For instructions on how to use Shifty, please consult the README: https://github.com/jeremyckahn/tweeny/blob/master/README.md

MIT Lincense.  This code free to use, modify, distribute and enjoy.

*/

(function Shifty (global) {

	/**
	 * Get the current UNIX time as an integer
	 * @returns {Number} An integer representing the current timestamp.
	 */
	function now () {
		return +new Date();
	}

	function each (obj, func) {
		var prop;

		for (prop in obj) {
			if (obj.hasOwnProperty(prop)) {
				func(obj, prop);
			}
		}
	}

	/**
	 * Does a basic copy of one Object's properties to another.  This is not a robust `extend` function, nor is it recusrsive.  It is only appropriate to use on objects that have primitive properties (Numbers, Strings, Boolean, etc.)
	 * @param {Object} targetObject The object to copy into
	 * @param {Object} srcObject The object to copy from
	 * @returns {Object} A reference to the augmented `targetObj` Object
	 */
	function simpleCopy (targetObj, srcObj) {
		each(srcObj, function (srcObj, prop) {
			targetObj[prop] = srcObj[prop];
		});

		return targetObj;
	}

	// Modifies the `from` object
	function tweenProps (currentTime, params, state) {
		var prop;

		for (prop in state.current) {
			if (state.current.hasOwnProperty(prop) && params.to.hasOwnProperty(prop)) {
				state.current[prop] = params.easingFunc(currentTime - params.timestamp, params.originalState[prop], params.to[prop] - params.originalState[prop], params.duration);
			}
		}
	}

	function scheduleUpdate (handler, fps) {
		return setTimeout(handler, 1000 / fps);
	}

	function invokeHook (hookName, hooks, applyTo, args) {
		var i;

		for (i = 0; i < hooks[hookName].length; i++) {
			hooks[hookName][i].apply(applyTo, args);
		}
	}

	function applyFilter (filterName, applyTo, args) {
		each(global.Tweenable.prototype.filter, function (filters, name) {
			if (filters[name][filterName]) {
				filters[name][filterName].apply(applyTo, args);
			}
		});
	}

	function timeoutHandler (params, state) {
		var currentTime;

		currentTime = now();

		if (currentTime < params.timestamp + params.duration && state.isAnimating) {
			applyFilter('beforeTween', params.owner, [state.current, params.originalState, params.to]);
			tweenProps (currentTime, params, state);
			applyFilter('afterTween', params.owner, [state.current, params.originalState, params.to]);

			if (params.hook.step) {
				invokeHook('step', params.hook, params.owner, [state.current]);
			}

			params.step.call(state.current);

			// The tween is still running, schedule an update
			state.loopId = scheduleUpdate(function () {
				timeoutHandler(params, state);
			}, params.fps);
		} else {
			// The duration of the tween has expired
			params.tweenController.stop(true);
		}
	}

	function Tween (params, state) {
		/**
		 * Stops the tween.
		 * @param {Boolean} gotoEnd If `false`, or omitted, the tween just stops at its current state, and the `callback` is not invoked.  If `true`, the tweened object's values are instantly set the the target values, and the `callbabk` is invoked.
		 */
		this.stop = function stop (gotoEnd) {
			clearTimeout(state.loopId);
			if (gotoEnd) {
				simpleCopy(state.current, params.to);
				state.isAnimating = false;
				params.callback.call(state.current);
			}
		};

		// This needs an example file!
		this.pause = function pause () {
			clearTimeout(state.loopId);
			state.pausedAtTime = now();
			state.isPaused = true;
			return this;
		};

		this.resume = function play () {
			if (state.isPaused) {
				params.timestamp += state.pausedAtTime - params.timestamp;
			}

			scheduleUpdate(function () {
				timeoutHandler(params, state);
			});

			return this;
		};

		/**
		 * Returns a reference to the tweened object (the `from` object that wat passed to `tweeny.tween`).
		 * @returns {Object}
		 */
		this.get = function get () {
			return state.current;
		};

		return this;
	}

	function Tweenable () {

		this.init = function init (options) {
			options = options || {};

			this._hook = {};

			this._tweenParams = {
				owner: this,
				hook: this._hook
			};

			this._state = {};

			// The framerate at which Tweeny updates.
			this.fps = options.fps || 30;

			// The default easing formula.  This can be changed publicly.
			this.easing = options.easing || 'linear';

			// The default `duration`.  This can be changed publicly.
			this.duration = options.duration || 500;

			return this;
		};

		/**
		 * @param {Object} from
		 * @param {Object} to
		 * @param {Number} duration
		 * @param {String} easing
		 */
		this.tween = function tween (from, to, duration, callback, easing) {
			var self;

			if (this._state.isAnimating) {
				return;
			}

			self = this;
			this._state.loopId = 0;
			this._state.current = {};
			this._state.pausedAtTime = null;

			// Normalize some internal values depending on how `Tweenable.tween` was invoked
			if (to) {
				// Assume the shorthand syntax is being used.
				this._tweenParams.step = function () {};
				this._state.current = from || {};
				this._tweenParams.to = to || {};
				this._tweenParams.duration = duration || this.duration;
				this._tweenParams.callback = callback || function () {};
				this._tweenParams.easing = easing || this.easing;
			} else {
				// If the second argument is not present, assume the longhand syntax is being used.
				this._tweenParams.step = from.step || function () {};
				this._tweenParams.callback = from.callback || function () {};
				this._state.current = from.from || {};
				this._tweenParams.to = from.to || {};
				this._tweenParams.duration = from.duration || this.duration;
				this._tweenParams.easing = from.easing || this.easing;
			}

			this._tweenParams.timestamp = now();
			this._tweenParams.easingFunc = this.formula[this._tweenParams.easing] || this.formula.linear;
			this._tweenParams.originalState = simpleCopy({}, this._state.current);
			applyFilter('tweenCreated', this._tweenParams.owner, [this._state.current, this._tweenParams.originalState, this._tweenParams.to]);
			this._tweenParams.tweenController = new Tween(this._tweenParams, this._state);
			this._state.isAnimating = true;

			scheduleUpdate(function () {
				timeoutHandler(self._tweenParams, self._state);
			}, this.fps);

			return this._tweenParams.tweenController;
		};

		this.hookAdd = function hookAdd (hookName, hookFunc) {
			if (!this._hook.hasOwnProperty(hookName)) {
				this._hook[hookName] = [];
			}

			this._hook[hookName].push(hookFunc);
		};

		this.hookRemove = function hookRemove (hookName, hookFunc) {
			var i;

			if (!this._hook.hasOwnProperty(hookName)) {
				return;
			}

			if (!hookFunc) {
				this._hook[hookName] = [];
				return;
			}

			for (i = this._hook[hookName].length; i >= 0; i++) {
				if (this._hook[hookName][i] === hookFunc) {
					this._hook[hookName].splice(i, 1);
				}
			}
		};

		return this;
	}

	Tweenable.prototype.filter = {};
	Tweenable.util = {
		'each': each
	};

	/**
	 * This object contains all of the tweens available to Tweeny.  It is extendable - simply attach properties to the Tweenable.prototype.formula Object following the same format at `linear`.
	 *
	 * This pattern was copied from Robert Penner, under BSD License (http://www.robertpenner.com/)
	 *
	 * @param t The current time
	 * @param b Start value
	 * @param c Change in value (delta)
	 * @param d Duration of the tween
	 */
	Tweenable.prototype.formula = {
		linear: function (t, b, c, d) {
			// no easing, no acceleration
			return c * t / d + b;
		}
	};

	global.Tweenable = Tweenable;

}(this));<commit_msg>Some src documentation for the core.<commit_after>/*global setTimeout:true, clearTimeout:true */

/**
Shifty - A teeny tiny tweening engine in JavaScript.
By Jeremy Kahn - jeremyckahn@gmail.com
  v0.1.0

For instructions on how to use Shifty, please consult the README: https://github.com/jeremyckahn/tweeny/blob/master/README.md

MIT Lincense.  This code free to use, modify, distribute and enjoy.

*/

(function Shifty (global) {

	/**
	 * Get the current UNIX epoch time as an integer.  Exposed publicly as `Tweenable.util.now()`.
	 * @returns {Number} An integer representing the current timestamp.
	 */
	function now () {
		return +new Date();
	}

	/**
	 * Handy shortcut for doing a for-in loop.  Takes care of all of the `hasOwnProperty` wizardry for you.  This also exposed publicly, external code can access it as `Tweenable.util.each()`.
	 * @param {Object} obj The object to iterate through.
	 * @param {Function} func The function to pass the object and "own" property to.  This handler function receives the `obj` back as the first parameter, and a property name as the second.
	 */
	function each (obj, func) {
		var prop;

		for (prop in obj) {
			if (obj.hasOwnProperty(prop)) {
				func(obj, prop);
			}
		}
	}

	/**
	 * Does a basic copy of one Object's properties to another.  This is not a robust `extend` function, nor is it recusrsive.  It is only appropriate to use on objects that have primitive properties (Numbers, Strings, Boolean, etc.).  Exposed publicly as `Tweenable.util.simpleCopy()`
	 * @param {Object} targetObject The object to copy into
	 * @param {Object} srcObject The object to copy from
	 * @returns {Object} A reference to the augmented `targetObj` Object
	 */
	function simpleCopy (targetObj, srcObj) {
		each(srcObj, function (srcObj, prop) {
			targetObj[prop] = srcObj[prop];
		});

		return targetObj;
	}

	/**
	 * Calculates the interpolated tween values of an Object based on the current time.
	 * @param {Number} currentTime The current time to evaluate the tween against.
	 * @param {Object} params A configuration Object containing the values that this function requires.  The required properties in this Object are:
	 *   @property {Object} originalState The original properties the Object is tweening from.
	 *   @property {Object} to The destination properties the Object is tweening to.
	 *   @property {Number} duration The length of the tween in milliseconds.
	 *   @property {Number} timestamp The UNIX epoch time at which the tween began.
	 *   @property {Function} easingFunc The function used to calculate the tween.  See the documentation for `Tweenable.prototype.formula` for more info on the appropriate function signature for this.
	 * @param {Object} state A configuration object containing current state data of the tween.  Required properties:
	 *   @property {Object} current The Object containing the current `Number` values of the tween.
	 */
	function tweenProps (currentTime, params, state) {
		var prop;

		for (prop in state.current) {
			if (state.current.hasOwnProperty(prop) && params.to.hasOwnProperty(prop)) {
				state.current[prop] = params.easingFunc(currentTime - params.timestamp, params.originalState[prop], params.to[prop] - params.originalState[prop], params.duration);
			}
		}
	}

	function scheduleUpdate (handler, fps) {
		return setTimeout(handler, 1000 / fps);
	}

	/**
	 * Calls all of the functions bound to a specified hook on a `Tweenable` instance.
	 * @param {String} hookName The name of the hook to invoke the handlers of.
	 * @param {Object} hooks The object containing the hook Arrays.
	 * @param {Object} applyTo The `Tweenable` instance to call the hooks upon.
	 * @param {Array} args The arguments to pass to each function in the specified hook.
	 */
	function invokeHook (hookName, hooks, applyTo, args) {
		var i;

		for (i = 0; i < hooks[hookName].length; i++) {
			hooks[hookName][i].apply(applyTo, args);
		}
	}

	/**
	 * Applies a Shifty filter to `Tweenable` instance.
	 * @param {String} filterName The name of the filter to apply.
	 * @param {Object} applyTo The `Tweenable` instance to call the filter upon.
	 * @param {Array} args The arguments to pass to the function in the specified filter.
	 */
	function applyFilter (filterName, applyTo, args) {
		each(global.Tweenable.prototype.filter, function (filters, name) {
			if (filters[name][filterName]) {
				filters[name][filterName].apply(applyTo, args);
			}
		});
	}

	function timeoutHandler (params, state) {
		var currentTime;

		currentTime = now();

		if (currentTime < params.timestamp + params.duration && state.isAnimating) {
			applyFilter('beforeTween', params.owner, [state.current, params.originalState, params.to]);
			tweenProps (currentTime, params, state);
			applyFilter('afterTween', params.owner, [state.current, params.originalState, params.to]);

			if (params.hook.step) {
				invokeHook('step', params.hook, params.owner, [state.current]);
			}

			params.step.call(state.current);

			// The tween is still running, schedule an update
			state.loopId = scheduleUpdate(function () {
				timeoutHandler(params, state);
			}, params.fps);
		} else {
			// The duration of the tween has expired
			params.tweenController.stop(true);
		}
	}

	function Tween (params, state) {
		/**
		 * Stops the tween.
		 * @param {Boolean} gotoEnd If `false`, or omitted, the tween just stops at its current state, and the `callback` is not invoked.  If `true`, the tweened object's values are instantly set the the target values, and the `callbabk` is invoked.
		 */
		this.stop = function stop (gotoEnd) {
			clearTimeout(state.loopId);
			if (gotoEnd) {
				simpleCopy(state.current, params.to);
				state.isAnimating = false;
				params.callback.call(state.current);
			}
		};

		// This needs an example file!
		this.pause = function pause () {
			clearTimeout(state.loopId);
			state.pausedAtTime = now();
			state.isPaused = true;
			return this;
		};

		this.resume = function play () {
			if (state.isPaused) {
				params.timestamp += state.pausedAtTime - params.timestamp;
			}

			scheduleUpdate(function () {
				timeoutHandler(params, state);
			});

			return this;
		};

		/**
		 * Returns a reference to the tweened object (the `from` object that wat passed to `tweeny.tween`).
		 * @returns {Object}
		 */
		this.get = function get () {
			return state.current;
		};

		return this;
	}

	function Tweenable () {

		this.init = function init (options) {
			options = options || {};

			this._hook = {};

			this._tweenParams = {
				owner: this,
				hook: this._hook
			};

			this._state = {};

			// The framerate at which Tweeny updates.
			this.fps = options.fps || 30;

			// The default easing formula.  This can be changed publicly.
			this.easing = options.easing || 'linear';

			// The default `duration`.  This can be changed publicly.
			this.duration = options.duration || 500;

			return this;
		};

		/**
		 * @param {Object} from
		 * @param {Object} to
		 * @param {Number} duration
		 * @param {String} easing
		 */
		this.tween = function tween (from, to, duration, callback, easing) {
			var self;

			if (this._state.isAnimating) {
				return;
			}

			self = this;
			this._state.loopId = 0;
			this._state.current = {};
			this._state.pausedAtTime = null;

			// Normalize some internal values depending on how `Tweenable.tween` was invoked
			if (to) {
				// Assume the shorthand syntax is being used.
				this._tweenParams.step = function () {};
				this._state.current = from || {};
				this._tweenParams.to = to || {};
				this._tweenParams.duration = duration || this.duration;
				this._tweenParams.callback = callback || function () {};
				this._tweenParams.easing = easing || this.easing;
			} else {
				// If the second argument is not present, assume the longhand syntax is being used.
				this._tweenParams.step = from.step || function () {};
				this._tweenParams.callback = from.callback || function () {};
				this._state.current = from.from || {};
				this._tweenParams.to = from.to || {};
				this._tweenParams.duration = from.duration || this.duration;
				this._tweenParams.easing = from.easing || this.easing;
			}

			this._tweenParams.timestamp = now();
			this._tweenParams.easingFunc = this.formula[this._tweenParams.easing] || this.formula.linear;
			this._tweenParams.originalState = simpleCopy({}, this._state.current);
			applyFilter('tweenCreated', this._tweenParams.owner, [this._state.current, this._tweenParams.originalState, this._tweenParams.to]);
			this._tweenParams.tweenController = new Tween(this._tweenParams, this._state);
			this._state.isAnimating = true;

			scheduleUpdate(function () {
				timeoutHandler(self._tweenParams, self._state);
			}, this.fps);

			return this._tweenParams.tweenController;
		};

		this.hookAdd = function hookAdd (hookName, hookFunc) {
			if (!this._hook.hasOwnProperty(hookName)) {
				this._hook[hookName] = [];
			}

			this._hook[hookName].push(hookFunc);
		};

		this.hookRemove = function hookRemove (hookName, hookFunc) {
			var i;

			if (!this._hook.hasOwnProperty(hookName)) {
				return;
			}

			if (!hookFunc) {
				this._hook[hookName] = [];
				return;
			}

			for (i = this._hook[hookName].length; i >= 0; i++) {
				if (this._hook[hookName][i] === hookFunc) {
					this._hook[hookName].splice(i, 1);
				}
			}
		};

		return this;
	}

	Tweenable.prototype.filter = {};
	Tweenable.util = {
		'now': now
		,'each': each
		,'simpleCopy': simpleCopy
	};

	/**
	 * This object contains all of the tweens available to Tweeny.  It is extendable - simply attach properties to the Tweenable.prototype.formula Object following the same format at `linear`.
	 *
	 * This pattern was copied from Robert Penner, under BSD License (http://www.robertpenner.com/)
	 *
	 * @param t The current time
	 * @param b Start value
	 * @param c Change in value (delta)
	 * @param d Duration of the tween
	 */
	Tweenable.prototype.formula = {
		linear: function (t, b, c, d) {
			// no easing, no acceleration
			return c * t / d + b;
		}
	};

	global.Tweenable = Tweenable;

}(this));<|endoftext|>


<commit_before>// This file is required by the index.html file and will
// be executed in the renderer process for that window.
<commit_msg>Add comment<commit_after>// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.
<|endoftext|>