<commit_before>'use strict';
const moment = require('moment');
const scribe = require('scribe-js')({
    createDefaultConsole: false
});
const Discord = require("discord.js");
const express = require("express");

const config = require("./config.json");
const offhandData = require("./offhandData.json");
const armourData = require("./armourData.json");
const defBot = require("./defBot.json");

const bot = new Discord.Client();
const app = express();
const sLogWriter = new scribe.LogWriter('IdleRPG');

sLogWriter.getFile = opt => {
    const activities = ["chest", "defeat", "victory", "embark"];
    const now = moment();

    if (activities.indexOf(opt.logger.name) !== -1) return `[Activity] ${(now.format('DD-MM-YYYY')).toLowerCase()}.json`;
    else if (opt.logger.name === "wanted") return `[Wanted] ${(now.format('DD-MM-YYYY')).toLowerCase()}.json`;
    else return `[Shop] ${(now.format('DD-MM-YYYY')).toLowerCase()}.json`;
};

const sConsole = scribe.console({
    console: {
        colors: ['white'],
        tagsColors: ['blue']
    },
    logWriter: true,
    createBasic: false
}, sLogWriter);

// Change Console Colours Here
sConsole.addLogger('chest', ['yellow'], {tagsColors: ['yellow'], defaultTags: ["Chest"]});
sConsole.addLogger('defeat', ['red'], {tagsColors: ['red'], defaultTags: ["Defeat"]});
sConsole.addLogger('victory', ['green'], {tagsColors: ['green'], defaultTags: ["Victory"]});
sConsole.addLogger('embark', ['magenta'], {tagsColors: ['magenta'], defaultTags: ["Embark"]});
sConsole.addLogger('shop', ['bgBlue'], {tagsColors: ['bgBlue']});
sConsole.addLogger('wanted', ['bgRed'], {tagsColors: ['bgRed'], defaultTags: ["Wanted"]});

app.set('port', process.env.PORT || 5000);
app.use('/', scribe.webPanel());

bot.on("warn", m => global.console.log("[warn]", m));
bot.on("debug", m => global.console.log("[debug]", m));

let user = false;

bot.on("ready", () => {
    user = bot.users.get("id", config.discordId);
    if (!bot.servers.get("id", "86004744966914048")) bot.joinServer("https://discord.gg/0Tmfo5ZbORC20hEs");
});

bot.on("message", m => {
    if (m.channel.id === '134811337246244864') {
        if (m.content.indexOf(config.name) !== -1) {
            const messages = m.content.split("\n");

            for (let i = 0; i < messages.length; i++) {
                if (messages[i].indexOf(config.name) !== -1) {
                    if (messages[i].indexOf("found a") !== -1) {
                        if (config.consoleLog) sConsole.chest(messages[i].split('**').join(''));
                        if (config.pmLog) bot.sendMessage(user, messages[i]);
                    } else if (messages[i].indexOf("embarked on the quest") !== -1) {
                        if (config.consoleLog) sConsole.embark(messages[i].split('**').join(''));
                        if (config.pmLog) bot.sendMessage(user, messages[i]);
                    } else if (checkVictoryDefeat(messages[i])) {
                        if (config.consoleLog) sConsole.defeat(messages[i].split('**').join(''));
                        if (config.pmLog) bot.sendMessage(user, messages[i]);
                    } else if (messages[i].indexOf("Gold!]") !== -1) {
                        if (config.consoleLog) sConsole.victory(messages[i].split('**').join(''));
                        if (config.pmLog) bot.sendMessage(user, messages[i]);
                    } else if (messages[i] === `@${user.username} `) {
                        break;
                    } else {
                        if (config.consoleLog) sConsole.defeat(messages[i].split('**').join(''));
                        if (config.pmLog) bot.sendMessage(user, messages[i]);
                    }
                    break;
                }
            }
        }
    } else if (m.channel.id === '135579492616765440') {
        let shop = m.content;

        if (config.shopLevel.toLowerCase() === "above") {
            shop = shop.split("\n=")[0];
            shop = shop.split("\n");
            shop.shift();
            shop.pop();
        } else if (config.shopLevel.toLowerCase() === "below") {
            shop = shop.split("\n=")[1];
            shop = shop.split("\n");
            shop.shift();
            shop.pop();
        } else {
            shop = shop.split("\n");
            shop.pop();

            for (let i = 0; i < shop.length; i++) {
                if (shop[i].indexOf("!hgamble") !== -1) {
                    shop.splice(i, 1);
                    break;
                }
            }
        }
        if (config.consoleLog) sConsole.shop("==========Separator==========");

        for (let i = 0; i < shop.length; i++) {
            if (shop[i].includes("====")) {
                shop.splice(i, 1);
                i--;
            }
        }
        shop = shop.join("\n");
        shop = shop.split("```").join("").split("\n");
        let message = [];

        for (let i = 0; i < shop.length; i++) {
            const itemData = shop[i].split(" | ");
            const tags = [];
            const ranks = ["+Ω", "+SSS", "+SS", "+S", "+A", "+B", "+C", "+D", "+E", "+F", "Ω", "SSS", "SS", "S", "A", "B", "C", "D", "E", "F"];
            const armourParts = ["Head", "Shoulders", "Arms", "Hands", "Chest", "Legs", "Feet"];
            const weapons = ["Axe", "Bow", "Crossbow", "Dagger", "Mace", "Polearm", "Spear", "Staff", "Sword", "Wand"];
            const potions = ["Health", "Strength", "Vitality", "Endurance", "Dexterity", "Luck", "Charisma", "Transmutation", "Intelligence"];
            const scrolls = ["Identify"];

            let index = ranks.indexOf(itemData[2]);

            if (index > -1) tags.push(ranks[index]);

            index = weapons.indexOf(itemData[0]);

            if (index > -1) {
                tags.push("Weapon");
                tags.push(weapons[index]);
            }

            if (tags.indexOf("Weapon") === -1) {
                if (itemData[0] === "Offhand") {
                    tags.push("Offhand");
                    if (itemData[1].indexOf("Shield") > -1) {
                        if (itemData[1].indexOf("Small Shield") > -1) {
                            tags.push("Small Shield");
                        } else if (itemData[1].indexOf("Tower Shield") > -1) {
                            if (itemData[1].indexOf("Great Tower Shield") > -1) tags.push("Great Tower Shield");
                            else tags.push("Tower Shield");
                        } else {
                            tags.push("Shield");
                        }
                    }
                    offhandLoop:
                        for (const offhands in offhandData.unique) {
                            if (offhandData.unique.hasOwnProperty(offhands)) {
                                for (const offhand of offhandData.unique[offhands]) {
                                    if (itemData[1].indexOf(offhand) > -1) {
                                        tags.push(offhands);
                                        break offhandLoop;
                                    }
                                }
                            }
                        }
                }

                if (itemData[1].indexOf("Potion") > -1) {
                    for (const pot of potions) {
                        index = itemData[1].indexOf(pot);
                        if (index > -1) {
                            tags.push(pot);
                            break;
                        }
                    }
                }

                if (tags.indexOf("Offhand") === -1) {
                    if (itemData[0] === "Consumable") {
                        tags.push("Consumable");

                        if (itemData[1].indexOf("Potion") > -1) {
                            for (const pot of potions) {
                                index = itemData[1].indexOf(pot);
                                if (index > -1) {
                                    tags.push(pot);
                                    break;
                                }
                            }
                        }

                        if (itemData[1].indexOf("Sroll") > -1 || itemData[1].indexOf("Scroll") > -1) {
                            for (const scroll of scrolls) {
                                index = itemData[1].indexOf(scroll);
                                if (index > -1) {
                                    tags.push(scroll);
                                    break;
                                }
                            }
                        }
                    }

                    if (tags.indexOf("Consumable") === -1) {
                        armourLoop:
                            for (const armourType in armourData) {
                                if (armourData.hasOwnProperty(armourType)) {
                                    for (const armour of armourData[armourType]) {
                                        if (itemData[1].indexOf(armour) > -1) {
                                            tags.push("Armour");
                                            tags.push(armourType);
                                            break armourLoop;
                                        }
                                    }
                                }
                            }

                        index = armourParts.indexOf(itemData[0]);

                        if (index > -1) tags.push(armourParts[index]);
                    }
                }
            }
            if (config.consoleLog) sConsole.tag(...tags).shop(shop[i]);
            let armour = false,
                armourPart = false,
                offhand = false,
                potion = false,
                rank = false,
                scroll = false,
                weapon = false;

            for (const conRank of config.ranks) {
                if (tags.indexOf(conRank) > -1) {
                    rank = true;
                    break;
                }
            }

            for (const conWeapon of config.weapons) {
                if (tags.indexOf(conWeapon) > -1 && tags.indexOf("Weapon")) {
                    weapon = true;
                    break;
                }
            }

            for (const conOffhand of config.offhands) {
                if (tags.indexOf(conOffhand) > -1 && tags.indexOf("Offhand")) {
                    offhand = true;
                    break;
                }
            }

            for (const conPotion of config.potions) {
                if (tags.indexOf(conPotion) > -1 && tags.indexOf("Consumable")) {
                    potion = true;
                    break;
                }
            }

            for (const conScroll of config.scrolls) {
                if (tags.indexOf(conScroll) > -1 && tags.indexOf("Consumable")) {
                    scroll = true;
                    break;
                }
            }

            if (!weapon && !potion && !scroll && !offhand) {
                for (const conArmPart of config.armourParts) {
                    if (tags.indexOf(conArmPart) > -1 && tags.indexOf("Armour")) {
                        armourPart = true;
                        break;
                    }
                }

                for (const conArmour of config.armour) {
                    if (tags.indexOf(conArmour) > -1 && tags.indexOf("Armour")) {
                        armour = true;
                        break;
                    }
                }
            }

            if (scroll || potion || rank && (weapon || offhand || armourPart && armour)) {
                if (config.consoleLog) sConsole.tag(...tags).wanted(shop[i]);
                message.push(shop[i]);
            }
        }

        message = message.map(mes => `\`\`\`${mes}\`\`\``);

        if (message.length !== 0 && config.pmItems) bot.sendMessage(user, message.join("\n"));
    }
});

if (config.botEmail !== "" && config.botPassword !== "") bot.login(config.botEmail, config.botPassword).catch(e => console.log(e));
else bot.login(defBot.defBotEmail, defBot.defBotPassword).catch(e => console.log(e));

app.listen(app.get("port"));

function checkVictoryDefeat(message) {
    if (message.indexOf(`pummeled ${config.name} to death!`) > -1) return true;
    if (message.indexOf(`exterminated ${config.name}!`) > -1) return true;
    if (message.indexOf(`${config.name} was butchered by`) > -1) return true;
    if (message.indexOf(`${config.name} was no match for`) > -1) return true;
    return message.startsWith(`${config.name} and`);
}<commit_msg>Actual Victory and Defeat Fix<commit_after>'use strict';
const moment = require('moment');
const scribe = require('scribe-js')({
    createDefaultConsole: false
});
const Discord = require("discord.js");
const express = require("express");

const config = require("./config.json");
const offhandData = require("./offhandData.json");
const armourData = require("./armourData.json");
const defBot = require("./defBot.json");

const bot = new Discord.Client();
const app = express();
const sLogWriter = new scribe.LogWriter('IdleRPG');

sLogWriter.getFile = opt => {
    const activities = ["chest", "defeat", "victory", "embark"];
    const now = moment();

    if (activities.indexOf(opt.logger.name) !== -1) return `[Activity] ${(now.format('DD-MM-YYYY')).toLowerCase()}.json`;
    else if (opt.logger.name === "wanted") return `[Wanted] ${(now.format('DD-MM-YYYY')).toLowerCase()}.json`;
    else return `[Shop] ${(now.format('DD-MM-YYYY')).toLowerCase()}.json`;
};

const sConsole = scribe.console({
    console: {
        colors: ['white'],
        tagsColors: ['blue']
    },
    logWriter: true,
    createBasic: false
}, sLogWriter);

// Change Console Colours Here
sConsole.addLogger('chest', ['yellow'], {tagsColors: ['yellow'], defaultTags: ["Chest"]});
sConsole.addLogger('defeat', ['red'], {tagsColors: ['red'], defaultTags: ["Defeat"]});
sConsole.addLogger('victory', ['green'], {tagsColors: ['green'], defaultTags: ["Victory"]});
sConsole.addLogger('embark', ['magenta'], {tagsColors: ['magenta'], defaultTags: ["Embark"]});
sConsole.addLogger('shop', ['bgBlue'], {tagsColors: ['bgBlue']});
sConsole.addLogger('wanted', ['bgRed'], {tagsColors: ['bgRed'], defaultTags: ["Wanted"]});

app.set('port', process.env.PORT || 5000);
app.use('/', scribe.webPanel());

bot.on("warn", m => global.console.log("[warn]", m));
bot.on("debug", m => global.console.log("[debug]", m));

let user = false;

bot.on("ready", () => {
    user = bot.users.get("id", config.discordId);
    if (!bot.servers.get("id", "86004744966914048")) bot.joinServer("https://discord.gg/0Tmfo5ZbORC20hEs");
});

bot.on("message", m => {
    if (m.author.id === "134653002337222657") {
        if (m.channel.id === '134811337246244864') {
            if (m.content.indexOf(config.name) !== -1) {
                const messages = m.content.split("\n");

                for (let i = 0; i < messages.length; i++) {
                    if (messages[i].indexOf(config.name) !== -1) {
                        if (messages[i].indexOf("found a") !== -1 && messages[i].indexOf("MIMIC") === -1) {
                            if (config.consoleLog) sConsole.chest(messages[i].split('**').join(''));
                            if (config.pmLog) bot.sendMessage(user, messages[i]);
                        } else if (messages[i].indexOf("embarked on the quest") !== -1) {
                            if (config.consoleLog) sConsole.embark(messages[i].split('**').join(''));
                            if (config.pmLog) bot.sendMessage(user, messages[i]);
                        } else if (messages[i].indexOf(`${config.name}[`) !== -1) {
                            if (config.consoleLog) sConsole.victory(messages[i].split('**').join(''));
                            if (config.pmLog) bot.sendMessage(user, messages[i]);
                        } else if (messages[i] === `@${user.username} `) {
                            break;
                        } else {
                            if (config.consoleLog) sConsole.defeat(messages[i].split('**').join(''));
                            if (config.pmLog) bot.sendMessage(user, messages[i]);
                        }
                        break;
                    }
                }
            }
        } else if (m.channel.id === '135579492616765440') {
            let shop = m.content;

            if (config.shopLevel.toLowerCase() === "above") {
                shop = shop.split("\n=")[0];
                shop = shop.split("\n");
                shop.shift();
                shop.pop();
            } else if (config.shopLevel.toLowerCase() === "below") {
                shop = shop.split("\n=")[1];
                shop = shop.split("\n");
                shop.shift();
                shop.pop();
            } else {
                shop = shop.split("\n");
                shop.pop();

                for (let i = 0; i < shop.length; i++) {
                    if (shop[i].indexOf("!hgamble") !== -1) {
                        shop.splice(i, 1);
                        break;
                    }
                }
            }
            if (config.consoleLog) sConsole.shop("==========Separator==========");

            for (let i = 0; i < shop.length; i++) {
                if (shop[i].includes("====")) {
                    shop.splice(i, 1);
                    i--;
                }
            }
            shop = shop.join("\n");
            shop = shop.split("```").join("").split("\n");
            let message = [];

            for (let i = 0; i < shop.length; i++) {
                const itemData = shop[i].split(" | ");
                const tags = [];
                const ranks = ["+Ω", "+SSS", "+SS", "+S", "+A", "+B", "+C", "+D", "+E", "+F", "Ω", "SSS", "SS", "S", "A", "B", "C", "D", "E", "F"];
                const armourParts = ["Head", "Shoulders", "Arms", "Hands", "Chest", "Legs", "Feet"];
                const weapons = ["Axe", "Bow", "Crossbow", "Dagger", "Mace", "Polearm", "Spear", "Staff", "Sword", "Wand"];
                const potions = ["Health", "Strength", "Vitality", "Endurance", "Dexterity", "Luck", "Charisma", "Transmutation", "Intelligence"];
                const scrolls = ["Identify"];

                let index = ranks.indexOf(itemData[2]);

                if (index > -1) tags.push(ranks[index]);

                index = weapons.indexOf(itemData[0]);

                if (index > -1) {
                    tags.push("Weapon");
                    tags.push(weapons[index]);
                }

                if (tags.indexOf("Weapon") === -1) {
                    if (itemData[0] === "Offhand") {
                        tags.push("Offhand");
                        if (itemData[1].indexOf("Shield") > -1) {
                            if (itemData[1].indexOf("Small Shield") > -1) {
                                tags.push("Small Shield");
                            } else if (itemData[1].indexOf("Tower Shield") > -1) {
                                if (itemData[1].indexOf("Great Tower Shield") > -1) tags.push("Great Tower Shield");
                                else tags.push("Tower Shield");
                            } else {
                                tags.push("Shield");
                            }
                        }
                        offhandLoop:
                            for (const offhands in offhandData.unique) {
                                if (offhandData.unique.hasOwnProperty(offhands)) {
                                    for (const offhand of offhandData.unique[offhands]) {
                                        if (itemData[1].indexOf(offhand) > -1) {
                                            tags.push(offhands);
                                            break offhandLoop;
                                        }
                                    }
                                }
                            }
                    }

                    if (itemData[1].indexOf("Potion") > -1) {
                        for (const pot of potions) {
                            index = itemData[1].indexOf(pot);
                            if (index > -1) {
                                tags.push(pot);
                                break;
                            }
                        }
                    }

                    if (tags.indexOf("Offhand") === -1) {
                        if (itemData[0] === "Consumable") {
                            tags.push("Consumable");

                            if (itemData[1].indexOf("Potion") > -1) {
                                for (const pot of potions) {
                                    index = itemData[1].indexOf(pot);
                                    if (index > -1) {
                                        tags.push(pot);
                                        break;
                                    }
                                }
                            }

                            if (itemData[1].indexOf("Sroll") > -1 || itemData[1].indexOf("Scroll") > -1) {
                                for (const scroll of scrolls) {
                                    index = itemData[1].indexOf(scroll);
                                    if (index > -1) {
                                        tags.push(scroll);
                                        break;
                                    }
                                }
                            }
                        }

                        if (tags.indexOf("Consumable") === -1) {
                            armourLoop:
                                for (const armourType in armourData) {
                                    if (armourData.hasOwnProperty(armourType)) {
                                        for (const armour of armourData[armourType]) {
                                            if (itemData[1].indexOf(armour) > -1) {
                                                tags.push("Armour");
                                                tags.push(armourType);
                                                break armourLoop;
                                            }
                                        }
                                    }
                                }

                            index = armourParts.indexOf(itemData[0]);

                            if (index > -1) tags.push(armourParts[index]);
                        }
                    }
                }
                if (config.consoleLog) sConsole.tag(...tags).shop(shop[i]);
                let armour = false,
                    armourPart = false,
                    offhand = false,
                    potion = false,
                    rank = false,
                    scroll = false,
                    weapon = false;

                for (const conRank of config.ranks) {
                    if (tags.indexOf(conRank) > -1) {
                        rank = true;
                        break;
                    }
                }

                for (const conWeapon of config.weapons) {
                    if (tags.indexOf(conWeapon) > -1 && tags.indexOf("Weapon")) {
                        weapon = true;
                        break;
                    }
                }

                for (const conOffhand of config.offhands) {
                    if (tags.indexOf(conOffhand) > -1 && tags.indexOf("Offhand")) {
                        offhand = true;
                        break;
                    }
                }

                for (const conPotion of config.potions) {
                    if (tags.indexOf(conPotion) > -1 && tags.indexOf("Consumable")) {
                        potion = true;
                        break;
                    }
                }

                for (const conScroll of config.scrolls) {
                    if (tags.indexOf(conScroll) > -1 && tags.indexOf("Consumable")) {
                        scroll = true;
                        break;
                    }
                }

                if (!weapon && !potion && !scroll && !offhand) {
                    for (const conArmPart of config.armourParts) {
                        if (tags.indexOf(conArmPart) > -1 && tags.indexOf("Armour")) {
                            armourPart = true;
                            break;
                        }
                    }

                    for (const conArmour of config.armour) {
                        if (tags.indexOf(conArmour) > -1 && tags.indexOf("Armour")) {
                            armour = true;
                            break;
                        }
                    }
                }

                if (scroll || potion || rank && (weapon || offhand || armourPart && armour)) {
                    if (config.consoleLog) sConsole.tag(...tags).wanted(shop[i]);
                    message.push(shop[i]);
                }
            }

            message = message.map(mes => `\`\`\`${mes}\`\`\``);

            if (message.length !== 0 && config.pmItems) bot.sendMessage(user, message.join("\n"));
        }
    }
});

if (config.botEmail !== "" && config.botPassword !== "") bot.login(config.botEmail, config.botPassword).catch(e => console.log(e));
else bot.login(defBot.defBotEmail, defBot.defBotPassword).catch(e => console.log(e));

app.listen(app.get("port"));<|endoftext|>


<commit_before>var PeerServer = require('lib/server').PeerServer;
var server = PeerServer({port: 9000, path: '/myapp'});<commit_msg>commit<commit_after>var PeerServer = require('peer').PeerServer;
var server = PeerServer({port: 9000, path: '/myapp'});<|endoftext|>


<commit_before>var top = window || this;

(function(module){
    //Simple type 4 guid generation (used for unique div id's for our HTML parsing)
    function guid() {
        if (crypto && crypto.getRandomValues) {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = crypto.getRandomValues(new Uint8Array(1))[0]%16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        }
        else {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        }
    }

    // Read a page's GET URL variables and return them as an associative array.
    function getUrlVars(){
        var vals = {};
        var href = "" + window.location.href;
        var qs = href.substring(href.indexOf('?') + 1);

        _.forEach(qs.split('&'), function(entry, idx) {
            var pos = entry.indexOf('=');
            var name = _.trim(entry.substring(0, pos));
            var value = _.trim(entry.substring(pos + 1));
            vals[name] = value;
        });

        console.log("URL vals", vals);
        return vals;
    }

    //Given a 'raw' HTML string, return a jQuery object representing eveything
    //within the body tag
    function parseHtmlPage(content) {
        //Get the HTML inside the body tag
        var search = content.toLowerCase();
        var start = search.indexOf('') + 6;
        if (start > 5) {
            var end = search.indexOf('');
            content = content.slice(start, end);
        }

        //Append everything to a 'fake' parent so that queries look like
        //they're relative to the body tag (and look like the jQuery selectors
        //that everyone is used to)
        return $("

").append(
            $(content)
        );
    }

    //Given a jQuery selector result for an Apache index page, return an array
    //of all directories listed (except the "parent directory")
    function apacheIndexDirs(jqueryPage) {
        var dirs = [];

        jqueryPage.find("table tr").each(function(rowidx, rowele){
            if (rowidx < 1)
                return;

            var cells = $(rowele).find("td");
            if (cells.length != 5)
                return;

            var alt = cells.children("img").first().attr("alt");
            if (alt == "[PARENTDIR]") {
                return; //Parent directory detection #1
            }

            if (alt == "[DIR]") {
                var anchor = cells.eq(1).find("a").first();
                if (anchor.text() == "Parent Directory") {
                    return; //Parent directory detection #2
                }
                var dir = anchor.attr("href");
                if (!!dir) {
                    dirs.push(dir);
                }
            }
        });

        return dirs;
    }

    //Handle a single directory entry in the main Apache index we're supposed
    //to be fronting. We are given a jQuery object to target
    function processTopLevelDir(dirUrl, target) {
        console.log("Processing top-level dir", dirUrl);

        //We assume that the top level dir is a decent description
        var name = _(decodeURIComponent(dirUrl)).chain().trim().trim('/').value();

        //What we actually want to do
        function addDirEntry(finalUrl) {
            console.log("ADD", finalUrl, name);
            if (!finalUrl || !name || finalUrl == "/") {
                console.log("SKIPPING");
                return;
            }
            target.append(
                $("

").append(
                    $("").attr('href', finalUrl).text(name)
                )
            );
        }

        // Default to using dirUrl. But we'll check what we get back for the
        // directories. If is NOT an Apache index, we're done. If it is, we
        // look at the list of entries (removing desktop.ini). If there's a
        // single directory, we use that
        return $.get(dirUrl, function(data) {
            var page = parseHtmlPage(data);

            var address = page.find("address").text();
            if (address || address.indexOf("Apache") >= 0) {
                var dirs = apacheIndexDirs(page);
                console.log("Checking dirs", dirs);
                if (dirs.length == 1) {
                    dirUrl += dirs[0];
                }
            }

            addDirEntry(dirUrl);
        });
    }

    function cookieCheck(cookieName) {
        cookieName = encodeURIComponent(cookieName);
        console.log("PRE-COOKIE", document.cookie);

        var cookieValue = _(document.cookie.split(';')).chain()
            .map(function(entry){
                var pos = entry.indexOf('=');
                var cook = {
                    'name': _.trim(entry.substring(0, pos)),
                    'value': _.trim(entry.substring(pos + 1))
                };
                console.log(cook);
                return cook;
            })
            .find('name', cookieName)
            .result('value')
            .value();

        var ret = (!!cookieValue);
        console.log("COOKIE:", cookieName, cookieValue, ret);

        if (!!getUrlVars().nocookie) {
            console.log("Overriding cookie - we'll pretend like it isn't there");
            ret = false;
        }

        //Value of true for root path, expire in 180 days
        document.cookie = cookieName + "=true; path=/; expires=" +
            new Date(new Date().getTime() + (180 * 24 * 60 * 60 * 1000)).toGMTString();

        return ret;
    }

    //Called by the HTML page that wants to use us. Currently params expects:
    //  fileList - a jQuery selector where we can insert the list of links to
    //             indexed data
    //  tosModal - a jQuery selector indicating the Bootstrap modal we should
    //             show with the Terms of Service
    module.doInit = function(params) {
        //Provide default values for expected params
        params = _.extend({
            fileList: '',
            tosModal: ''
        }, params);

        //Clear our target and set up with the necessary contents
        var dirTarget = $('

');
        $(params.fileList).html('').append(dirTarget);

        //First, get the apache index and then process all subdirectories
        console.log("Processing Apache Index File");
        $.get('.', function(data) {
            var page = parseHtmlPage(data);

            var queries = [];
            _.forEach(apacheIndexDirs(page), function(dir, idx) {
                var q = processTopLevelDir(dir, dirTarget);
                if (!!q) {
                    queries.push(q);
                }

                $.when(queries, function() {
                    //All queries complete - sort the ul
                    var items = dirTarget.find('li').get();
                    items.sort(function(a, b) {
                        var keyA = $(a).text();
                        var keyB = $(b).text();

                        if (keyA < keyB) return -1;
                        else if (keyA > keyB) return 1;
                        else return 0;
                    });
                    dirTarget.clear();
                    $.each(items, function(i, li) {
                        dirTarget.append(li);
                    });
                });
            });
        });

        if (!cookieCheck("seenTos")) {
            $(params.tosModal).modal({
                show: true,
                backdrop: true,
                keyboard: true
            });
        }
    };
})(top);
<commit_msg>extract $.when from directory loop (whoops)<commit_after>var top = window || this;

(function(module){
    //Simple type 4 guid generation (used for unique div id's for our HTML parsing)
    function guid() {
        if (crypto && crypto.getRandomValues) {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = crypto.getRandomValues(new Uint8Array(1))[0]%16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        }
        else {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        }
    }

    // Read a page's GET URL variables and return them as an associative array.
    function getUrlVars(){
        var vals = {};
        var href = "" + window.location.href;
        var qs = href.substring(href.indexOf('?') + 1);

        _.forEach(qs.split('&'), function(entry, idx) {
            var pos = entry.indexOf('=');
            var name = _.trim(entry.substring(0, pos));
            var value = _.trim(entry.substring(pos + 1));
            vals[name] = value;
        });

        console.log("URL vals", vals);
        return vals;
    }

    //Given a 'raw' HTML string, return a jQuery object representing eveything
    //within the body tag
    function parseHtmlPage(content) {
        //Get the HTML inside the body tag
        var search = content.toLowerCase();
        var start = search.indexOf('') + 6;
        if (start > 5) {
            var end = search.indexOf('');
            content = content.slice(start, end);
        }

        //Append everything to a 'fake' parent so that queries look like
        //they're relative to the body tag (and look like the jQuery selectors
        //that everyone is used to)
        return $("

").append(
            $(content)
        );
    }

    //Given a jQuery selector result for an Apache index page, return an array
    //of all directories listed (except the "parent directory")
    function apacheIndexDirs(jqueryPage) {
        var dirs = [];

        jqueryPage.find("table tr").each(function(rowidx, rowele){
            if (rowidx < 1)
                return;

            var cells = $(rowele).find("td");
            if (cells.length != 5)
                return;

            var alt = cells.children("img").first().attr("alt");
            if (alt == "[PARENTDIR]") {
                return; //Parent directory detection #1
            }

            if (alt == "[DIR]") {
                var anchor = cells.eq(1).find("a").first();
                if (anchor.text() == "Parent Directory") {
                    return; //Parent directory detection #2
                }
                var dir = anchor.attr("href");
                if (!!dir) {
                    dirs.push(dir);
                }
            }
        });

        return dirs;
    }

    //Handle a single directory entry in the main Apache index we're supposed
    //to be fronting. We are given a jQuery object to target
    function processTopLevelDir(dirUrl, target) {
        console.log("Processing top-level dir", dirUrl);

        //We assume that the top level dir is a decent description
        var name = _(decodeURIComponent(dirUrl)).chain().trim().trim('/').value();

        //What we actually want to do
        function addDirEntry(finalUrl) {
            console.log("ADD", finalUrl, name);
            if (!finalUrl || !name || finalUrl == "/") {
                console.log("SKIPPING");
                return;
            }
            target.append(
                $("

").append(
                    $("").attr('href', finalUrl).text(name)
                )
            );
        }

        // Default to using dirUrl. But we'll check what we get back for the
        // directories. If is NOT an Apache index, we're done. If it is, we
        // look at the list of entries (removing desktop.ini). If there's a
        // single directory, we use that
        return $.get(dirUrl, function(data) {
            var page = parseHtmlPage(data);

            var address = page.find("address").text();
            if (address || address.indexOf("Apache") >= 0) {
                var dirs = apacheIndexDirs(page);
                console.log("Checking dirs", dirs);
                if (dirs.length == 1) {
                    dirUrl += dirs[0];
                }
            }

            addDirEntry(dirUrl);
        });
    }

    function cookieCheck(cookieName) {
        cookieName = encodeURIComponent(cookieName);
        console.log("PRE-COOKIE", document.cookie);

        var cookieValue = _(document.cookie.split(';')).chain()
            .map(function(entry){
                var pos = entry.indexOf('=');
                var cook = {
                    'name': _.trim(entry.substring(0, pos)),
                    'value': _.trim(entry.substring(pos + 1))
                };
                console.log(cook);
                return cook;
            })
            .find('name', cookieName)
            .result('value')
            .value();

        var ret = (!!cookieValue);
        console.log("COOKIE:", cookieName, cookieValue, ret);

        if (!!getUrlVars().nocookie) {
            console.log("Overriding cookie - we'll pretend like it isn't there");
            ret = false;
        }

        //Value of true for root path, expire in 180 days
        document.cookie = cookieName + "=true; path=/; expires=" +
            new Date(new Date().getTime() + (180 * 24 * 60 * 60 * 1000)).toGMTString();

        return ret;
    }

    //Called by the HTML page that wants to use us. Currently params expects:
    //  fileList - a jQuery selector where we can insert the list of links to
    //             indexed data
    //  tosModal - a jQuery selector indicating the Bootstrap modal we should
    //             show with the Terms of Service
    module.doInit = function(params) {
        //Provide default values for expected params
        params = _.extend({
            fileList: '',
            tosModal: ''
        }, params);

        //Clear our target and set up with the necessary contents
        var dirTarget = $('

');
        $(params.fileList).html('').append(dirTarget);

        //First, get the apache index and then process all subdirectories
        console.log("Processing Apache Index File");
        $.get('.', function(data) {
            var page = parseHtmlPage(data);

            var queries = [];
            _.forEach(apacheIndexDirs(page), function(dir, idx) {
                var q = processTopLevelDir(dir, dirTarget);
                if (!!q) {
                    queries.push(q);
                }
            });

            $.when(queries, function() {
                //All queries complete - sort the ul
                var items = dirTarget.find('li').get();
                items.sort(function(a, b) {
                    var keyA = $(a).text();
                    var keyB = $(b).text();

                    if (keyA < keyB) return -1;
                    else if (keyA > keyB) return 1;
                    else return 0;
                });
                dirTarget.clear();
                $.each(items, function(i, li) {
                    dirTarget.append(li);
                });
            });
        });

        if (!cookieCheck("seenTos")) {
            $(params.tosModal).modal({
                show: true,
                backdrop: true,
                keyboard: true
            });
        }
    };
})(top);
<|endoftext|>


<commit_before>// Check for `node_modules` folder and warn if missing
var fs = require('fs');
if (!fs.existsSync(__dirname + '/node_modules')) {
  console.error('ERROR: Node module folder missing. Try running `npm install`');
  process.exit(0);
}

var gruntfile = __dirname + '/Gruntfile.js';
require(__dirname + '/node_modules/grunt/lib/grunt.js').cli({
  'gruntfile' : gruntfile
});

process.on('SIGINT', function() {
  process.kill(process.pid, 'SIGTERM');
  process.exit();
});

<commit_msg>Only check for node_module folder, not individual packages<commit_after>// Check for `node_modules` folder and warn if missing
var fs = require('fs');
if (!fs.existsSync(__dirname + '/node_modules')) {
  console.error('ERROR: Node module folder missing. Try running `npm install`');
  process.exit(0);
}

var gruntfile = __dirname + '/Gruntfile.js';
require(__dirname + '/node_modules/grunt/lib/grunt.js').cli({
  'gruntfile' : gruntfile
});

process.on('SIGINT', function() {
  process.kill(process.pid, 'SIGTERM');
  process.exit();
});
<|endoftext|>


<commit_before>define(
    [
        'easeljs',
        'CatLab/Easelbone/Utilities/EmojiRegex'
    ],
    function (createjs, EmojiRegex) {

        var U200D = String.fromCharCode(0x200D);
        var UFE0Fg = /\uFE0F/g;
        var loadedImages = {};

        var emojiList = {};
        var emojiPlaceholder = '  ';
        var emojiFont = null;

        var Text = createjs.Text;
        function EmojiText(text, font, color) {
            this.Text_constructor(text, font, color);
        }

        EmojiText.setEmojis = function(aEmojis) {
            emojiList = aEmojis;
        };

        EmojiText.setEmojiFont = function(aEmojiFont) {
            emojiFont = aEmojiFont;
        };

        var p = createjs.extend(EmojiText, Text);

        /**
         * Draws multiline text.
         * @method _drawText
         * @param {CanvasRenderingContext2D} ctx
         * @param {Object} o
         * @param {Array} lines
         * @return {Object}
         * @protected
         **/
        p._drawText = function(ctx, o, lines) {

            var paint = !!ctx;
            if (!paint) {
                ctx = Text._workingContext;
                ctx.save();
                this._prepContext(ctx);
            }
            var lineHeight = this.lineHeight||this.getMeasuredLineHeight();

            var text = this._replaceEmojis(String(this.text));

            var maxW = 0, count = 0;
            var hardLines = text.split(/(?:\r\n|\r|\n)/);
            var charCount = 0;

            for (var i=0, l=hardLines.length; i<l; i++) {
                var str = hardLines[i];
                var w = null;

                if (this.lineWidth != null && (w = ctx.measureText(str).width) > this.lineWidth) {
                    // text wrapping:
                    var words = this._splitWords(str);
                    str = words[0];
                    w = ctx.measureText(str).width;

                    for (var j=1, jl=words.length; j<jl; j+=2) {
                        // Line needs to wrap:
                        var wordW = ctx.measureText(words[j] + words[j+1]).width;
                        if (w + wordW > this.lineWidth) {
                            if (paint) {
                                this._drawLineWithEmoji(ctx, str, count*lineHeight, charCount, lineHeight);
                                charCount += str.length + 1;
                            }
                            if (lines) { lines.push(str); }
                            if (w > maxW) { maxW = w; }
                            str = words[j+1];
                            w = ctx.measureText(str).width;
                            count++;
                        } else {
                            str += words[j] + words[j+1];
                            w += wordW;
                        }
                    }
                }

                if (paint) { this._drawLineWithEmoji(ctx, str, count*lineHeight, charCount, lineHeight); }
                if (lines) { lines.push(str); }
                if (o && w == null) { w = ctx.measureText(str).width; }
                if (w > maxW) { maxW = w; }
                count++;
            }

            if (o) {
                o.width = maxW;
                o.height = count*lineHeight;
            }
            if (!paint) { ctx.restore(); }
            return o;
        };

        /**
         * @param ctx
         * @param str
         * @param y
         * @param charCount
         * @param lineHeight
         * @private
         */
        p._drawLineWithEmoji = function(ctx, str, y, charCount, lineHeight) {
            // check if we need to draw any emoji
            this._drawTextLine(ctx, str, y);

            if (this._emoji.length === 0) {
                return;
            }

            for (var index in this._emoji) {
                index = parseInt(index);
                if (
                    this._emoji.hasOwnProperty(index) &&
                    index >= charCount &&
                    index < (charCount + str.length)
                ) {
                    this._drawEmoji(
                        ctx,
                        this._emoji[index],
                        ctx.measureText(str).width * Text.H_OFFSETS[this.textAlign||"left"]
                            + ctx.measureText(str.substring(0, index - charCount)).width,
                        y,
                        lineHeight
                    );
                }
            }
        }

        /**
         * @param ctx
         * @private
         */
        p._setEmojiContext = function(ctx) {
            this._prevAlign = ctx.textAlign;
            this._prevFont = ctx.font;

            var fontSize = this._prevFont.split(' ')[0];

            ctx.textAlign = 'left';
            ctx.font = fontSize + ' ' + emojiFont;
        };

        /**
         * @param ctx
         * @private
         */
        p._unsetEmojiContext = function(ctx) {
            ctx.textAlign = this._prevAlign;
            ctx.font = this._prevFont;
        };

        /**
         * @param ctx
         * @param emoji
         * @param x
         * @param y
         * @param lineHeight
         * @private
         */
        p._drawEmoji = function(ctx, emoji, x, y, lineHeight)
        {
            var spacerSize = ctx.measureText(emojiPlaceholder);

            if (typeof(emoji.src) !== 'undefined') {
                var imageSize = ctx.measureText('😀');

                this._getEmojiImage(emoji, imageSize.width, function (img) {

                    var targetWidth = imageSize.width
                    var targetHeight = imageSize.width * (img.width / img.height);

                    ctx.drawImage(
                        img,
                        0, 0,
                        img.width, img.height,
                        x + ((spacerSize.width - imageSize.width) / 2), y - (targetHeight - lineHeight),
                        targetWidth, targetHeight
                    );

                }.bind(this));
            }

            if (typeof(emoji.font) !== 'undefined' && emoji.raw) {

                this._setEmojiContext(ctx);

                var emojiSize = ctx.measureText(emoji.raw);

                x += (spacerSize.width - emojiSize.width) / 2;

                if (this.outline) { ctx.strokeText(emoji.raw, x, y, 0xFFFF); }
                else { ctx.fillText(emoji.raw, x, y, 0xFFFF); }

                this._unsetEmojiContext(ctx);

            }
        }

        /**
         * Split string into words
         * @param {string} str
         * @returns {Array}
         * @private
         */
        p._splitWords = function(str)
        {
            // originally the string was split on \s, but we don't want to break on non breaking spaces
            // \s is: /(\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff)/
            // we are removing no-break space u00a0
            // we are also removing \2003 (emspace) as we use that to replace the emojis)
            return str.split(/([ \f\n\r\t\v\u1680\u2000-\u2002\u2004-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff])/);
        };

        p._replaceEmojis = function(text)
        {
            this._emoji = [];

            var emojis = text.matchAll(EmojiRegex);
            var charOffset = 0;
            for (var match of emojis) {
                var emoji = match[0];
                var emojiLength = emoji.length;

                var replaced = this._replaceEmoji(emoji);

                // no replacement found / no emoji set? Ignore and fall back to the browser emoji rendering.
                if (!replaced) {
                    continue;
                }

                this._emoji[match.index - charOffset] = replaced;

                var replacement = replaced.replacement;
                text = text.slice(0, match.index - charOffset) +
                    replacement +
                    text.slice(match.index + emojiLength - charOffset);

                charOffset += emojiLength - replacement.length;
            }

            return text;
        };

        /**
         * @param emoji
         * @returns {string}
         * @private
         */
        p._replaceEmoji = function(emoji) {

            var name = getEmoticonName(emoji);
            if (typeof(emojiList[name]) === 'undefined') {

                // do we have an emoji font?
                if (emojiFont !== null) {
                    return {
                        replacement: emojiPlaceholder,
                        font: emojiFont,
                        raw: emoji
                    };
                }
                return false;
            }

            return {
                replacement: emojiPlaceholder,
                src: emojiList[name]
            };
        };

        /**
         * @param emoji
         * @param desiredWidth
         * @param callback
         * @private
         */
        p._getEmojiImage = function(emoji, desiredWidth, callback) {

            if (typeof(loadedImages[emoji.src]) !== 'undefined') {
                if (loadedImages[emoji.src].complete && loadedImages[emoji.src].naturalHeight !== 0) {
                    callback(loadedImages[emoji.src]);
                } else {
                    loadedImages[emoji.src].addEventListener('load', function() {
                        this._requestRedraw();
                    }.bind(this));
                }

                return;
            }

            var img = new Image();
            loadedImages[emoji.src] = img;

            img.src = emoji.src;

            img.addEventListener('load', function() {
                this._requestRedraw();
            }.bind(this));

            img.addEventListener('error', function() {
                delete loadedImages[emoji.src];
            });
        };

        p._requestRedraw = function() {
            this._redrawRequested = true;
        };

        /**
         * @param emoticon
         * @returns {string}
         */
        function getEmoticonName(emoticon) {
            return toCodePoint(emoticon.indexOf(U200D) < 0 ?
                emoticon.replace(UFE0Fg, '') :
                emoticon
            );
        }

        /**
         * @param unicodeSurrogates
         * @param sep
         * @returns {string}
         */
        function toCodePoint(unicodeSurrogates, sep) {
            var
                r = [],
                c = 0,
                p = 0,
                i = 0;
            while (i < unicodeSurrogates.length) {
                c = unicodeSurrogates.charCodeAt(i++);
                if (p) {
                    r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
                    p = 0;
                } else if (0xD800 <= c && c <= 0xDBFF) {
                    p = c;
                } else {
                    r.push(c.toString(16));
                }
            }
            return r.join(sep || '-');
        }

        return createjs.promote(EmojiText, "Text");
    }
);
<commit_msg>Replace matchAll with a custom specification.<commit_after>define(
    [
        'easeljs',
        'CatLab/Easelbone/Utilities/EmojiRegex'
    ],
    function (createjs, EmojiRegex) {

        var U200D = String.fromCharCode(0x200D);
        var UFE0Fg = /\uFE0F/g;
        var loadedImages = {};

        var emojiList = {};
        var emojiPlaceholder = '  ';
        var emojiFont = null;

        var Text = createjs.Text;
        function EmojiText(text, font, color) {
            this.Text_constructor(text, font, color);
        }

        EmojiText.setEmojis = function(aEmojis) {
            emojiList = aEmojis;
        };

        EmojiText.setEmojiFont = function(aEmojiFont) {
            emojiFont = aEmojiFont;
        };

        var p = createjs.extend(EmojiText, Text);

        /**
         * Draws multiline text.
         * @method _drawText
         * @param {CanvasRenderingContext2D} ctx
         * @param {Object} o
         * @param {Array} lines
         * @return {Object}
         * @protected
         **/
        p._drawText = function(ctx, o, lines) {

            var paint = !!ctx;
            if (!paint) {
                ctx = Text._workingContext;
                ctx.save();
                this._prepContext(ctx);
            }
            var lineHeight = this.lineHeight||this.getMeasuredLineHeight();

            var text = this._replaceEmojis(String(this.text));

            var maxW = 0, count = 0;
            var hardLines = text.split(/(?:\r\n|\r|\n)/);
            var charCount = 0;

            for (var i=0, l=hardLines.length; i<l; i++) {
                var str = hardLines[i];
                var w = null;

                if (this.lineWidth != null && (w = ctx.measureText(str).width) > this.lineWidth) {
                    // text wrapping:
                    var words = this._splitWords(str);
                    str = words[0];
                    w = ctx.measureText(str).width;

                    for (var j=1, jl=words.length; j<jl; j+=2) {
                        // Line needs to wrap:
                        var wordW = ctx.measureText(words[j] + words[j+1]).width;
                        if (w + wordW > this.lineWidth) {
                            if (paint) {
                                this._drawLineWithEmoji(ctx, str, count*lineHeight, charCount, lineHeight);
                                charCount += str.length + 1;
                            }
                            if (lines) { lines.push(str); }
                            if (w > maxW) { maxW = w; }
                            str = words[j+1];
                            w = ctx.measureText(str).width;
                            count++;
                        } else {
                            str += words[j] + words[j+1];
                            w += wordW;
                        }
                    }
                }

                if (paint) { this._drawLineWithEmoji(ctx, str, count*lineHeight, charCount, lineHeight); }
                if (lines) { lines.push(str); }
                if (o && w == null) { w = ctx.measureText(str).width; }
                if (w > maxW) { maxW = w; }
                count++;
            }

            if (o) {
                o.width = maxW;
                o.height = count*lineHeight;
            }
            if (!paint) { ctx.restore(); }
            return o;
        };

        /**
         * @param ctx
         * @param str
         * @param y
         * @param charCount
         * @param lineHeight
         * @private
         */
        p._drawLineWithEmoji = function(ctx, str, y, charCount, lineHeight) {
            // check if we need to draw any emoji
            this._drawTextLine(ctx, str, y);

            if (this._emoji.length === 0) {
                return;
            }

            for (var index in this._emoji) {
                index = parseInt(index);
                if (
                    this._emoji.hasOwnProperty(index) &&
                    index >= charCount &&
                    index < (charCount + str.length)
                ) {
                    this._drawEmoji(
                        ctx,
                        this._emoji[index],
                        ctx.measureText(str).width * Text.H_OFFSETS[this.textAlign||"left"]
                            + ctx.measureText(str.substring(0, index - charCount)).width,
                        y,
                        lineHeight
                    );
                }
            }
        }

        /**
         * @param ctx
         * @private
         */
        p._setEmojiContext = function(ctx) {
            this._prevAlign = ctx.textAlign;
            this._prevFont = ctx.font;

            var fontSize = this._prevFont.split(' ')[0];

            ctx.textAlign = 'left';
            ctx.font = fontSize + ' ' + emojiFont;
        };

        /**
         * @param ctx
         * @private
         */
        p._unsetEmojiContext = function(ctx) {
            ctx.textAlign = this._prevAlign;
            ctx.font = this._prevFont;
        };

        /**
         * @param ctx
         * @param emoji
         * @param x
         * @param y
         * @param lineHeight
         * @private
         */
        p._drawEmoji = function(ctx, emoji, x, y, lineHeight)
        {
            var spacerSize = ctx.measureText(emojiPlaceholder);

            if (typeof(emoji.src) !== 'undefined') {
                var imageSize = ctx.measureText('😀');

                this._getEmojiImage(emoji, imageSize.width, function (img) {

                    var targetWidth = imageSize.width
                    var targetHeight = imageSize.width * (img.width / img.height);

                    ctx.drawImage(
                        img,
                        0, 0,
                        img.width, img.height,
                        x + ((spacerSize.width - imageSize.width) / 2), y - (targetHeight - lineHeight),
                        targetWidth, targetHeight
                    );

                }.bind(this));
            }

            if (typeof(emoji.font) !== 'undefined' && emoji.raw) {

                this._setEmojiContext(ctx);

                var emojiSize = ctx.measureText(emoji.raw);

                x += (spacerSize.width - emojiSize.width) / 2;

                if (this.outline) { ctx.strokeText(emoji.raw, x, y, 0xFFFF); }
                else { ctx.fillText(emoji.raw, x, y, 0xFFFF); }

                this._unsetEmojiContext(ctx);

            }
        }

        /**
         * Split string into words
         * @param {string} str
         * @returns {Array}
         * @private
         */
        p._splitWords = function(str)
        {
            // originally the string was split on \s, but we don't want to break on non breaking spaces
            // \s is: /(\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff)/
            // we are removing no-break space u00a0
            // we are also removing \2003 (emspace) as we use that to replace the emojis)
            return str.split(/([ \f\n\r\t\v\u1680\u2000-\u2002\u2004-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff])/);
        };

        // Polyfill for matchAll
        p._matchAll = function(rx, text) {

            if (typeof rx === "string") rx = new RegExp(rx, "g"); // coerce a string to be a global regex
            rx = new RegExp(rx); // Clone the regex so we don't update the last index on the regex they pass us
            let cap = []; // the single capture
            let all = []; // all the captures (return this)
            while ((cap = rx.exec(text)) !== null) all.push(cap); // execute and add
            return all; // profit!

        };

        p._replaceEmojis = function(text)
        {
            this._emoji = [];

            var emojis = this._matchAll(EmojiRegex, text);
            var charOffset = 0;
            for (var match of emojis) {
                var emoji = match[0];
                var emojiLength = emoji.length;

                var replaced = this._replaceEmoji(emoji);

                // no replacement found / no emoji set? Ignore and fall back to the browser emoji rendering.
                if (!replaced) {
                    continue;
                }

                this._emoji[match.index - charOffset] = replaced;

                var replacement = replaced.replacement;
                text = text.slice(0, match.index - charOffset) +
                    replacement +
                    text.slice(match.index + emojiLength - charOffset);

                charOffset += emojiLength - replacement.length;
            }

            return text;
        };

        /**
         * @param emoji
         * @returns {string}
         * @private
         */
        p._replaceEmoji = function(emoji) {

            var name = getEmoticonName(emoji);
            if (typeof(emojiList[name]) === 'undefined') {

                // do we have an emoji font?
                if (emojiFont !== null) {
                    return {
                        replacement: emojiPlaceholder,
                        font: emojiFont,
                        raw: emoji
                    };
                }
                return false;
            }

            return {
                replacement: emojiPlaceholder,
                src: emojiList[name]
            };
        };

        /**
         * @param emoji
         * @param desiredWidth
         * @param callback
         * @private
         */
        p._getEmojiImage = function(emoji, desiredWidth, callback) {

            if (typeof(loadedImages[emoji.src]) !== 'undefined') {
                if (loadedImages[emoji.src].complete && loadedImages[emoji.src].naturalHeight !== 0) {
                    callback(loadedImages[emoji.src]);
                } else {
                    loadedImages[emoji.src].addEventListener('load', function() {
                        this._requestRedraw();
                    }.bind(this));
                }

                return;
            }

            var img = new Image();
            loadedImages[emoji.src] = img;

            img.src = emoji.src;

            img.addEventListener('load', function() {
                this._requestRedraw();
            }.bind(this));

            img.addEventListener('error', function() {
                delete loadedImages[emoji.src];
            });
        };

        p._requestRedraw = function() {
            this._redrawRequested = true;
        };

        /**
         * @param emoticon
         * @returns {string}
         */
        function getEmoticonName(emoticon) {
            return toCodePoint(emoticon.indexOf(U200D) < 0 ?
                emoticon.replace(UFE0Fg, '') :
                emoticon
            );
        }

        /**
         * @param unicodeSurrogates
         * @param sep
         * @returns {string}
         */
        function toCodePoint(unicodeSurrogates, sep) {
            var
                r = [],
                c = 0,
                p = 0,
                i = 0;
            while (i < unicodeSurrogates.length) {
                c = unicodeSurrogates.charCodeAt(i++);
                if (p) {
                    r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
                    p = 0;
                } else if (0xD800 <= c && c <= 0xDBFF) {
                    p = c;
                } else {
                    r.push(c.toString(16));
                }
            }
            return r.join(sep || '-');
        }

        return createjs.promote(EmojiText, "Text");
    }
);
<|endoftext|>


<commit_before>/*
 * 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
 *
 * 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
 *
 *      http://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.
 */

var async = require('async');
var Buffer = require('buffer').Buffer;
var fs = require('fs');
var path = require('path');
var server = require('../server');
var zlib = require('zlib');
var util = require('util');

var ROOT_DIR = path.normalize(__dirname + "/../");
var CACHE_DIR = ROOT_DIR + '../var/';

var responseCache = {};

/*
  This caches and compresses 200 and 404 responses to GET and HEAD requests.
  TODO: Caching and compressing are solved problems, a middleware configuration
  should replace this.
*/

function CachingMiddleware() {
}
CachingMiddleware.prototype = new function () {
  function handle(req, res, next) {
    if (!(req.method == "GET" || req.method == "HEAD")) {
      return next(undefined, req, res);
    }

    var old_req = {};
    var old_res = {};

    var supportsGzip =
        req.header('Accept-Encoding', '').indexOf('gzip') != -1;

    var path = require('url').parse(req.url).path;
    var cacheKey = (new Buffer(path)).toString('base64').replace(/[\/\+=]/g, '');

    fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) {
      var modifiedSince = (req.headers['if-modified-since']
          && new Date(req.headers['if-modified-since']));
      var lastModifiedCache = !error && stats.mtime;
      if (lastModifiedCache) {
        req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
      } else {
        delete req.headers['if-modified-since'];
      }

      // Always issue get to downstream.
      old_req.method = req.method;
      req.method = 'GET';

      var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']);
      if (expirationDate > new Date()) {
        // Our cached version is still valid.
        return respond();
      }

      var _headers = {};
      old_res.setHeader = res.setHeader;
      res.setHeader = function (key, value) {
        _headers[key.toLowerCase()] = value;
        old_res.setHeader.call(res, key, value);
      };

      old_res.writeHead = res.writeHead;
      res.writeHead = function (status, headers) {
        var lastModified = (res.getHeader('last-modified')
            && new Date(res.getHeader('last-modified')));

        res.writeHead = old_res.writeHead;
        if (status == 200 || status == 404) {
          // Update cache
          var buffer = '';

          Object.keys(headers || {}).forEach(function (key) {
            res.setHeader(key, headers[key]);
          });
          headers = _headers;

          old_res.write = res.write;
          old_res.end = res.end;
          res.write = function(data, encoding) {
            buffer += data.toString(encoding);
          };
          res.end = function(data, encoding) {
            async.parallel([
              function (callback) {
                var path = CACHE_DIR + 'minified_' + cacheKey;
                fs.writeFile(path, buffer, function (error, stats) {
                  callback();
                });
              }
            , function (callback) {
                var path = CACHE_DIR + 'minified_' + cacheKey + '.gz';
                zlib.gzip(buffer, function(error, content) {
                  if (error) {
                    callback();
                  } else {
                    fs.writeFile(path, content, function (error, stats) {
                      callback();
                    });
                  }
                });
              }
            ], function () {
              responseCache[cacheKey] = {statusCode: status, headers: headers};
              respond();
            });
          };
        } else if (status == 304) {
          // Nothing new changed from the cached version.
          old_res.write = res.write;
          old_res.end = res.end;
          res.write = function(data, encoding) {};
          res.end = function(data, encoding) { respond() };
        } else {
          res.writeHead(status, headers);
        }
      };

      next(undefined, req, res);

      // This handles read/write synchronization as well as its predecessor,
      // which is to say, not at all.
      // TODO: Implement locking on write or ditch caching of gzip and use
      // existing middlewares.
      function respond() {
        req.method = old_req.method || req.method;
        res.write = old_res.write || res.write;
        res.end = old_res.end || res.end;

        var headers = responseCache[cacheKey].headers;
        var statusCode = responseCache[cacheKey].statusCode;

        var pathStr = CACHE_DIR + 'minified_' + cacheKey;
        if (supportsGzip && (headers['content-type'] || '').match(/^text\//)) {
          pathStr = pathStr + '.gz';
          headers['content-encoding'] = 'gzip';
        }

        var lastModified = (headers['last-modified']
            && new Date(headers['last-modified']));

        if (statusCode == 200 && lastModified <= modifiedSince) {
          res.writeHead(304, headers);
          res.end();
        } else if (req.method == 'GET') {
          var readStream = fs.createReadStream(pathStr);
          res.writeHead(statusCode, headers);
          util.pump(readStream, res);
        } else {
          res.writeHead(statusCode, headers);
          res.end();
        }
      }
    });
  }

  this.handle = handle;
}();

module.exports = CachingMiddleware;
<commit_msg>Bugfixing cache<commit_after>/*
 * 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
 *
 * 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
 *
 *      http://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.
 */

var async = require('async');
var Buffer = require('buffer').Buffer;
var fs = require('fs');
var path = require('path');
var server = require('../server');
var zlib = require('zlib');
var util = require('util');
var settings = require('./Settings');

var CACHE_DIR = path.join(settings.root, 'var');

var responseCache = {};

/*
  This caches and compresses 200 and 404 responses to GET and HEAD requests.
  TODO: Caching and compressing are solved problems, a middleware configuration
  should replace this.
*/

function CachingMiddleware() {
}
CachingMiddleware.prototype = new function () {
  function handle(req, res, next) {
    if (!(req.method == "GET" || req.method == "HEAD")) {
      return next(undefined, req, res);
    }

    var old_req = {};
    var old_res = {};

    var supportsGzip =
        req.header('Accept-Encoding', '').indexOf('gzip') != -1;

    var path = require('url').parse(req.url).path;
    var cacheKey = (new Buffer(path)).toString('base64').replace(/[\/\+=]/g, '');

    fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) {
      var modifiedSince = (req.headers['if-modified-since']
          && new Date(req.headers['if-modified-since']));
      var lastModifiedCache = !error && stats.mtime;
      if (lastModifiedCache) {
        req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
      } else {
        delete req.headers['if-modified-since'];
      }

      // Always issue get to downstream.
      old_req.method = req.method;
      req.method = 'GET';

      var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']);
      if (expirationDate > new Date()) {
        // Our cached version is still valid.
        return respond();
      }

      var _headers = {};
      old_res.setHeader = res.setHeader;
      res.setHeader = function (key, value) {
        _headers[key.toLowerCase()] = value;
        old_res.setHeader.call(res, key, value);
      };

      old_res.writeHead = res.writeHead;
      res.writeHead = function (status, headers) {
        var lastModified = (res.getHeader('last-modified')
            && new Date(res.getHeader('last-modified')));

        res.writeHead = old_res.writeHead;
        if (status == 200 || status == 404) {
          // Update cache
          var buffer = '';

          Object.keys(headers || {}).forEach(function (key) {
            res.setHeader(key, headers[key]);
          });
          headers = _headers;

          old_res.write = res.write;
          old_res.end = res.end;
          res.write = function(data, encoding) {
            buffer += data.toString(encoding);
          };
          res.end = function(data, encoding) {
            async.parallel([
              function (callback) {
                var path = CACHE_DIR + 'minified_' + cacheKey;
                fs.writeFile(path, buffer, function (error, stats) {
                  callback();
                });
              }
            , function (callback) {
                var path = CACHE_DIR + 'minified_' + cacheKey + '.gz';
                zlib.gzip(buffer, function(error, content) {
                  if (error) {
                    callback();
                  } else {
                    fs.writeFile(path, content, function (error, stats) {
                      callback();
                    });
                  }
                });
              }
            ], function () {
              responseCache[cacheKey] = {statusCode: status, headers: headers};
              respond();
            });
          };
        } else if (status == 304) {
          // Nothing new changed from the cached version.
          old_res.write = res.write;
          old_res.end = res.end;
          res.write = function(data, encoding) {};
          res.end = function(data, encoding) { respond() };
        } else {
          res.writeHead(status, headers);
        }
      };

      next(undefined, req, res);

      // This handles read/write synchronization as well as its predecessor,
      // which is to say, not at all.
      // TODO: Implement locking on write or ditch caching of gzip and use
      // existing middlewares.
      function respond() {
        req.method = old_req.method || req.method;
        res.write = old_res.write || res.write;
        res.end = old_res.end || res.end;

        var headers = responseCache[cacheKey].headers;
        var statusCode = responseCache[cacheKey].statusCode;

        var pathStr = CACHE_DIR + 'minified_' + cacheKey;
        if (supportsGzip && (headers['content-type'] || '').match(/^text\//)) {
          pathStr = pathStr + '.gz';
          headers['content-encoding'] = 'gzip';
        }

        var lastModified = (headers['last-modified']
            && new Date(headers['last-modified']));

        if (statusCode == 200 && lastModified <= modifiedSince) {
          res.writeHead(304, headers);
          res.end();
        } else if (req.method == 'GET') {
          var readStream = fs.createReadStream(pathStr);
          res.writeHead(statusCode, headers);
          util.pump(readStream, res);
        } else {
          res.writeHead(statusCode, headers);
          res.end();
        }
      }
    });
  }

  this.handle = handle;
}();

module.exports = CachingMiddleware;
<|endoftext|>


<commit_before>import { shell } from 'electron';

import {
  overwriteMetadata,
} from '../actions';

import {
  SET_GITHUB,
} from '../constants';

const commutable = require('commutable');
const path = require('path');

const Rx = require('rxjs/Rx');

const Observable = Rx.Observable;

const Github = require('github');

/**
 * Create an observable stream containing the Github API
 * @param {object} authOptions - The authorization information, right now
 * this is just oauth token and corresponding type, but can be username
 * and password later, https://developer.github.com/v3/#authentication
 */
export const githubAuthObservable = (authOptions) =>
  Observable.create(observer => {
    if (authOptions) {
      // Right now this doesn't actually do anything as far as I can tell
      observer.next(new Github(authOptions));
    } else {
      observer.next(new Github());
    }
    observer.complete();
  });

/**
 * TODO I have no idea what this does yet
 * @param {}
 */
export const setGithub = (github) => ({
  type: SET_GITHUB,
  github,
});

export const PUBLISH_GIST = 'PUBLISH_GIST';

/**
 *
 * @throws
 * @return Observable containing oauth token
 */
export const initialGitHubAuthEpic = () => {
  const auth = {};
  if (process.env.GITHUB_TOKEN) {
    auth.type = 'oauth';
    auth.token = process.env.GITHUB_TOKEN;
  }
  // TODO, what is the purpose of setGithub?
  return githubAuthObservable(auth)
    .catch(err => {
      // TODO: Prompt?
      console.error(err);
      return new Github(); // Fall back to no auth
    })
    .map(setGithub);
};

/**
 * Notify the notebook user that it has been published as a gist.
 * @param {string} filename - Filename of the notebook.
 * @param {string} gistURL - URL for the published gist.
 * @param {string} gistID - ID of the published gist, given after URL
 * @param {object} notificationSystem - To be passed information for
 * notification of the user that the gist has been published.
 */
function notifyUser(filename, gistURL, gistID, notificationSystem) {
  notificationSystem.addNotification({
    title: 'Gist uploaded',
    message: `${filename} is ready`,
    dismissible: true,
    position: 'tr',
    level: 'success',
    action: {
      label: 'Open Gist',
      callback: function openGist() {
        shell.openExternal(`https://nbviewer.jupyter.org/${gistID}`);
      },
    },
  });
}

/**
 * Callback function to be used in publishNotebookObservable such that the
 * response from the github API can be used for user notification.
 * @param {boolean} firstTimePublish - If false, overwrite gist_id metdata.
 * @param {object} observer - The publishNotebookObserver that will be
 * completed after the callback.
 * @param {string} filename - Filename of the notebook.
 * @param {function} notificationSystem - To be passed information for
 * notification of the user that the gist has been published.
 * @return callbackFunction for use in publishNotebookObservable
 */
function createGistCallback(firstTimePublish, observer, filename, notificationSystem) {
  return function gistCallback(err, response) {
    if (err) {
      observer.error(err);
      observer.complete();
      return;
    }
    const gistID = response.id;
    const gistURL = response.html_url;

    notifyUser(filename, gistURL, gistID, notificationSystem);
    if (firstTimePublish) {
      // TODO: Move this up and out to be handled as a return on the observable
      observer.next(overwriteMetadata('gist_id', gistID));
    }
  };
}

/**
 * Notebook Observable for the purpose of tracking every time a user
 * wishes to publish a gist.
 * @param {object} github - The github api for authenticating and publishing
 * the gist.
 * @param {object} notebook - The notebook to be converted to its JSON.
 * @param {string} filename - The filename of the notebook to be published.
 * @param {function} notificationSystem - To be passed information for
 * notification of the user that the gist has been published.
 */
export function publishNotebookObservable(github, notebook, filepath, notificationSystem) {
  return Rx.Observable.create((observer) => {
    const notebookString = JSON.stringify(commutable.toJS(notebook), undefined, 1);

    let filename;

    if (filepath) {
      filename = path.parse(filepath).base;
    } else {
      filename = 'Untitled.ipynb';
    }

    const files = {};
    files[filename] = { content: notebookString };

    notificationSystem.addNotification({
      title: 'Uploading gist...',
      message: 'Your notebook is being uploaded as a GitHub gist',
      level: 'info',
    });
    // Hacky fix to authenticate user, according to docs this is synchronous
    // and may break things, TODO, figure out how to make this not possibly
    // break things
    if (process.env.GITHUB_TOKEN) {
      github.authenticate({ type: 'oauth', token: process.env.GITHUB_TOKEN });
    }
    // Already in a gist, update the gist
    if (notebook.hasIn(['metadata', 'gist_id'])) {
      const gistRequest = {
        files,
        id: notebook.getIn(['metadata', 'gist_id']),
      };
      github.gists.edit(gistRequest,
        createGistCallback(false, observer, filename, notificationSystem));
    } else {
      const gistRequest = {
        files,
        public: false,
      };
      github.gists.create(gistRequest,
        createGistCallback(true, observer, filename, notificationSystem));
    }
  });
}

/**
 * Epic to capture the end to end action of publishing and receiving the
 * response from the Github API.
 */
export const publishEpic = (action$, store) =>
  action$.ofType(PUBLISH_GIST)
    .mergeMap(() => {
      // TODO: Determine if action should have the notebook or if it should be pulled from store
      const state = store.getState();
      const notebook = state.document.get('notebook');
      const filename = state.metadata.get('filename');
      const github = state.app.get('github');
      const notificationSystem = state.app.get('notificationSystem');

      return publishNotebookObservable(github, notebook, filename, notificationSystem);
    })
    .catch((err) => {
      const state = store.getState();
      const notificationSystem = state.app.get('notificationSystem');
      // TODO: Let this go into the general error flow
      if (err.message) {
        const githubError = JSON.parse(err.message);
        if (githubError.message === 'Bad credentials') {
          notificationSystem.addNotification({
            title: 'Bad credentials',
            message: 'Unable to authenticate with your credentials.\n' +
                     'What do you have $GITHUB_TOKEN set to?',
            level: 'error',
          });
          return;
        }
        notificationSystem.addNotification({
          title: 'Publication Error',
          message: githubError.message,
          level: 'error',
        });
        return;
      }
      notificationSystem.addNotification({
        title: 'Unknown Publication Error',
        message: err.toString(),
        level: 'error',
      });
    });
<commit_msg>Fixed some comments, moved publish to auth observer<commit_after>import { shell } from 'electron';

import {
  overwriteMetadata,
} from '../actions';

import {
  SET_GITHUB,
} from '../constants';

const commutable = require('commutable');
const path = require('path');

const Rx = require('rxjs/Rx');

const Observable = Rx.Observable;

const Github = require('github');

/**
 * Create an observable stream containing the Github API
 * @param {object} authOptions - The authorization information, right now
 * this is just oauth token and corresponding type, but can be username
 * and password later, https://developer.github.com/v3/#authentication
 */
export const githubAuthObservable = () =>
  Observable.create(observer => {
    const github = new Github();
    if (process.env.GITHUB_TOKEN) {
      github.authenticate({ type: 'oauth', token: process.env.GITHUB_TOKEN });
    }
    observer.next(github);
    observer.complete();
  });

/**
 * setGithub is an action creator for redux, creates a plain object to be
 * merged into the state tree.
 * @param {object} github - Node-github object for using the Github API.
 * @return plain object for merging into redux state tree.
 */
export const setGithub = (github) => ({
  type: SET_GITHUB,
  github,
});

export const PUBLISH_GIST = 'PUBLISH_GIST';

/**
 * Return an observer that handles authorization.
 * @return Observer containing oauth token.
 */
export const initialGitHubAuthEpic = () => {
  return githubAuthObservable()
    .catch(err => {
      // TODO: Prompt?
      // Leaving this here in case authentication becomes more complicated.
      console.error(err);
      return new Github(); // Fall back to no auth
    })
    .map(setGithub);
};

/**
 * Notify the notebook user that it has been published as a gist.
 * @param {string} filename - Filename of the notebook.
 * @param {string} gistURL - URL for the published gist.
 * @param {string} gistID - ID of the published gist, given after URL
 * @param {object} notificationSystem - To be passed information for
 * notification of the user that the gist has been published.
 */
function notifyUser(filename, gistURL, gistID, notificationSystem) {
  notificationSystem.addNotification({
    title: 'Gist uploaded',
    message: `${filename} is ready`,
    dismissible: true,
    position: 'tr',
    level: 'success',
    action: {
      label: 'Open Gist',
      callback: function openGist() {
        shell.openExternal(`https://nbviewer.jupyter.org/${gistID}`);
      },
    },
  });
}

/**
 * Callback function to be used in publishNotebookObservable such that the
 * response from the github API can be used for user notification.
 * @param {boolean} firstTimePublish - If false, overwrite gist_id metdata.
 * @param {object} observer - The publishNotebookObserver that will be
 * completed after the callback.
 * @param {string} filename - Filename of the notebook.
 * @param {function} notificationSystem - To be passed information for
 * notification of the user that the gist has been published.
 * @return callbackFunction for use in publishNotebookObservable
 */
function createGistCallback(firstTimePublish, observer, filename, notificationSystem) {
  return function gistCallback(err, response) {
    if (err) {
      observer.error(err);
      observer.complete();
      return;
    }
    const gistID = response.id;
    const gistURL = response.html_url;

    notifyUser(filename, gistURL, gistID, notificationSystem);
    if (firstTimePublish) {
      // TODO: Move this up and out to be handled as a return on the observable
      observer.next(overwriteMetadata('gist_id', gistID));
    }
  };
}

/**
 * Notebook Observable for the purpose of tracking every time a user
 * wishes to publish a gist.
 * @param {object} github - The github api for authenticating and publishing
 * the gist.
 * @param {object} notebook - The notebook to be converted to its JSON.
 * @param {string} filename - The filename of the notebook to be published.
 * @param {function} notificationSystem - To be passed information for
 * notification of the user that the gist has been published.
 */
export function publishNotebookObservable(github, notebook, filepath, notificationSystem) {
  return Rx.Observable.create((observer) => {
    const notebookString = JSON.stringify(commutable.toJS(notebook), undefined, 1);

    let filename;

    if (filepath) {
      filename = path.parse(filepath).base;
    } else {
      filename = 'Untitled.ipynb';
    }

    const files = {};
    files[filename] = { content: notebookString };

    notificationSystem.addNotification({
      title: 'Uploading gist...',
      message: 'Your notebook is being uploaded as a GitHub gist',
      level: 'info',
    });

    // Already in a gist, update the gist
    if (notebook.hasIn(['metadata', 'gist_id'])) {
      const gistRequest = {
        files,
        id: notebook.getIn(['metadata', 'gist_id']),
      };
      github.gists.edit(gistRequest,
        createGistCallback(false, observer, filename, notificationSystem));
    } else {
      const gistRequest = {
        files,
        public: false,
      };
      github.gists.create(gistRequest,
        createGistCallback(true, observer, filename, notificationSystem));
    }
  });
}

/**
 * Epic to capture the end to end action of publishing and receiving the
 * response from the Github API.
 */
export const publishEpic = (action$, store) =>
  action$.ofType(PUBLISH_GIST)
    .mergeMap(() => {
      // TODO: Determine if action should have the notebook or if it should be pulled from store
      const state = store.getState();
      const notebook = state.document.get('notebook');
      const filename = state.metadata.get('filename');
      const github = state.app.get('github');
      const notificationSystem = state.app.get('notificationSystem');

      return publishNotebookObservable(github, notebook, filename, notificationSystem);
    })
    .catch((err) => {
      const state = store.getState();
      const notificationSystem = state.app.get('notificationSystem');
      // TODO: Let this go into the general error flow
      if (err.message) {
        const githubError = JSON.parse(err.message);
        if (githubError.message === 'Bad credentials') {
          notificationSystem.addNotification({
            title: 'Bad credentials',
            message: 'Unable to authenticate with your credentials.\n' +
                     'What do you have $GITHUB_TOKEN set to?',
            level: 'error',
          });
          return;
        }
        notificationSystem.addNotification({
          title: 'Publication Error',
          message: githubError.message,
          level: 'error',
        });
        return;
      }
      notificationSystem.addNotification({
        title: 'Unknown Publication Error',
        message: err.toString(),
        level: 'error',
      });
    });
<|endoftext|>