<commit_before>'use strict';

const path = require('path');

const ModulesTransform = (function() {
  let plugin = require('babel-plugin-transform-es2015-modules-amd');

  // adding `baseDir` ensures that broccoli-babel-transpiler does not
  // issue a warning and opt out of caching
  let pluginPath = require.resolve('babel-plugin-transform-es2015-modules-amd/package');
  let pluginBaseDir = path.dirname(pluginPath);
  plugin.baseDir = () => pluginBaseDir;

  return plugin;
})();

module.exports = function processModulesOnly(tree, annotation) {
  let options = {
    plugins: [
      [ModulesTransform, { loose: true, noIterop: true }],
    ],
    moduleIds: true,
    resolveModuleSource: require('amd-name-resolver').moduleResolve,
    annotation,
  };

  const Babel = require('broccoli-babel-transpiler');
  return new Babel(tree, options);
};
<commit_msg>bugfix(legacy-addons): Fixes typo in babel transpilation options<commit_after>'use strict';

const path = require('path');

const ModulesTransform = (function() {
  let plugin = require('babel-plugin-transform-es2015-modules-amd');

  // adding `baseDir` ensures that broccoli-babel-transpiler does not
  // issue a warning and opt out of caching
  let pluginPath = require.resolve('babel-plugin-transform-es2015-modules-amd/package');
  let pluginBaseDir = path.dirname(pluginPath);
  plugin.baseDir = () => pluginBaseDir;

  return plugin;
})();

module.exports = function processModulesOnly(tree, annotation) {
  let options = {
    plugins: [
      [ModulesTransform, { loose: true, noInterop: true }],
    ],
    moduleIds: true,
    resolveModuleSource: require('amd-name-resolver').moduleResolve,
    annotation,
  };

  const Babel = require('broccoli-babel-transpiler');
  return new Babel(tree, options);
};
<|endoftext|>


<commit_before>const Hapi = require('hapi');
const Good = require('good');
const algoliasearch = require('algoliasearch');
const client = algoliasearch("TDNMRH8LS3", "ec222292c9b89b658fe00b34ff341194");
const index = client.initIndex("songs");
const version = 2;

const handleFulfilment = function (req, reply) {
    const TAG = "fulfil";
    let response = {
        'source': "Algolia",
        'backend_version': version,
        data: []
    };
    let artist = '', artistOriginal = '', period = '', artistActual = '';
    let allHitsHaveSameArtist = true;

    if (req.payload.result.parameters['artist']) {
        artist = req.payload.result.parameters['artist'];
        artistOriginal = req.payload.result.contexts[0].parameters['artist.original'];
        server.log(TAG, "Artist: " + artist);
    }
    if (req.payload.result.parameters['period']) {
        period = req.payload.result.parameters['period'];
        server.log(TAG, "Period: " + period);
    }

    if (artist.length !== 0) {
        let songs = [];
        index.search(artist, {restrictSearchableAttributes: ['artistName']}, (err, content) => {
            if (err) {
                server.error(err);
                return;
            }

            server.log(TAG, "Searching for " + artist + " returned " + content.nbHits + " results.");
            if (content.nbHits > 0) {
                for (let i in content.hits) {
                    let hit = content.hits[i];
                    if (hit.artistName != artist) {
                        if (artistActual == '') {
                            artistActual = hit.artistName;
                        } else {
                            if (artistActual != hit.artistName) {
                                allHitsHaveSameArtist = false;
                            }
                        }
                    }
                    server.log(TAG, 'Hit(' + hit.objectID + '): ' + hit.trackName);
                    songs.push(hit);
                }
                response["speech"] = "I found those songs by " + artistOriginal + ": " + songs.map(hit => hit.trackName).join(", ") + ".";
                response["data"] = {"songs": songs};
                if (allHitsHaveSameArtist && artistActual != '') {
                    response["data"]["artist"] = artistActual;
                }
            } else {
                response["speech"] = "I'm afraid I know no songs from " + artist + ".";
            }
            server.log(TAG, "speech:" + response["speech"]);
            response["displayText"] = response["speech"];
            reply(response);
        });
    }
};

// Init server
const server = new Hapi.Server();
server.connection({
    host: +process.env.HOST || '0.0.0.0',
    port: +process.env.PORT || 8000
});

// Setup routes
server.route([
    {
        method: 'GET',
        path: '/',
        handler: function (request, reply) {
            reply("Success!\n");
        }
    },
    {
        method: 'POST',
        path: '/fulfillment',
        handler: handleFulfilment
    }
]);

// Setup reporting
server.register({
    register: Good,
    options: {
        reporters: {
            console: [{
                module: 'good-squeeze',
                name: 'Squeeze',
                args: [{
                    response: '*',
                    log: '*'
                }]
            }, {
                module: 'good-console'
            }, 'stdout']
        }
    }
}, (err) => {
    if (err) {
        throw err; // error while loading the plugin
    }
});

// Run server
server.start((err) => {
    if (err) {
        throw err;
    }

    console.log("Server running at ", server.info.uri);
});<commit_msg>artist search: Use input/artist name depending on results<commit_after>const Hapi = require('hapi');
const Good = require('good');
const algoliasearch = require('algoliasearch');
const client = algoliasearch("TDNMRH8LS3", "ec222292c9b89b658fe00b34ff341194");
const index = client.initIndex("songs");
const version = 3;

const handleFulfilment = function (req, reply) {
    const TAG = "fulfil";
    let response = {
        'source': "Algolia",
        'backend_version': version,
        data: []
    };
    let artist = '', artistOriginal = '', period = '', artistActual = '';
    let allHitsHaveSameArtist = true;

    if (req.payload.result.parameters['artist']) {
        artist = req.payload.result.parameters['artist'];
        artistOriginal = req.payload.result.contexts[0].parameters['artist.original'];
        server.log(TAG, "Artist: " + artist);
    }
    if (req.payload.result.parameters['period']) {
        period = req.payload.result.parameters['period'];
        server.log(TAG, "Period: " + period);
    }

    if (artist.length !== 0) { // Search for the given artist
        let songs = [];
        index.search(artist, {restrictSearchableAttributes: ['artistName']}, (err, content) => {
                if (err) {
                    server.error(err);
                    return;
                }

                server.log(TAG, "Searching for " + artist + " returned " + content.nbHits + " results.");
                if (content.nbHits > 0) {
                    for (let i in content.hits) {
                        let hit = content.hits[i];
                        if (hit.artistName != artist) {
                            if (artistActual == '') {
                                artistActual = hit.artistName;
                            } else {
                                if (artistActual != hit.artistName) {
                                    allHitsHaveSameArtist = false;
                                }
                            }
                        }
                        server.log(TAG, 'Hit(' + hit.objectID + '): ' + hit.trackName);
                        songs.push(hit);
                    }
                    response["data"] = {"songs": songs};
                    if (allHitsHaveSameArtist) {
                        // Only one artist, reply `by` with its name
                        const currentArtist = artistActual != '' ? artistActual : artist;
                        response["speech"] = "I found those songs by " + currentArtist + ": " + songs.map(hit => hit.trackName).join(", ") + ".";
                        response["data"]["artist"] = artistActual;
                    } else { // Several artists not matching the input, reply with `for input`
                        response["speech"] = "I found those songs for " + artistOriginal + ": " + songs.map(hit => hit.trackName).join(", ") + ".";
                    }
                }
                else {
                    response["speech"] = "I'm afraid I know no songs from " + artist + ".";
                }
                server.log(TAG, "speech:" + response["speech"]);
                response["displayText"] = response["speech"];
                reply(response);
            }
        );
    }
};

// Init server
const server = new Hapi.Server();
server.connection({
    host: +process.env.HOST || '0.0.0.0',
    port: +process.env.PORT || 8000
});

// Setup routes
server.route([
    {
        method: 'GET',
        path: '/',
        handler: function (request, reply) {
            reply("Success!\n");
        }
    },
    {
        method: 'POST',
        path: '/fulfillment',
        handler: handleFulfilment
    }
]);

// Setup reporting
server.register({
    register: Good,
    options: {
        reporters: {
            console: [{
                module: 'good-squeeze',
                name: 'Squeeze',
                args: [{
                    response: '*',
                    log: '*'
                }]
            }, {
                module: 'good-console'
            }, 'stdout']
        }
    }
}, (err) => {
    if (err) {
        throw err; // error while loading the plugin
    }
});

// Run server
server.start((err) => {
    if (err) {
        throw err;
    }

    console.log("Server running at ", server.info.uri);
});<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, CodeMirror, brackets */

/**
 * ExtensionLoader searches the filesystem for extensions, then creates a new context for each one and loads it
 */

define(function (require, exports, module) {
    'use strict';

    var NativeFileSystem    = require("file/NativeFileSystem").NativeFileSystem,
        FileUtils           = require("file/FileUtils"),
        Async               = require("utils/Async");

    /**
     * Loads the extension that lives at baseUrl into its own Require.js context
     *
     * @param {!string} name, used to identify the extension
     * @param {!string} baseUrl, URL path relative to index.html, where the main JS file can be found
     * @param {!string} entryPoint, name of the main js file to load
     * @return {!$.Promise} A promise object that is resolved when the extension is loaded.
     */
    function loadExtension(name, baseUrl, entryPoint) {
        var result = new $.Deferred(),
            extensionRequire = brackets.libRequire.config({
                context: name,
                baseUrl: baseUrl
            });

        console.log("[Extension] starting to load " + baseUrl);

        extensionRequire([entryPoint], function () {
            console.log("[Extension] finished loading " + baseUrl);
            result.resolve();
        });

        return result.promise();
    }

    /**
     * Runs unit tests for the extension that lives at baseUrl into its own Require.js context
     *
     * @param {!string} name, used to identify the extension
     * @param {!string} baseUrl, URL path relative to index.html, where the main JS file can be found
     * @param {!string} entryPoint, name of the main js file to load
     * @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
     */
    function testExtension(name, baseUrl, entryPoint) {
        var result = new $.Deferred(),
            extensionPath = FileUtils.getNativeBracketsDirectoryPath();

        // Assumes the caller's window.location context is /test/SpecRunner.html
        extensionPath = extensionPath.replace("brackets/test", "brackets/src"); // convert from "test" to "src"
        extensionPath += "/" + baseUrl + "/" + entryPoint + ".js";

        var fileExists = false, statComplete = false;
        brackets.fs.stat(extensionPath, function (err, stat) {
            statComplete = true;
            if (err === brackets.fs.NO_ERROR && stat.isFile()) {
                // unit test file exists
                var extensionRequire = brackets.libRequire.config({
                    context: name,
                    baseUrl: "../src/" + baseUrl
                });

                console.log("[Extension] loading unit test " + baseUrl);
                extensionRequire([entryPoint], function () {
                    console.log("[Extension] loaded unit tests " + baseUrl);
                    result.resolve();
                });
            } else {
                result.reject();
            }
        });

        return result.promise();
    }

    /**
     * @private
     * Loads a file entryPoint from each extension folder within the baseUrl into its own Require.js context
     *
     * @param {!string} directory, an absolute native path that contains a directory of extensions.
     *                  each subdirectory is interpreted as an independent extension
     * @param {!string} baseUrl, URL path relative to index.html that maps to the same place as directory
     * @param {!string} entryPoint Module name to load (without .js suffix)
     * @param {function} processExtension
     * @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
     */
    function _loadAll(directory, baseUrl, entryPoint, processExtension) {
        var result = new $.Deferred();

        NativeFileSystem.requestNativeFileSystem(directory,
            function (rootEntry) {
                rootEntry.createReader().readEntries(
                    function (entries) {
                        var i,
                            extensions = [];

                        for (i = 0; i < entries.length; i++) {
                            if (entries[i].isDirectory) {
                                // FUTURE (JRB): read package.json instead of just using the entrypoint "main".
                                // Also, load sub-extensions defined in package.json.
                                extensions.push(entries[i].name);
                            }
                        }

                        Async.doInParallel(extensions, function (item) {
                            return processExtension(item, baseUrl + "/" + item, entryPoint);
                        }).done(function () {
                            result.resolve();
                        }).fail(function () {
                            result.reject();
                        });
                    },
                    function (error) {
                        console.log("[Extension] Error -- could not read native directory: " + directory);
                    }
                );
            },
            function (error) {
                console.log("[Extension] Error -- could not open native directory: " + directory);
            });

        return result.promise();
    }

    /**
     * Loads the extension that lives at baseUrl into its own Require.js context
     *
     * @param {!string} directory, an absolute native path that contains a directory of extensions.
     *                  each subdirectory is interpreted as an independent extension
     * @param {!string} baseUrl, URL path relative to index.html that maps to the same place as directory
     * @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
     */
    function loadAllExtensionsInNativeDirectory(directory, baseUrl) {
        return _loadAll(directory, baseUrl, "main", loadExtension);
    }

    /**
     * Runs unit test for the extension that lives at baseUrl into its own Require.js context
     *
     * @param {!string} directory, an absolute native path that contains a directory of extensions.
     *                  each subdirectory is interpreted as an independent extension
     * @param {!string} baseUrl, URL path relative to index.html that maps to the same place as directory
     * @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
     */
    function testAllExtensionsInNativeDirectory(directory, baseUrl) {
        return _loadAll(directory, baseUrl, "unittests", testExtension);
    }

    exports.loadExtension = loadExtension;
    exports.testExtension = testExtension;
    exports.loadAllExtensionsInNativeDirectory = loadAllExtensionsInNativeDirectory;
    exports.testAllExtensionsInNativeDirectory = testAllExtensionsInNativeDirectory;
});
<commit_msg>Modified ExtensionLoader to allow for retrieving the require context that loaded an extension<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, CodeMirror, brackets */

/**
 * ExtensionLoader searches the filesystem for extensions, then creates a new context for each one and loads it
 */

define(function (require, exports, module) {
    'use strict';

    var NativeFileSystem    = require("file/NativeFileSystem").NativeFileSystem,
        FileUtils           = require("file/FileUtils"),
        Async               = require("utils/Async"),
        contexts            = {};
    /**
     * Returns the require.js require context used to load an extension
     *
     * @param {!string} name, used to identify the extension
     * @return {!Object} A require.js require object used to load the extension, or undefined if
     * there is no require object ith that name
     */
    function getRequireContextForExtension(name) {
        return contexts[name];
    }

    /**
     * Loads the extension that lives at baseUrl into its own Require.js context
     *
     * @param {!string} name, used to identify the extension
     * @param {!string} baseUrl, URL path relative to index.html, where the main JS file can be found
     * @param {!string} entryPoint, name of the main js file to load
     * @return {!$.Promise} A promise object that is resolved when the extension is loaded.
     */
    function loadExtension(name, baseUrl, entryPoint) {
        var result = new $.Deferred(),
            extensionRequire = brackets.libRequire.config({
                context: name,
                baseUrl: baseUrl
            });
        contexts[name] = extensionRequire;

        console.log("[Extension] starting to load " + baseUrl);

        extensionRequire([entryPoint], function () {
            console.log("[Extension] finished loading " + baseUrl);
            result.resolve();
        });

        return result.promise();
    }

    /**
     * Runs unit tests for the extension that lives at baseUrl into its own Require.js context
     *
     * @param {!string} name, used to identify the extension
     * @param {!string} baseUrl, URL path relative to index.html, where the main JS file can be found
     * @param {!string} entryPoint, name of the main js file to load
     * @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
     */
    function testExtension(name, baseUrl, entryPoint) {
        var result = new $.Deferred(),
            extensionPath = FileUtils.getNativeBracketsDirectoryPath();

        // Assumes the caller's window.location context is /test/SpecRunner.html
        extensionPath = extensionPath.replace("brackets/test", "brackets/src"); // convert from "test" to "src"
        extensionPath += "/" + baseUrl + "/" + entryPoint + ".js";

        var fileExists = false, statComplete = false;
        brackets.fs.stat(extensionPath, function (err, stat) {
            statComplete = true;
            if (err === brackets.fs.NO_ERROR && stat.isFile()) {
                // unit test file exists
                var extensionRequire = brackets.libRequire.config({
                    context: name,
                    baseUrl: "../src/" + baseUrl
                });

                console.log("[Extension] loading unit test " + baseUrl);
                extensionRequire([entryPoint], function () {
                    console.log("[Extension] loaded unit tests " + baseUrl);
                    result.resolve();
                });
            } else {
                result.reject();
            }
        });

        return result.promise();
    }

    /**
     * @private
     * Loads a file entryPoint from each extension folder within the baseUrl into its own Require.js context
     *
     * @param {!string} directory, an absolute native path that contains a directory of extensions.
     *                  each subdirectory is interpreted as an independent extension
     * @param {!string} baseUrl, URL path relative to index.html that maps to the same place as directory
     * @param {!string} entryPoint Module name to load (without .js suffix)
     * @param {function} processExtension
     * @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
     */
    function _loadAll(directory, baseUrl, entryPoint, processExtension) {
        var result = new $.Deferred();

        NativeFileSystem.requestNativeFileSystem(directory,
            function (rootEntry) {
                rootEntry.createReader().readEntries(
                    function (entries) {
                        var i,
                            extensions = [];

                        for (i = 0; i < entries.length; i++) {
                            if (entries[i].isDirectory) {
                                // FUTURE (JRB): read package.json instead of just using the entrypoint "main".
                                // Also, load sub-extensions defined in package.json.
                                extensions.push(entries[i].name);
                            }
                        }

                        Async.doInParallel(extensions, function (item) {
                            return processExtension(item, baseUrl + "/" + item, entryPoint);
                        }).done(function () {
                            result.resolve();
                        }).fail(function () {
                            result.reject();
                        });
                    },
                    function (error) {
                        console.log("[Extension] Error -- could not read native directory: " + directory);
                    }
                );
            },
            function (error) {
                console.log("[Extension] Error -- could not open native directory: " + directory);
            });

        return result.promise();
    }

    /**
     * Loads the extension that lives at baseUrl into its own Require.js context
     *
     * @param {!string} directory, an absolute native path that contains a directory of extensions.
     *                  each subdirectory is interpreted as an independent extension
     * @param {!string} baseUrl, URL path relative to index.html that maps to the same place as directory
     * @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
     */
    function loadAllExtensionsInNativeDirectory(directory, baseUrl) {
        return _loadAll(directory, baseUrl, "main", loadExtension);
    }

    /**
     * Runs unit test for the extension that lives at baseUrl into its own Require.js context
     *
     * @param {!string} directory, an absolute native path that contains a directory of extensions.
     *                  each subdirectory is interpreted as an independent extension
     * @param {!string} baseUrl, URL path relative to index.html that maps to the same place as directory
     * @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
     */
    function testAllExtensionsInNativeDirectory(directory, baseUrl) {
        return _loadAll(directory, baseUrl, "unittests", testExtension);
    }

    exports.getRequireContextForExtension = getRequireContextForExtension;
    exports.loadExtension = loadExtension;
    exports.testExtension = testExtension;
    exports.loadAllExtensionsInNativeDirectory = loadAllExtensionsInNativeDirectory;
    exports.testAllExtensionsInNativeDirectory = testAllExtensionsInNativeDirectory;
});
<|endoftext|>


<commit_before>/*
 * to-markdown - an HTML to Markdown converter
 *
 * Copyright 2011-15, Dom Christie
 * Licenced under the MIT licence
 *
 */

'use strict';

var htmlToDom = require('./lib/html-to-dom');
var converters = require('./lib/md-converters');
var utilities = require('./lib/utilities');
var gfmConverters = require('./lib/gfm-converters');

var isBlock = utilities.isBlock;
var trim = utilities.trim;
var decodeHTMLEntities = require('he').decode;

var VOID_ELEMENTS = [
  'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
  'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
];

var toMarkdown;

module.exports = toMarkdown = function (input) {

  if (typeof input !== 'string') {
    throw new TypeError(input + ' is not a string');
  }

  // Escape potential ol triggers
  input = input.replace(/(\d+)\. /g, '$1\\. ');

  var doc = htmlToDom(input);
  var clone = doc.body;

  // Flattens node tree into a single array
  var nodes = bfsOrder(clone);

  converters = converters.concat(gfmConverters);

  // Loop through nodes in reverse (so deepest child elements are first).
  // Replace nodes as necessary.
  for (var i = nodes.length - 1; i >= 0; i--) {
    var node = nodes[i];
    var replacement = replacementForNode(node, doc);
    if (replacement) { node.parentNode.replaceChild(replacement, node); }
  }

  var output = decodeHTMLEntities(clone.innerHTML);

  return output.replace(/^[\t\r\n]+|[\t\r\n\s]+$/g, '')
               .replace(/\n\s+\n/g, '\n\n')
               .replace(/\n{3,}/g, '\n\n');
};

toMarkdown.decodeHTMLEntities = decodeHTMLEntities;
toMarkdown.isBlock = isBlock;
toMarkdown.trim = trim;

function bfsOrder(root) {
  var inqueue = [root];
  var outqueue = [];
  while (inqueue.length > 0) {
    var elem = inqueue.shift();
    outqueue.push(elem);
    var children = elem.childNodes;
    for (var i = 0 ; i < children.length; i++) {
      if (children[i].nodeType === 1) {
        inqueue.push(children[i]);
      }
    }
  }
  outqueue.shift();
  return outqueue;
}

function canConvertNode(node, filter) {
  if (typeof filter === 'string') {
    return filter === node.nodeName.toLowerCase();
  }
  if (Array.isArray(filter)) {
    return filter.indexOf(node.nodeName.toLowerCase()) !== -1;
  }
  else if (typeof filter === 'function') {
    return filter.call(toMarkdown, node);
  }
  else {
    throw '`filter` needs to be a string, array, or function';
  }
}

function isFlankedByExternalSpace(direction, node) {
  var sibling,
      regExp,
      flankedBySpace,
      flankedBySpaceInInlineElement;

  if (direction === 'left') {
    sibling = node.previousSibling;
    regExp = / $/;
  }
  else {
    sibling = node.nextSibling;
    regExp = /^ /;
  }

  if (sibling) {
    if (sibling.nodeType === 3) {
      flankedBySpace = regExp.test(sibling.nodeValue);
    }
    else if(sibling.nodeType === 1 && !isBlock(sibling)) {
      flankedBySpaceInInlineElement = regExp.test(node.textContent || node.innertext);
    }
  }
  return flankedBySpace || flankedBySpaceInInlineElement;
}

// Loops through all md converters, checking to see if the node nodeName matches.
// Returns the replacement text node or null.
function replacementForNode(node, doc) {

  // Remove blank nodes
  if (VOID_ELEMENTS.indexOf(node.nodeName.toLowerCase()) === -1 && /^\s*$/i.test(node.innerHTML)) {
    return doc.createTextNode('');
  }

  for (var i = 0; i < converters.length; i++) {
    var converter = converters[i];

    if (canConvertNode(node, converter.filter)) {
      var replacement = converter.replacement;
      var text;
      var textNode;
      var leadingSpace = '';
      var trailingSpace = '';

      if (typeof replacement !== 'function') {
        throw '`replacement` needs to be a function that returns a string';
      }

      if (!isBlock(node)) {
        var hasLeadingWhitespace = /^[ \r\n\t]/.test(node.innerHTML);
        var hasTrailingWhitespace = /[ \r\n\t]$/.test(node.innerHTML);

        node.innerHTML = trim(node.innerHTML);

        if (hasLeadingWhitespace && !isFlankedByExternalSpace('left', node)) {
          leadingSpace = ' ';
        }
        if (hasTrailingWhitespace && !isFlankedByExternalSpace('right', node)) {
          trailingSpace = ' ';
        }
      }

      text = replacement.call(toMarkdown, decodeHTMLEntities(node.innerHTML), node);
      textNode = doc.createTextNode(leadingSpace + text + trailingSpace);
      textNode._attributes = node.attributes;

      return textNode;
    }
  }
  return null;
}
<commit_msg>Conditionally import gfm converters<commit_after>/*
 * to-markdown - an HTML to Markdown converter
 *
 * Copyright 2011-15, Dom Christie
 * Licenced under the MIT licence
 *
 */

'use strict';

var htmlToDom = require('./lib/html-to-dom');
var converters = require('./lib/md-converters');
var utilities = require('./lib/utilities');
var gfmConverters = require('./lib/gfm-converters');

var isBlock = utilities.isBlock;
var trim = utilities.trim;
var decodeHTMLEntities = require('he').decode;

var VOID_ELEMENTS = [
  'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
  'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
];

var toMarkdown;

module.exports = toMarkdown = function (input, options) {
  options = options || {};

  if (typeof input !== 'string') {
    throw new TypeError(input + ' is not a string');
  }

  // Escape potential ol triggers
  input = input.replace(/(\d+)\. /g, '$1\\. ');

  var doc = htmlToDom(input);
  var clone = doc.body;

  // Flattens node tree into a single array
  var nodes = bfsOrder(clone);

  if (options.gfm) {
    converters = gfmConverters.concat(converters);
  }

  // Loop through nodes in reverse (so deepest child elements are first).
  // Replace nodes as necessary.
  for (var i = nodes.length - 1; i >= 0; i--) {
    var node = nodes[i];
    var replacement = replacementForNode(node, doc);
    if (replacement) { node.parentNode.replaceChild(replacement, node); }
  }

  var output = decodeHTMLEntities(clone.innerHTML);

  return output.replace(/^[\t\r\n]+|[\t\r\n\s]+$/g, '')
               .replace(/\n\s+\n/g, '\n\n')
               .replace(/\n{3,}/g, '\n\n');
};

toMarkdown.decodeHTMLEntities = decodeHTMLEntities;
toMarkdown.isBlock = isBlock;
toMarkdown.trim = trim;

function bfsOrder(root) {
  var inqueue = [root];
  var outqueue = [];
  while (inqueue.length > 0) {
    var elem = inqueue.shift();
    outqueue.push(elem);
    var children = elem.childNodes;
    for (var i = 0 ; i < children.length; i++) {
      if (children[i].nodeType === 1) {
        inqueue.push(children[i]);
      }
    }
  }
  outqueue.shift();
  return outqueue;
}

function canConvertNode(node, filter) {
  if (typeof filter === 'string') {
    return filter === node.nodeName.toLowerCase();
  }
  if (Array.isArray(filter)) {
    return filter.indexOf(node.nodeName.toLowerCase()) !== -1;
  }
  else if (typeof filter === 'function') {
    return filter.call(toMarkdown, node);
  }
  else {
    throw '`filter` needs to be a string, array, or function';
  }
}

function isFlankedByExternalSpace(direction, node) {
  var sibling,
      regExp,
      flankedBySpace,
      flankedBySpaceInInlineElement;

  if (direction === 'left') {
    sibling = node.previousSibling;
    regExp = / $/;
  }
  else {
    sibling = node.nextSibling;
    regExp = /^ /;
  }

  if (sibling) {
    if (sibling.nodeType === 3) {
      flankedBySpace = regExp.test(sibling.nodeValue);
    }
    else if(sibling.nodeType === 1 && !isBlock(sibling)) {
      flankedBySpaceInInlineElement = regExp.test(node.textContent || node.innertext);
    }
  }
  return flankedBySpace || flankedBySpaceInInlineElement;
}

// Loops through all md converters, checking to see if the node nodeName matches.
// Returns the replacement text node or null.
function replacementForNode(node, doc) {

  // Remove blank nodes
  if (VOID_ELEMENTS.indexOf(node.nodeName.toLowerCase()) === -1 && /^\s*$/i.test(node.innerHTML)) {
    return doc.createTextNode('');
  }

  for (var i = 0; i < converters.length; i++) {
    var converter = converters[i];

    if (canConvertNode(node, converter.filter)) {
      var replacement = converter.replacement;
      var text;
      var textNode;
      var leadingSpace = '';
      var trailingSpace = '';

      if (typeof replacement !== 'function') {
        throw '`replacement` needs to be a function that returns a string';
      }

      if (!isBlock(node)) {
        var hasLeadingWhitespace = /^[ \r\n\t]/.test(node.innerHTML);
        var hasTrailingWhitespace = /[ \r\n\t]$/.test(node.innerHTML);

        node.innerHTML = trim(node.innerHTML);

        if (hasLeadingWhitespace && !isFlankedByExternalSpace('left', node)) {
          leadingSpace = ' ';
        }
        if (hasTrailingWhitespace && !isFlankedByExternalSpace('right', node)) {
          trailingSpace = ' ';
        }
      }

      text = replacement.call(toMarkdown, decodeHTMLEntities(node.innerHTML), node);
      textNode = doc.createTextNode(leadingSpace + text + trailingSpace);
      textNode._attributes = node.attributes;

      return textNode;
    }
  }
  return null;
}
<|endoftext|>


<commit_before>export default {
  en: {
    assessment: {
      unansweredQuestionWarning: "Warning There are unanswered questions", // Warning displayed on last question if there are unanswered questions
      leavingQuizPopup: "Don’t leave!", // Text displayed on javascript window alert when a student navigates away from summative quiz
      counterQuestion: "Question {0} of {1}", // Counter for when only a single question is displayed at a time
      counterPage: "Page {0} of {1}", // Counter for when many questions are displayed at a time
    },
    assessmentComplete: {
      complete: "Quiz Complete" // Text displayed when quiz is completed
    },
    twoButtonNav:{
      previousButton: "Previous", // Text displayed on previous questions button
      nextButton: "Next", // Text displayed on next questions button
      checkAnswerButton: "Check Answer", // Text displayed on next questions button
      submitButton: "Finish Quiz" // Text displayed on submit button
    },
    item:{
      questionDirectionMany:"Choose ALL that apply.", // Question direction for multi answer question
      questionDirectionAll:"Choose the BEST answer." // Question direction for single answer question
    },
    retriesExceeded: {
      triesExceeded: "Too many tries" // Text displayed when a student has run out of quiz attempts
    },
    start: {
      summativeInstruction: "Summative Quiz", // Instructions for summative quiz
      formativeInstruction: "Formative Quiz", // Instructions for formative quiz
      showWhatYouKnowInstruction: "Show What You Know", // Instructions for 'show what you know' style quiz
      startButton: "Start Quiz" // Text displayed on start quiz button
    },
    notFound: {
      notFound: "Not Found" // Text displayed when a page is not found
    },
    about: {
      title:"Open Assessments" // About page title
    },
    loading: {
      loading: "Loading..."
    },
    audioUpload: {
      record: "Record",
      stop: "Stop"
    }
  }
};
<commit_msg>remove question prompt from localized strings<commit_after>export default {
  en: {
    assessment: {
      unansweredQuestionWarning: "Warning There are unanswered questions", // Warning displayed on last question if there are unanswered questions
      leavingQuizPopup: "Don’t leave!", // Text displayed on javascript window alert when a student navigates away from summative quiz
      counterQuestion: "Question {0} of {1}", // Counter for when only a single question is displayed at a time
      counterPage: "Page {0} of {1}", // Counter for when many questions are displayed at a time
    },
    assessmentComplete: {
      complete: "Quiz Complete" // Text displayed when quiz is completed
    },
    twoButtonNav:{
      previousButton: "Previous", // Text displayed on previous questions button
      nextButton: "Next", // Text displayed on next questions button
      checkAnswerButton: "Check Answer", // Text displayed on next questions button
      submitButton: "Finish Quiz" // Text displayed on submit button
    },
    retriesExceeded: {
      triesExceeded: "Too many tries" // Text displayed when a student has run out of quiz attempts
    },
    start: {
      summativeInstruction: "Summative Quiz", // Instructions for summative quiz
      formativeInstruction: "Formative Quiz", // Instructions for formative quiz
      showWhatYouKnowInstruction: "Show What You Know", // Instructions for 'show what you know' style quiz
      startButton: "Start Quiz" // Text displayed on start quiz button
    },
    notFound: {
      notFound: "Not Found" // Text displayed when a page is not found
    },
    about: {
      title:"Open Assessments" // About page title
    },
    loading: {
      loading: "Loading..."
    },
    audioUpload: {
      record: "Record",
      stop: "Stop"
    }
  }
};
<|endoftext|>


<commit_before>/**
 * withData higher-order component.
 *
 * Site Kit by Google, Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import getNoDataComponent from 'GoogleComponents/notifications/nodata';
import getDataErrorComponent from 'GoogleComponents/notifications/data-error';
import getSetupIncompleteComponent from 'GoogleComponents/notifications/setup-incomplete';

const {
	addFilter,
	addAction,
} = wp.hooks;
const { each, isArray } = lodash;
const { Component } = wp.element;
const { __ } = wp.i18n;

/**
 * A Higher order Component that provides data functionality to Components.
 *
 * This function takes a React Component that is data dependent, resolving via the data API.
 *
 * Automatically detects data errors, displaying an error CTA Component. Components can extend the default
 * error handling to enable custom error messaging or data shapes.
 *
 * Components can provide a callback that checks if the data is "zero" - typically when an account is newly established and not yet providing data. In most cases the API returns all 0s, however some APIs may return empty strings or null.
 *
 * Components can optionally include `handleDataError` and `handleDataSuccess` function as props. `handleDataError` will be
 * called with the error message string if there is a data error and called with no string if the data is empty.
 * `handleDataSuccess` will be called when data resolves correctly.
 *
 * @param {React.Component} DataDependentComponent The React Component to render once we have its required data.
 * @param {Array}           selectData             An array of data objects to resolve.
 *                                                 Each object includes the following properties:
 *                                                 {string}         type       The data type. Either 'core' or 'modules'.
 *                                                 {string}         identifier The data identifier, for example a module slug.
 *                                                 {string}         datapoint  The datapoint.
 *                                                 {Object?}        data       Optional arguments to pass along.
 *                                                 {number}         priority   The data request priority, used for batching.
 *                                                 {number}         maxAge     How long to cache the data results.
 *                                                 {string | array} context    The context(s) to resolve data, eg 'Dashboard'.
 *
 * @param {React.Component} loadingComponent       A React Component to render while the data is resolving.
 * @param {Object}          layoutOptions          An object with layout options that are passed to the getNoDataComponent and getDataErrorComponent components.
 * @param {function}        isDataZero             A callback function that is passed the resolved data and returns true
 *                                                 if the data is "zero".
 * @param {function}        getDataError           A callback function that is passed the resolved data and returns the
 *                                                 error message.

 *
 */
const withData = (
	DataDependentComponent,
	selectData,
	loadingComponent = null,

	layoutOptions = {
		inGrid: false,
		fullWidth: false,
		createGrid: false,
	},

	// The default isDataZero handler always returns false, Components must define when data is zero.
	// `isDataZero` is passed `returnedData`and `datapoint`.
	isDataZero = () => {
		return false;
	},

	// The default getDataError handler detects data.error and extracts the message from data.error.message or data.error.errors[0].message.
	getDataError = ( data ) => {
		if ( data && data.error ) {
			if ( data.error.message ) {
				return data.error.message;
			}
			if ( data.error.errors && data.error.errors[ 0 ] && data.error.errors[ 0 ].message ) {
				return data.error.errors[ 0 ].message;
			}
			return __( 'Unidentified error', 'google-site-kit' );
		}

		if ( data && data.errors && data.errors[ 0 ] && data.errors[ 0 ].message ) {
			return data.errors[ 0 ].message;
		}

		if ( data && data.error_data ) {
			const errors = Object.values( data.error_data );

			// Catch RateLimitExceeded specifically.
			if ( errors[ 0 ] && 'RateLimitExceeded' === errors[ 0 ].reason ) {
				return __( 'Too many requests have been sent within a given time span. Please reload this page again in a few seconds', 'google-site-kit' );
			}
		}

		if ( data && data.errors ) {
			const errors = Object.values( data.errors );
			if ( errors[ 0 ] && errors[ 0 ][ 0 ] ) {
				return errors[ 0 ][ 0 ];
			}
		}

		// No error.
		return false;
	}
) => {
	// ...and returns another component...
	return class NewComponent extends Component {
		constructor( props ) {
			super( props );

			this.state = {
				data: false,
				zeroData: false,
				error: false,
			};

			addAction(
				'googlesitekit.moduleDataReset',
				'googlesitekit.moduleDataResetHandler',
				() => {
					this.setState( { data: false } );
				}
			);

			/**
			 * Handle a single datapoint returned from the data API.
			 *
			 * Each resolved data point is passed thru this handler to detect errors and zero data conditions, and
			 * to trigger `handleDataError` and `handleDataSuccess` helpers.
			 *
			 * @param {Object} returnedData The data returned from the API.
			 * @param {string} datapoint    The datapoint name resolved by the API.
			 * @param {Object} requestData  The data object for the request.
			 */
			const handleReturnedData = ( returnedData, datapoint, requestData ) => {
				// If available, `handleDataError` will be called for errors (with a string) and empty data.
				const {
					handleDataError,
					handleDataSuccess,
				} = this.props;
				const { identifier } = requestData;

				// Check to see if the returned data is an error. If so, getDataError will return a string.
				const error = getDataError( returnedData );
				if ( error ) {
					// Set an error state on the Component.
					this.setState( {
						error,
						module: identifier,
					} );

					// If the Component included a `handleDataError` helper, pass it the error message.
					if ( handleDataError ) {
						handleDataError( error );
					}
				} else if ( isDataZero( returnedData, datapoint ) ) { // No data error, next check for zero data.
					// If we have a `handleDataError` call it without any parameters (indicating empty data).
					if ( handleDataError ) {
						handleDataError( error );
					}

					// Set a zeroData state on the Component.
					this.setState( { zeroData: true } );
				} else if ( handleDataSuccess ) {
					// Success! `handleDataSuccess` will be called (ie. not error or zero).
					handleDataSuccess();
				}

				// Resolve the returned data my setting state on the Component.
				this.setState( {
					requestData,
					data: returnedData,
					datapoint,
					module: identifier,
				} );
			};

			// Resolve all selectedData.
			each( selectData, ( data ) => {
				// Handle single contexts, or arrays of contexts.
				if ( isArray( data.context ) ) {
					each( data.context, ( acontext ) => {
						/**
						 * Request data for the context.
						 */
						addFilter( `googlesitekit.module${ acontext }DataRequest`,
							`googlesitekit.data${ acontext }`, ( moduleData ) => {
								data.callback = ( returnedData, datapoint ) => {
									handleReturnedData( returnedData, datapoint, data );
								};
								moduleData.push( data );
								return moduleData;
							} );
					} );
				} else {
					/**
					 * Request data for the context.
					 */
					addFilter( `googlesitekit.module${ data.context }DataRequest`,
						`googlesitekit.data${ data.context }`, ( moduleData ) => {
							data.callback = ( returnedData, datapoint ) => {
								handleReturnedData( returnedData, datapoint, data );
							};
							moduleData.push( data );
							return moduleData;
						} );
				}
			} );
		}

		render() {
			const {
				data,
				datapoint,
				module,
				zeroData,
				error,
				requestData,
			} = this.state;

			// Render the loading component until we have data.
			if ( ! data ) {
				return loadingComponent;
			}

			const moduleName = module ? googlesitekit.modules[ module ].name : __( 'Site Kit', 'google-site-kit' );

			// If module is active but setup not complete.
			if ( module && googlesitekit.modules[ module ].active && ! googlesitekit.modules[ module ].setupComplete ) {
				return getSetupIncompleteComponent( module, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// If we have an error, display the DataErrorComponent.
			if ( error ) {
				return ( 'string' !== typeof error ) ? error : getDataErrorComponent( moduleName, error, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// If we have zeroData, display the NoDataComponent.
			if ( zeroData ) {
				return getNoDataComponent( moduleName, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// Render the Component when we have data, passing the datapoint.
			return (
				<DataDependentComponent
					data={ data }
					datapoint={ datapoint }
					requestData={ requestData }
					{ ...this.props }
				/>
			);
		}
	};
};

export default withData;
<commit_msg>simplify withData handleReturnedData<commit_after>/**
 * withData higher-order component.
 *
 * Site Kit by Google, Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import getNoDataComponent from 'GoogleComponents/notifications/nodata';
import getDataErrorComponent from 'GoogleComponents/notifications/data-error';
import getSetupIncompleteComponent from 'GoogleComponents/notifications/setup-incomplete';

const {
	addFilter,
	addAction,
} = wp.hooks;
const { each, isArray } = lodash;
const { Component } = wp.element;
const { __ } = wp.i18n;

/**
 * A Higher order Component that provides data functionality to Components.
 *
 * This function takes a React Component that is data dependent, resolving via the data API.
 *
 * Automatically detects data errors, displaying an error CTA Component. Components can extend the default
 * error handling to enable custom error messaging or data shapes.
 *
 * Components can provide a callback that checks if the data is "zero" - typically when an account is newly established and not yet providing data. In most cases the API returns all 0s, however some APIs may return empty strings or null.
 *
 * Components can optionally include `handleDataError` and `handleDataSuccess` function as props. `handleDataError` will be
 * called with the error message string if there is a data error and called with no string if the data is empty.
 * `handleDataSuccess` will be called when data resolves correctly.
 *
 * @param {React.Component} DataDependentComponent The React Component to render once we have its required data.
 * @param {Array}           selectData             An array of data objects to resolve.
 *                                                 Each object includes the following properties:
 *                                                 {string}         type       The data type. Either 'core' or 'modules'.
 *                                                 {string}         identifier The data identifier, for example a module slug.
 *                                                 {string}         datapoint  The datapoint.
 *                                                 {Object?}        data       Optional arguments to pass along.
 *                                                 {number}         priority   The data request priority, used for batching.
 *                                                 {number}         maxAge     How long to cache the data results.
 *                                                 {string | array} context    The context(s) to resolve data, eg 'Dashboard'.
 *
 * @param {React.Component} loadingComponent       A React Component to render while the data is resolving.
 * @param {Object}          layoutOptions          An object with layout options that are passed to the getNoDataComponent and getDataErrorComponent components.
 * @param {function}        isDataZero             A callback function that is passed the resolved data and returns true
 *                                                 if the data is "zero".
 * @param {function}        getDataError           A callback function that is passed the resolved data and returns the
 *                                                 error message.

 *
 */
const withData = (
	DataDependentComponent,
	selectData,
	loadingComponent = null,

	layoutOptions = {
		inGrid: false,
		fullWidth: false,
		createGrid: false,
	},

	// The default isDataZero handler always returns false, Components must define when data is zero.
	// `isDataZero` is passed `returnedData`and `datapoint`.
	isDataZero = () => {
		return false;
	},

	// The default getDataError handler detects data.error and extracts the message from data.error.message or data.error.errors[0].message.
	getDataError = ( data ) => {
		if ( data && data.error ) {
			if ( data.error.message ) {
				return data.error.message;
			}
			if ( data.error.errors && data.error.errors[ 0 ] && data.error.errors[ 0 ].message ) {
				return data.error.errors[ 0 ].message;
			}
			return __( 'Unidentified error', 'google-site-kit' );
		}

		if ( data && data.errors && data.errors[ 0 ] && data.errors[ 0 ].message ) {
			return data.errors[ 0 ].message;
		}

		if ( data && data.error_data ) {
			const errors = Object.values( data.error_data );

			// Catch RateLimitExceeded specifically.
			if ( errors[ 0 ] && 'RateLimitExceeded' === errors[ 0 ].reason ) {
				return __( 'Too many requests have been sent within a given time span. Please reload this page again in a few seconds', 'google-site-kit' );
			}
		}

		if ( data && data.errors ) {
			const errors = Object.values( data.errors );
			if ( errors[ 0 ] && errors[ 0 ][ 0 ] ) {
				return errors[ 0 ][ 0 ];
			}
		}

		// No error.
		return false;
	}
) => {
	// ...and returns another component...
	return class NewComponent extends Component {
		constructor( props ) {
			super( props );

			this.state = {
				data: false,
				zeroData: false,
				error: false,
			};

			addAction(
				'googlesitekit.moduleDataReset',
				'googlesitekit.moduleDataResetHandler',
				() => {
					this.setState( { data: false } );
				}
			);

			/**
			 * Handle a single datapoint returned from the data API.
			 *
			 * Each resolved data point is passed thru this handler to detect errors and zero data conditions, and
			 * to trigger `handleDataError` and `handleDataSuccess` helpers.
			 *
			 * @param {Object} returnedData The data returned from the API.
			 * @param {Object} requestData  The data object for the request.
			 */
			const handleReturnedData = ( returnedData, requestData ) => {
				// If available, `handleDataError` will be called for errors (with a string) and empty data.
				const {
					handleDataError,
					handleDataSuccess,
				} = this.props;
				const { datapoint, identifier } = requestData;

				// Check to see if the returned data is an error. If so, getDataError will return a string.
				const error = getDataError( returnedData );
				if ( error ) {
					// Set an error state on the Component.
					this.setState( {
						error,
						module: identifier,
					} );

					// If the Component included a `handleDataError` helper, pass it the error message.
					if ( handleDataError ) {
						handleDataError( error );
					}
				} else if ( isDataZero( returnedData, datapoint ) ) { // No data error, next check for zero data.
					// If we have a `handleDataError` call it without any parameters (indicating empty data).
					if ( handleDataError ) {
						handleDataError( error );
					}

					// Set a zeroData state on the Component.
					this.setState( { zeroData: true } );
				} else if ( handleDataSuccess ) {
					// Success! `handleDataSuccess` will be called (ie. not error or zero).
					handleDataSuccess();
				}

				// Resolve the returned data my setting state on the Component.
				this.setState( {
					requestData,
					data: returnedData,
					datapoint,
					module: identifier,
				} );
			};

			// Resolve all selectedData.
			each( selectData, ( data ) => {
				// Handle single contexts, or arrays of contexts.
				if ( isArray( data.context ) ) {
					each( data.context, ( acontext ) => {
						/**
						 * Request data for the context.
						 */
						addFilter( `googlesitekit.module${ acontext }DataRequest`,
							`googlesitekit.data${ acontext }`, ( moduleData ) => {
								data.callback = ( returnedData ) => {
									handleReturnedData( returnedData, data );
								};
								moduleData.push( data );
								return moduleData;
							} );
					} );
				} else {
					/**
					 * Request data for the context.
					 */
					addFilter( `googlesitekit.module${ data.context }DataRequest`,
						`googlesitekit.data${ data.context }`, ( moduleData ) => {
							data.callback = ( returnedData ) => {
								handleReturnedData( returnedData, data );
							};
							moduleData.push( data );
							return moduleData;
						} );
				}
			} );
		}

		render() {
			const {
				data,
				datapoint,
				module,
				zeroData,
				error,
				requestData,
			} = this.state;

			// Render the loading component until we have data.
			if ( ! data ) {
				return loadingComponent;
			}

			const moduleName = module ? googlesitekit.modules[ module ].name : __( 'Site Kit', 'google-site-kit' );

			// If module is active but setup not complete.
			if ( module && googlesitekit.modules[ module ].active && ! googlesitekit.modules[ module ].setupComplete ) {
				return getSetupIncompleteComponent( module, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// If we have an error, display the DataErrorComponent.
			if ( error ) {
				return ( 'string' !== typeof error ) ? error : getDataErrorComponent( moduleName, error, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// If we have zeroData, display the NoDataComponent.
			if ( zeroData ) {
				return getNoDataComponent( moduleName, layoutOptions.inGrid, layoutOptions.fullWidth, layoutOptions.createGrid );
			}

			// Render the Component when we have data, passing the datapoint.
			return (
				<DataDependentComponent
					data={ data }
					datapoint={ datapoint }
					requestData={ requestData }
					{ ...this.props }
				/>
			);
		}
	};
};

export default withData;
<|endoftext|>


<commit_before>const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const mongoOp = require('./model/mongo')
const router = express.Router()
const cookieParser = require('cookie-parser')
const twilioRoute = require('./src/twilio.route')
const externalRoute = require('./external/external.route')
const logger = require('winston')

mongoOp.intialize()

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({'extended': false}))

app.use(cookieParser())

app.set('port', (process.env.PORT || 5000))

router.get('/', (req, res) => {
  res.json({'error': false, 'message': 'available'})
})

router.route('/twilio')
  .get((request, response) => {
    twilioRoute.route(request, response)
  })

router.route('/api')
  .get((request, response) => {
    externalRoute.route(request, response)
  })

app.use('/', router)

app.listen(app.get('port'), () => {
  logger.log('info', '*** Node app is running on port', app.get('port'))
})
<commit_msg>continue testing<commit_after>const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const mongoOp = require('./model/mongo')
const router = express.Router()
const cookieParser = require('cookie-parser')
const twilioRoute = require('./src/twilio.route')
const externalRoute = require('./external/external.route')
const logger = require('winston')

mongoOp.intialize()

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({'extended': false}))

app.use(cookieParser())

app.set('port', (process.env.PORT || 5000))

router.get('/', (req, res) => {
  res.json({'error': false, 'message': 'available'})
})

router.route('/twilio')
  .get((request, response) => {
    twilioRoute.route(request, response)
  })

router.route('/api/*')
  .get((request, response) => {
    externalRoute.route(request, response)
  })

app.use('/', router)

app.listen(app.get('port'), () => {
  logger.log('info', '*** Node app is running on port', app.get('port'))
})
<|endoftext|>


<commit_before>'use-strict';

require('isomorphic-fetch')
const cheerio = require('cheerio')
const Url = require('url')
const Queue = require('promise-queue')
const defaultOptions = require('./defaultOptions.js')

function run(opt, callback) {
  const options = Object.assign({}, defaultOptions, opt)
  const queue = new Queue(options.concurrency, options.maxQueue)
  // Main data structure.
  const output = {
    pages: {
      internal: {},
      external: {},
    },
    stats: {
      startTime: Date.now(),
      endTime: null,
      totalTime: null,
    }
  }
  const requests = {}
  var pageCount = 0

  return new Promise(function(resolve, reject) {
    // Add start urls to queue.
    if (options.startUrls && options.startUrls.length) {
      options.startUrls.forEach(function(startUrl) {
        queue.add(function() {
          return fetchUrl(startUrl, true)
        }).then(checkQueue).catch(function(e) {
          console.error(e)
        })
      })
    }

    // Fetch a url
    function fetchUrl(url, internal) {
      const obj = {}
      pageCount++
      if (pageCount > options.maxPages) {
        throw new Error('Error: Maximum number of pages reached.')
      }
      url = url.split('#')[0]

      return new Promise(function(res, rej) {
        // Skip mailto and ftp links.
        if (url.indexOf('mailto:') === 0 || url.indexOf('ftp:') === 0) {
          res(output)
          return
        }
        setTimeout(function() {
          requests[url] = {}
          if (options.preFetchCallback) {
            options.preFetchCallback({
              url: url,
              requests: requests,
              output: output,
            })
          }
          requests[url].startTime = Date.now()
          fetch(url, options.fetchOptions)
            .then(function(r) {
              requests[url].endTime = Date.now()
              requests[url].elapsedTime = requests[url].endTime - requests[url].startTime
              // Add request stuff to data object.
              obj.fetchTime = requests[url].elapsedTime
              obj.status = getHeader('last-modified', r)
              obj.contentLength = getHeader('content-length', r)
              return r.text()
            }).then(function(d) {
              const $ = cheerio.load(d)
              const links = $('a')

              // Build `output` object.
              obj.linkCount = links.length
              obj.url = url
              obj.isInternal = internal

              // This sets the data key on `output`.
              const where = internal ? 'internal' : 'external'
              output.pages[where][url] = obj

              if (internal) {
                links.each((i, link) => {
                  processLink(i, link, url)
                })
              }

              if (options.postFetchCallback) {
                options.postFetchCallback({
                  url: url,
                  requests: requests,
                  output: output,
                })
              }

              res(output)
            }).catch(function(e) {
              console.error(e)
            })
        }, options.delay)
      })
    }

    function processLink(i, link, url) {
      const href = link.attribs.href
      if (href) {
        const fullUrl = Url.resolve(url, href).split('#')[0]
        if (!requests[fullUrl]) {
          const fullUrlInternal = isInternal(url, fullUrl)
          if (fullUrlInternal || options.includeExternal) {
            if (fullUrlInternal || options.fetchExternal) {
              queue.add(function() {
                return fetchUrl(fullUrl, fullUrlInternal)
              }).then(checkQueue).catch(function(e) {
                if (callback) {
                  callback(e)
                }
                reject(e)
                console.error(e)
              })
            } else {
              output.pages.external[fullUrl] = {}
            }
          }
        }
      }
    }

    function checkQueue(data) {
      // Final result.
      if (queue.pendingPromises === 0) {
        output.stats.endTime = Date.now()
        output.stats.totalTime = output.stats.endTime - output.stats.startTime

        if (callback) {
          callback(null, output)
        }
        resolve(output)
      }
      return data
    }
  })
}

function getHeader(header, response) {
  return response.headers
    && response.headers._headers
    && response.headers._headers[header]
    && response.headers._headers[header][0]
}

function isInternal(baseUrl, url) {
  const hostname = Url.parse(baseUrl).hostname
  return hostname === Url.parse(url).hostname
}

module.exports = {
  run: run
}
<commit_msg>pass promise to callbacks<commit_after>'use-strict';

require('isomorphic-fetch')
const cheerio = require('cheerio')
const Url = require('url')
const Queue = require('promise-queue')
const defaultOptions = require('./defaultOptions.js')

function run(opt, callback) {
  const options = Object.assign({}, defaultOptions, opt)
  const queue = new Queue(options.concurrency, options.maxQueue)
  // Main data structure.
  const output = {
    pages: {
      internal: {},
      external: {},
    },
    stats: {
      startTime: Date.now(),
      endTime: null,
      totalTime: null,
    }
  }
  const requests = {}
  var pageCount = 0

  return new Promise(function(resolve, reject) {
    // Add start urls to queue.
    if (options.startUrls && options.startUrls.length) {
      options.startUrls.forEach(function(startUrl) {
        queue.add(function() {
          return fetchUrl(startUrl, true)
        }).then(checkQueue).catch(function(e) {
          console.error(e)
        })
      })
    }

    // Fetch a url
    function fetchUrl(url, internal) {
      const obj = {}
      pageCount++
      if (pageCount > options.maxPages) {
        throw new Error('Error: Maximum number of pages reached.')
      }
      url = url.split('#')[0]

      const p = new Promise(function(res, rej) {
        // Skip mailto and ftp links.
        if (url.indexOf('mailto:') === 0 || url.indexOf('ftp:') === 0) {
          res(output)
          return
        }
        setTimeout(function() {
          requests[url] = {}
          if (options.preFetchCallback) {
            options.preFetchCallback({
              output: output,
              promise: p,
              requests: requests,
              url: url,
            })
          }
          requests[url].startTime = Date.now()
          fetch(url, options.fetchOptions)
            .then(function(r) {
              requests[url].endTime = Date.now()
              requests[url].elapsedTime = requests[url].endTime - requests[url].startTime
              // Add request stuff to data object.
              obj.fetchTime = requests[url].elapsedTime
              obj.status = getHeader('last-modified', r)
              obj.contentLength = getHeader('content-length', r)
              return r.text()
            }).then(function(d) {
              const $ = cheerio.load(d)
              const links = $('a')

              // Build `output` object.
              obj.linkCount = links.length
              obj.url = url
              obj.isInternal = internal

              // This sets the data key on `output`.
              const where = internal ? 'internal' : 'external'
              output.pages[where][url] = obj

              if (internal) {
                links.each((i, link) => {
                  processLink(i, link, url)
                })
              }

              if (options.postFetchCallback) {
                options.postFetchCallback({
                  output: output,
                  promise: p,
                  requests: requests,
                  url: url,
                })
              }

              res(output)
            }).catch(function(e) {
              console.error(e)
            })
        }, options.delay)
      })

      return p
    }

    function processLink(i, link, url) {
      const href = link.attribs.href
      if (href) {
        const fullUrl = Url.resolve(url, href).split('#')[0]
        if (!requests[fullUrl]) {
          const fullUrlInternal = isInternal(url, fullUrl)
          if (fullUrlInternal || options.includeExternal) {
            if (fullUrlInternal || options.fetchExternal) {
              queue.add(function() {
                return fetchUrl(fullUrl, fullUrlInternal)
              }).then(checkQueue).catch(function(e) {
                if (callback) {
                  callback(e)
                }
                reject(e)
                console.error(e)
              })
            } else {
              output.pages.external[fullUrl] = {}
            }
          }
        }
      }
    }

    function checkQueue(data) {
      // Final result.
      if (queue.pendingPromises === 0) {
        output.stats.endTime = Date.now()
        output.stats.totalTime = output.stats.endTime - output.stats.startTime

        if (callback) {
          callback(null, output)
        }
        resolve(output)
      }
      return data
    }
  })
}

function getHeader(header, response) {
  return response.headers
    && response.headers._headers
    && response.headers._headers[header]
    && response.headers._headers[header][0]
}

function isInternal(baseUrl, url) {
  const hostname = Url.parse(baseUrl).hostname
  return hostname === Url.parse(url).hostname
}

module.exports = {
  run: run
}
<|endoftext|>


<commit_before>let app;
let listingURL = {};
let listingB64 = null;
let isAutomated = false;
let block = false;
let waiting = false;
let hackProgress = 0;
let wordLoop = null;
let minerLoop = null;
let upgradeLoop = null;
let myBT = 0;
let botWindow;
let isDragReady = false;
let dragOffset = {
	x: 0,
	y: 0
};
let minerStatus = [
	{
		name: "shop-basic-miner",
		value: 0
	},
	{
		name: "shop-advanced-miner",
		value: 0
	},
	{
		name: "shop-mining-drill",
		value: 0
	},
	{
		name: "shop-data-center",
		value: 0
	},
	{
		name: "shop-bot-net",
		value: 0
	},
	{
		name: "shop-quantum-server",
		value: 0
	}
];
let maxStats = {
	charge: 30,
	strength: 4,
	regen: 10
};
const firewalls = ["1", "2", "3"];
const ocrApiKey = "XXX";
const db = "https://raw.githubusercontent.com/snollygolly/sourceio-automation/master/db.json";
let message = "papa bless, one love /r/javascript";
let wordFreq = 1250;
let mineFreq = 3000;
let blockFreq = 5000;
let upgradeFreq = 7500;
let minerLevel = 20;
let playerToAttack = 0;

app = {
	start: () => {
		$.get(db).done((data) => {
			listingB64 = JSON.parse(data);
			app.automate();
		});
	},

	exportListing: () => {
		log(JSON.stringify(listingURL, null, 2));
	},

	automate: () => {
		// first check the windows are open, and open them if they aren't
		if ($("#player-list").is(":visible") === false) {
			log("* Target list must be open");
			$("#desktop-list").children("img").click();
		}
		if ($("#window-shop").is(":visible") === false) {
			log("* Black market must be open");
			$("#desktop-shop").children("img").click();
			$("#desktop-miner").children("img").click();
		}
		if ($("#window-computer").is(":visible") === false) {
			log("* My computer must be open");
			$("#desktop-computer").children("img").click();
		}
		if ($("#window-bot").is(":visible") === false) {
			log("* Opening bot window");
			app.gui();
		}
		isAutomated = true;
		// start by getting the first target in the list
		const targetName = $("#player-list").children("tr").eq(playerToAttack)[0].innerText;
		log(`. Now attacking ${targetName}`);
		// click it, and then hack, and then port b
		$("#player-list").children("tr").eq(playerToAttack)[0].click();
		$("#window-other-button").click();
		// do a check for money
		const portStyle = $(`#window-other-port${firewalls[0]}`).attr("style");
		if (portStyle.indexOf("opacity: 1") === -1) {
			// this port costs too much, let's wait a bit
			log("* Hack too expensive, waiting");
			setTimeout(app.automate, blockFreq);
			return;
		}
		const portNumber = getRandomInt(1,3);
		$(`#window-other-port${portNumber}`).click();
		// handle upgrades
		app.loops.upgrade();
		// start the loop that does the guessing
		//wordLoop = setInterval(app.loops.word, wordFreq);
		wordLoop = setInterval(app.loops.word, wordFreq);
		// start the loop for btc monitoring
		minerLoop = setInterval(app.loops.miner, mineFreq);
		// start the loop for upgrades
		//upgradeLoop = setInterval(app.loops.upgrade, upgradeFreq);
	},

	gui: () => {
        //check if bot window has been appended already
        if ($("#window-bot").length > 0) {
            $("#window-bot").show();
        }
        else {
            //Change windowWidth and windowHeight to change the bot's window size
            let windowWidth = "320px";
            let windowHeight = "350px";
            let botHTML =
            "
" +
                "
Source.io Bot" +
                    "" +
                        "" +
                    "" +
                    "
" +
                    "
" +
                        "
Restart Bot
" +
                        "
Stop Bot
" +
						"Hack speed:" +
							"<input type='text' id='hack-speed-input' class='input-form' onkeypress='return event.charCode >= 48 && event.charCode <= 57' style='width:50px;margin:0px 0px 15px 5px' value=" + wordFreq +
							">(ms)" +
						"" +
                        "
This script is on Github!
" +
                    "
" +
                "
" +
            "
";

            $(".window-wrapper").append(botHTML);

            //bind functions to the gui's buttons
            $("#bot-title > span.window-close-style").on("click", () => {
                $("#window-bot").hide();
            });

            $("#restart-button").on("click", () => {
                app.restart();
            });

            $("#stop-button").on("click", () => {
                app.stop();
            });

            $("#github-button").on("click", () => {
                window.open("https://github.com/snollygolly/sourceio-automation")
            });

			$("#hack-speed-input").change(() => {
				wordFreq = $("#hack-speed-input").val();
			});
            //make the bot window draggable
            botWindow = ("#window-bot");

            $(document).on("mousedown", botWindow, (e) => {
                isDragReady = true;
                dragOffset.x = e.pageX - $(botWindow).position().left;
                dragOffset.y = e.pageY - $(botWindow).position().top;
            });

            $(document).on("mouseup", botWindow, (e) => {
                isDragReady = false;
            });

            $(document).on("mousemove", (e) => {
                if (isDragReady) {
                    $(botWindow).css("top", (e.pageY - dragOffset.y) + "px");
                    $(botWindow).css("left", (e.pageX - dragOffset.x) + "px");
                }
            });
        }
    },

	loops: {
		word: () => {
			if (block === true) {
				return;
			}
			if ($("#targetmessage-input").is(":visible") === true) {
				// we're done!
				$("#targetmessage-input").val(message);
				$("#targetmessage-button-send").click();
				app.restart();
				return;
			}
			// if we're waiting on the progress bar to move...
			if (waiting === true) {
				const newHackProgress = parseHackProgress($("#progressbar-firewall-amount").attr("style"));
				// check to see if it's new
				if (hackProgress === newHackProgress) {
					// the bar hasn't moved
					log("* Progress bar hasn't moved, waiting");
					// maybe the URLs have changed
					// the user must press "restart bot"
					listingURL = {};
					// TODO: make this an automatic process
					return;
				} else {
					// the bar has moved
					hackProgress = newHackProgress;
					waiting = false;
				}
			}
			// actually do the word stuff
			waiting = true;
			app.go();
		},
		miner: () => {
			// first, get the status of our miners
			for (const miner of minerStatus) {
				// set value
				miner.value = parseInt($(`#${miner.name}-amount`).text());
				// this is available to buy
				if ($(`#${miner.name}`).attr("style") === "opacity: 1;") {
					if (miner.value < minerLevel) {
						// we should buy this
						$(`#${miner.name}`).click();
					}
				}
			}
		},
		upgrade: () => {
			myBT = parseInt($("#window-my-coinamount").text());
			// if the back button is visible, we're on a page, let's back out
			if ($("#window-firewall-pagebutton").is(":visible") === true) {
				$("#window-firewall-pagebutton").click();
			}
			// take it off the top
			const firewall = firewalls.shift()
			firewalls.push(firewall);
			// select the firewall
			log(`. Handling upgrades to firewall ${firewall}`);
			$(`#window-firewall-part${firewall}`).click();
			// get stats
			const stats = {
				charge: parseInt($("#shop-max-charges").text()),
				strength: parseInt($("#shop-strength").text()),
				regen: parseInt($("#shop-regen").text()),
			};
			// start checking prices, start with strength
			if (stats.strength < maxStats.strength) {
				log(". Strength isn't maxed");
				const strengthPrice = parseInt($("#shop-firewall-difficulty-value").text());
				if (strengthPrice < myBT) {
					log(". Buying strength");
					$("#shop-firewall-difficulty").click();
					return;
				}
			}
			// check max charges
			if (stats.charge < maxStats.charge) {
				log(". Charge isn't maxed");
				const chargePrice = parseInt($("#shop-firewall-max_charge10-value").text());
				if (chargePrice < myBT) {
					$("#shop-firewall-max_charge10").click();
					log(". Buying charge");
					return;
				}
			}
			// check regen
			if (stats.regen < maxStats.regen) {
				log(". Regen isn't maxed");
				const regenPrice = parseInt($("#shop-firewall-regen-value").text());
				if (regenPrice < myBT) {
					$("#shop-firewall-regen").click();
					log(". Buying regen");
					return;
				}
			}
			// nothing matched, let's go back
			if ($("#window-firewall-pagebutton").is(":visible") === true) {
				$("#window-firewall-pagebutton").click();
			}
		},
	},

	restart: () => {
		app.stop();
		app.automate();
	},

	stop: () => {
		if (wordLoop === null && minerLoop === null && upgradeLoop === null) {
			log("! No loops to stop");
			return;
		}
		isAutomated = false;
		block = false;
		waiting = false;
		clearInterval(wordLoop);
		wordLoop = null;
		clearInterval(minerLoop);
		minerLoop = null;
		clearInterval(upgradeLoop);
		upgradeLoop = null;
		log("* Stopped loops");
	},

	go: () => {
		const wordLink = $(".tool-type-img").prop("src");
		if (!wordLink.endsWith("s0urce.io/client/img/words/template.png")) {
			if (listingB64.hasOwnProperty(wordLink) === true) {
				const word = listingB64[wordLink];
				log(`. Found word: [${word}]`);
				app.submit(word);
				return;
			}
			else {
				toDataURL(wordLink)
				   .then(dataUrl => {
					const hash = dataUrl.hashCode().toString();
					if (listingB64.hasOwnProperty(hash) === true) {
						const word = listingB64[hash];
						app.learn(word);
						return;
					}
					log("* Not seen, trying OCR...");
					app.ocr(wordLink);
				});
			}
		}
		else {
			log("* Can't find the word link...");
			app.restart();
		}
	},

	submit: (word) => {
		$("#tool-type-word").val(word);
		$("#tool-type-word").submit();
	},

	learn: (word) => {
		const wordLink = $(".tool-type-img").prop("src");
		listingURL[wordLink] = word;
		app.submit(word);
	},

	ocr: (url) => {
		block = true;
		$.post("http://api.ocr.space/parse/image", {
			apikey: ocrApiKey,
			language: "eng",
			url: url
		}).done((data) => {
			const word = String(data["ParsedResults"][0]["ParsedText"]).trim().toLowerCase().split(" ").join("");
			if (word.length > 2) {
				log(`. Got data: [${word}]`);
				$("#tool-type-word").val(word);
				if (isAutomated === true) {
					app.learn(word);
					block = false;
				}
			} else {
				log("* OCR failed");
				app.restart();
			}
		});
	}
};

function parseHackProgress(progress) {
	// remove the %;
	const newProgress = progress.slice(0, -2);
	const newProgressParts = newProgress.split("width: ");
	return parseInt(newProgressParts.pop());
}

function getRandomInt(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

String.prototype.hashCode = function() {
	let hash = 0;
	if (this.length == 0) {
		return hash;
	}
	for (let i = 0; i < this.length; i++) {
		let c = this.charCodeAt(i);
		hash = ((hash<<5) - hash) + c;
		hash = hash & hash;
	}
	return hash;
}

const toDataURL = url => fetch(url)
	.then(response => response.blob())
	.then(blob => new Promise((resolve, reject) => {
		const reader = new FileReader();
		reader.onloadend = () => resolve(reader.result);
		reader.onerror = reject;
		reader.readAsDataURL(blob);
	}));

function log(message) {
	console.log(`:: ${message}`);
}
<commit_msg>Fixed variable listing<commit_after>let app;
let listingURL = {};
let listingB64 = null;
let isAutomated = false;
let block = false;
let waiting = false;
let hackProgress = 0;
let wordLoop = null;
let minerLoop = null;
let upgradeLoop = null;
let myBT = 0;
let botWindow;
let isDragReady = false;
let dragOffset = {
	x: 0,
	y: 0
};
let minerStatus = [
	{
		name: "shop-basic-miner",
		value: 0
	},
	{
		name: "shop-advanced-miner",
		value: 0
	},
	{
		name: "shop-mining-drill",
		value: 0
	},
	{
		name: "shop-data-center",
		value: 0
	},
	{
		name: "shop-bot-net",
		value: 0
	},
	{
		name: "shop-quantum-server",
		value: 0
	}
];
let maxStats = {
	charge: 30,
	strength: 4,
	regen: 10
};
const firewalls = ["1", "2", "3"];
const ocrApiKey = "XXX";
const db = "https://raw.githubusercontent.com/snollygolly/sourceio-automation/master/db.json";
let message = "papa bless, one love /r/javascript";
let wordFreq = 1250;
let mineFreq = 3000;
let blockFreq = 5000;
let upgradeFreq = 7500;
let minerLevel = 20;
let playerToAttack = 0;

app = {
	start: () => {
		$.get(db).done((data) => {
			listingB64 = JSON.parse(data);
			app.automate();
		});
	},

	exportListing: () => {
		log(JSON.stringify(listingURL, null, 2));
	},

	automate: () => {
		// first check the windows are open, and open them if they aren't
		if ($("#player-list").is(":visible") === false) {
			log("* Target list must be open");
			$("#desktop-list").children("img").click();
		}
		if ($("#window-shop").is(":visible") === false) {
			log("* Black market must be open");
			$("#desktop-shop").children("img").click();
			$("#desktop-miner").children("img").click();
		}
		if ($("#window-computer").is(":visible") === false) {
			log("* My computer must be open");
			$("#desktop-computer").children("img").click();
		}
		if ($("#window-bot").is(":visible") === false) {
			log("* Opening bot window");
			app.gui();
		}
		isAutomated = true;
		// start by getting the first target in the list
		const targetName = $("#player-list").children("tr").eq(playerToAttack)[0].innerText;
		log(`. Now attacking ${targetName}`);
		// click it, and then hack, and then port b
		$("#player-list").children("tr").eq(playerToAttack)[0].click();
		$("#window-other-button").click();
		// do a check for money
		const portStyle = $(`#window-other-port${firewalls[0]}`).attr("style");
		if (portStyle.indexOf("opacity: 1") === -1) {
			// this port costs too much, let's wait a bit
			log("* Hack too expensive, waiting");
			setTimeout(app.automate, blockFreq);
			return;
		}
		const portNumber = getRandomInt(1,3);
		$(`#window-other-port${portNumber}`).click();
		// handle upgrades
		app.loops.upgrade();
		// start the loop that does the guessing
		//wordLoop = setInterval(app.loops.word, wordFreq);
		wordLoop = setInterval(app.loops.word, wordFreq);
		// start the loop for btc monitoring
		minerLoop = setInterval(app.loops.miner, mineFreq);
		// start the loop for upgrades
		//upgradeLoop = setInterval(app.loops.upgrade, upgradeFreq);
	},

	gui: () => {
        //check if bot window has been appended already
        if ($("#window-bot").length > 0) {
            $("#window-bot").show();
        }
        else {
            //Change windowWidth and windowHeight to change the bot's window size
            let windowWidth = "320px";
            let windowHeight = "350px";
            let botHTML =
            "
" +
                "
Source.io Bot" +
                    "" +
                        "" +
                    "" +
                    "
" +
                    "
" +
                        "
Restart Bot
" +
                        "
Stop Bot
" +
						"Hack speed:" +
							"<input type='text' id='hack-speed-input' class='input-form' onkeypress='return event.charCode >= 48 && event.charCode <= 57' style='width:50px;margin:0px 0px 15px 5px' value=" + wordFreq +
							">(ms)" +
						"" +
                        "
This script is on Github!
" +
                    "
" +
                "
" +
            "
";

            $(".window-wrapper").append(botHTML);

            //bind functions to the gui's buttons
            $("#bot-title > span.window-close-style").on("click", () => {
                $("#window-bot").hide();
            });

            $("#restart-button").on("click", () => {
                app.restart();
            });

            $("#stop-button").on("click", () => {
                app.stop();
            });

            $("#github-button").on("click", () => {
                window.open("https://github.com/snollygolly/sourceio-automation")
            });

			$("#hack-speed-input").change(() => {
				wordFreq = $("#hack-speed-input").val();
			});
            //make the bot window draggable
            botWindow = ("#window-bot");

            $(document).on("mousedown", botWindow, (e) => {
                isDragReady = true;
                dragOffset.x = e.pageX - $(botWindow).position().left;
                dragOffset.y = e.pageY - $(botWindow).position().top;
            });

            $(document).on("mouseup", botWindow, (e) => {
                isDragReady = false;
            });

            $(document).on("mousemove", (e) => {
                if (isDragReady) {
                    $(botWindow).css("top", (e.pageY - dragOffset.y) + "px");
                    $(botWindow).css("left", (e.pageX - dragOffset.x) + "px");
                }
            });
        }
    },

	loops: {
		word: () => {
			if (block === true) {
				return;
			}
			if ($("#targetmessage-input").is(":visible") === true) {
				// we're done!
				$("#targetmessage-input").val(message);
				$("#targetmessage-button-send").click();
				app.restart();
				return;
			}
			// if we're waiting on the progress bar to move...
			if (waiting === true) {
				const newHackProgress = parseHackProgress($("#progressbar-firewall-amount").attr("style"));
				// check to see if it's new
				if (hackProgress === newHackProgress) {
					// the bar hasn't moved
					log("* Progress bar hasn't moved, waiting");
					// maybe the URLs have changed
					// the user must press "restart bot"
					listingURL = {};
					// TODO: make this an automatic process
					return;
				} else {
					// the bar has moved
					hackProgress = newHackProgress;
					waiting = false;
				}
			}
			// actually do the word stuff
			waiting = true;
			app.go();
		},
		miner: () => {
			// first, get the status of our miners
			for (const miner of minerStatus) {
				// set value
				miner.value = parseInt($(`#${miner.name}-amount`).text());
				// this is available to buy
				if ($(`#${miner.name}`).attr("style") === "opacity: 1;") {
					if (miner.value < minerLevel) {
						// we should buy this
						$(`#${miner.name}`).click();
					}
				}
			}
		},
		upgrade: () => {
			myBT = parseInt($("#window-my-coinamount").text());
			// if the back button is visible, we're on a page, let's back out
			if ($("#window-firewall-pagebutton").is(":visible") === true) {
				$("#window-firewall-pagebutton").click();
			}
			// take it off the top
			const firewall = firewalls.shift()
			firewalls.push(firewall);
			// select the firewall
			log(`. Handling upgrades to firewall ${firewall}`);
			$(`#window-firewall-part${firewall}`).click();
			// get stats
			const stats = {
				charge: parseInt($("#shop-max-charges").text()),
				strength: parseInt($("#shop-strength").text()),
				regen: parseInt($("#shop-regen").text()),
			};
			// start checking prices, start with strength
			if (stats.strength < maxStats.strength) {
				log(". Strength isn't maxed");
				const strengthPrice = parseInt($("#shop-firewall-difficulty-value").text());
				if (strengthPrice < myBT) {
					log(". Buying strength");
					$("#shop-firewall-difficulty").click();
					return;
				}
			}
			// check max charges
			if (stats.charge < maxStats.charge) {
				log(". Charge isn't maxed");
				const chargePrice = parseInt($("#shop-firewall-max_charge10-value").text());
				if (chargePrice < myBT) {
					$("#shop-firewall-max_charge10").click();
					log(". Buying charge");
					return;
				}
			}
			// check regen
			if (stats.regen < maxStats.regen) {
				log(". Regen isn't maxed");
				const regenPrice = parseInt($("#shop-firewall-regen-value").text());
				if (regenPrice < myBT) {
					$("#shop-firewall-regen").click();
					log(". Buying regen");
					return;
				}
			}
			// nothing matched, let's go back
			if ($("#window-firewall-pagebutton").is(":visible") === true) {
				$("#window-firewall-pagebutton").click();
			}
		},
	},

	restart: () => {
		app.stop();
		app.automate();
	},

	stop: () => {
		if (wordLoop === null && minerLoop === null && upgradeLoop === null) {
			log("! No loops to stop");
			return;
		}
		isAutomated = false;
		block = false;
		waiting = false;
		clearInterval(wordLoop);
		wordLoop = null;
		clearInterval(minerLoop);
		minerLoop = null;
		clearInterval(upgradeLoop);
		upgradeLoop = null;
		log("* Stopped loops");
	},

	go: () => {
		const wordLink = $(".tool-type-img").prop("src");
		if (!wordLink.endsWith("s0urce.io/client/img/words/template.png")) {
			if (listingURL.hasOwnProperty(wordLink) === true) {
				const word = listingURL[wordLink];
				log(`. Found word (URL): [${word}]`);
				app.submit(word);
				return;
			}
			else {
				toDataURL(wordLink)
				   .then(dataUrl => {
					const hash = dataUrl.hashCode().toString();
					if (listingB64.hasOwnProperty(hash) === true) {
						const word = listingB64[hash];
						log(`. Found word (B64): [${word}]`);
						app.learn(word);
						return;
					}
					log("* Not seen, trying OCR...");
					app.ocr(wordLink);
				});
			}
		}
		else {
			log("* Can't find the word link...");
			app.restart();
		}
	},

	submit: (word) => {
		$("#tool-type-word").val(word);
		$("#tool-type-word").submit();
	},

	learn: (word) => {
		const wordLink = $(".tool-type-img").prop("src");
		listingURL[wordLink] = word;
		app.submit(word);
	},

	ocr: (url) => {
		block = true;
		$.post("http://api.ocr.space/parse/image", {
			apikey: ocrApiKey,
			language: "eng",
			url: url
		}).done((data) => {
			const word = String(data["ParsedResults"][0]["ParsedText"]).trim().toLowerCase().split(" ").join("");
			if (word.length > 2) {
				log(`. Got data: [${word}]`);
				$("#tool-type-word").val(word);
				if (isAutomated === true) {
					app.learn(word);
					block = false;
				}
			} else {
				log("* OCR failed");
				app.restart();
			}
		});
	}
};

function parseHackProgress(progress) {
	// remove the %;
	const newProgress = progress.slice(0, -2);
	const newProgressParts = newProgress.split("width: ");
	return parseInt(newProgressParts.pop());
}

function getRandomInt(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

String.prototype.hashCode = function() {
	let hash = 0;
	if (this.length == 0) {
		return hash;
	}
	for (let i = 0; i < this.length; i++) {
		let c = this.charCodeAt(i);
		hash = ((hash<<5) - hash) + c;
		hash = hash & hash;
	}
	return hash;
}

const toDataURL = url => fetch(url)
	.then(response => response.blob())
	.then(blob => new Promise((resolve, reject) => {
		const reader = new FileReader();
		reader.onloadend = () => resolve(reader.result);
		reader.onerror = reject;
		reader.readAsDataURL(blob);
	}));

function log(message) {
	console.log(`:: ${message}`);
}
<|endoftext|>