<commit_before>const Eris = require("eris");
const path = require("path");

const config = require("./cfg");
const bot = require("./bot");
const knex = require("./knex");
const {messageQueue} = require("./queue");
const utils = require("./utils");
const { createCommandManager } = require("./commands");
const { getPluginAPI, installPlugins, loadPlugins } = require("./plugins");
const { callBeforeNewThreadHooks } = require("./hooks/beforeNewThread");

const blocked = require("./data/blocked");
const threads = require("./data/threads");
const updates = require("./data/updates");

const {ACCIDENTAL_THREAD_MESSAGES} = require("./data/constants");
const {getOrFetchChannel} = require("./utils");

module.exports = {
  async start() {
    console.log("Preparing plugins...");
    await installAllPlugins();

    console.log("Connecting to Discord...");

    bot.once("ready", async () => {
      console.log("Connected! Waiting for servers to become available...");

      await (new Promise(resolve => {
        const waitNoteTimeout = setTimeout(() => {
          console.log("Servers did not become available after 15 seconds, continuing start-up anyway");
          console.log("");

          const isSingleServer = config.mainServerId.includes(config.inboxServerId);
          if (isSingleServer) {
            console.log("WARNING: The bot will not work before it's invited to the server.");
          } else {
            const hasMultipleMainServers = config.mainServerId.length > 1;
            if (hasMultipleMainServers) {
              console.log("WARNING: The bot will not function correctly until it's invited to *all* main servers and the inbox server.");
            } else {
              console.log("WARNING: The bot will not function correctly until it's invited to *both* the main server and the inbox server.");
            }
          }

          console.log("");

          resolve();
        }, 15 * 1000);

        Promise.all([
          ...config.mainServerId.map(id => waitForGuild(id)),
          waitForGuild(config.inboxServerId),
        ]).then(() => {
          clearTimeout(waitNoteTimeout);
          resolve();
        });
      }));

      console.log("Initializing...");
      initStatus();
      initBaseMessageHandlers();
      initUpdateNotifications();

      console.log("Loading plugins...");
      const pluginResult = await loadAllPlugins();
      console.log(`Loaded ${pluginResult.loadedCount} plugins (${pluginResult.baseCount} built-in plugins, ${pluginResult.externalCount} external plugins)`);

      console.log("");
      console.log("Done! Now listening to DMs.");
      console.log("");
    });

    bot.connect();
  }
};

function waitForGuild(guildId) {
  if (bot.guilds.has(guildId)) {
    return Promise.resolve();
  }

  return new Promise(resolve => {
    bot.on("guildAvailable", guild => {
      if (guild.id === guildId) {
        resolve();
      }
    });
  });
}

function initStatus() {
  function applyStatus() {
    const type = {
      "playing": 0,
      "watching": 3,
      "listening": 2,
      "streaming": 1,
    }[config.statusType] || 0;
    type == 1 ? bot.editStatus(null, {name: config.status, type, url: config.statusUrl}) : bot.editStatus(null, {name: config.status, type});
  }

  if (config.status == null || config.status === "" || config.status === "none" || config.status === "off") {
    return;
  }

  // Set the bot status initially, then reapply it every hour since in some cases it gets unset
  applyStatus();
  setInterval(applyStatus, 60 * 60 * 1000);
}

function initBaseMessageHandlers() {
  /**
   * When a moderator posts in a modmail thread...
   * 1) If alwaysReply is enabled, reply to the user
   * 2) If alwaysReply is disabled, save that message as a chat message in the thread
   */
  bot.on("messageCreate", async msg => {
    if (! await utils.messageIsOnInboxServer(bot, msg)) return;
    if (msg.author.id === bot.user.id) return;

    const thread = await threads.findByChannelId(msg.channel.id);
    if (! thread) return;

    if (! msg.author.bot && (msg.content.startsWith(config.prefix) || msg.content.startsWith(config.snippetPrefix))) {
      // Save commands as "command messages"
      thread.saveCommandMessageToLogs(msg);
    } else if (! msg.author.bot && config.alwaysReply) {
      // AUTO-REPLY: If config.alwaysReply is enabled, send all chat messages in thread channels as replies
      if (! utils.isStaff(msg.member)) return; // Only staff are allowed to reply

      const replied = await thread.replyToUser(msg.member, msg.content.trim(), msg.attachments, config.alwaysReplyAnon || false);
      if (replied) msg.delete();
    } else {
      // Otherwise just save the messages as "chat" in the logs
      thread.saveChatMessageToLogs(msg);
    }
  });

  /**
   * When we get a private message...
   * 1) Find the open modmail thread for this user, or create a new one
   * 2) Post the message as a user reply in the thread
   */
  bot.on("messageCreate", async msg => {
    const channel = await getOrFetchChannel(bot, msg.channel.id);
    if (! (channel instanceof Eris.PrivateChannel)) return;
    if (msg.author.bot) return;
    if (msg.type !== 0) return; // Ignore pins etc.

    if (await blocked.isBlocked(msg.author.id)) return;

    // Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels being created
    messageQueue.add(async () => {
      let thread = await threads.findOpenThreadByUserId(msg.author.id);
      const createNewThread = (thread == null);

      // New thread
      if (createNewThread) {
        // Ignore messages that shouldn't usually open new threads, such as "ok", "thanks", etc.
        if (config.ignoreAccidentalThreads && msg.content && ACCIDENTAL_THREAD_MESSAGES.includes(msg.content.trim().toLowerCase())) return;

        thread = await threads.createNewThreadForUser(msg.author, {
          source: "dm",
          message: msg,
        });
      }

      if (thread) {
        await thread.receiveUserReply(msg);

        if (createNewThread) {
          // Send auto-reply to the user
          if (config.responseMessage) {
            const responseMessage = utils.readMultilineConfigValue(config.responseMessage);

            try {
              const postToThreadChannel = config.showResponseMessageInThreadChannel;
              await thread.sendSystemMessageToUser(responseMessage, { postToThreadChannel });
            } catch (err) {
              await thread.postSystemMessage(`**NOTE:** Could not send auto-response to the user. The error given was: \`${err.message}\``);
            }
          }
        }
      }
    });
  });

  /**
   * When a message is edited...
   * 1) If that message was in DMs, and we have a thread open with that user, post the edit as a system message in the thread
   * 2) If that message was moderator chatter in the thread, update the corresponding chat message in the DB
   */
  bot.on("messageUpdate", async (msg, oldMessage) => {
    if (! msg || ! msg.author) return;
    if (msg.author.id === bot.user.id) return;
    if (await blocked.isBlocked(msg.author.id)) return;
    if (! msg.content) return;

    const channel = await getOrFetchChannel(bot, msg.channel.id);

    // Old message content doesn't persist between bot restarts
    const oldContent = oldMessage && oldMessage.content || "*Unavailable due to bot restart*";
    const newContent = msg.content;

    // Ignore edit events with changes only in embeds etc.
    if (newContent.trim() === oldContent.trim()) return;

    // 1) If this edit was in DMs
    if (! msg.author.bot && channel instanceof Eris.PrivateChannel) {
      const thread = await threads.findOpenThreadByUserId(msg.author.id);
      if (! thread) return;

      const editMessage = utils.disableLinkPreviews(`**The user edited their message:**\n\`B:\` ${oldContent}\n\`A:\` ${newContent}`);
      thread.postSystemMessage(editMessage);
    }

    // 2) If this edit was a chat message in the thread
    else if (await utils.messageIsOnInboxServer(bot, msg) && (msg.author.bot || utils.isStaff(msg.member))) {
      const thread = await threads.findOpenThreadByChannelId(msg.channel.id);
      if (! thread) return;

      thread.updateChatMessageInLogs(msg);
    }
  });

  /**
   * When a staff message is deleted in a modmail thread, delete it from the database as well
   */
  bot.on("messageDelete", async msg => {
    if (! msg.author) return;
    if (msg.author.id === bot.user.id) return;
    if (! await utils.messageIsOnInboxServer(bot, msg)) return;
    if (! msg.author.bot && ! utils.isStaff(msg.member)) return;

    const thread = await threads.findOpenThreadByChannelId(msg.channel.id);
    if (! thread) return;

    thread.deleteChatMessageFromLogs(msg.id);
  });

  /**
   * When the bot is mentioned on the main server, ping staff in the log channel about it
   */
  bot.on("messageCreate", async msg => {
    const channel = await getOrFetchChannel(bot, msg.channel.id);
    if (! await utils.messageIsOnMainServer(bot, msg)) return;
    if (! msg.mentions.some(user => user.id === bot.user.id)) return;
    if (msg.author.bot) return;

    if (await utils.messageIsOnInboxServer(bot, msg)) {
      // For same server setups, check if the person who pinged modmail is staff. If so, ignore the ping.
      if (utils.isStaff(msg.member)) return;
    } else {
      // For separate server setups, check if the member is staff on the modmail server
      const inboxMember = utils.getInboxGuild().members.get(msg.author.id);
      if (inboxMember && utils.isStaff(inboxMember)) return;
    }

    // If the person who mentioned the bot is blocked, ignore them
    if (await blocked.isBlocked(msg.author.id)) return;

    let content;
    const mainGuilds = utils.getMainGuilds();
    const staffMention = (config.pingOnBotMention ? utils.getInboxMention() : "");
    const allowedMentions = (config.pingOnBotMention ? utils.getInboxMentionAllowedMentions() : undefined);

    const userMentionStr = `**${msg.author.username}#${msg.author.discriminator}** (\`${msg.author.id}\`)`;
    const messageLink = `https:\/\/discordapp.com\/channels\/${channel.guild.id}\/${channel.id}\/${msg.id}`;

    if (mainGuilds.length === 1) {
        content = `${staffMention}Bot mentioned in ${channel.mention} by ${userMentionStr}: "${msg.cleanContent}"\n\n<${messageLink}>`;
    } else {
        content = `${staffMention}Bot mentioned in ${channel.mention} (${channel.guild.name}) by ${userMentionStr}: "${msg.cleanContent}"\n\n<${messageLink}>`;
    }

    bot.createMessage(utils.getLogChannel().id, {
      content,
      allowedMentions,
    });

    // Send an auto-response to the mention, if enabled
    if (config.botMentionResponse) {
      const botMentionResponse = utils.readMultilineConfigValue(config.botMentionResponse);
      bot.createMessage(channel.id, botMentionResponse.replace(/{userMention}/g, `<@${msg.author.id}>`));
    }

    // If configured, automatically open a new thread with a user who has pinged it
    if (config.createThreadOnMention) {
      const existingThread = await threads.findOpenThreadByUserId(msg.author.id);
      if (! existingThread) {
        // Only open a thread if we don't already have one
        const createdThread = await threads.createNewThreadForUser(msg.author, { quiet: true });
        await createdThread.postSystemMessage(`This thread was opened from a bot mention in <#${channel.id}>`);
        await createdThread.receiveUserReply(msg);
      }
    }
  });
}

function initUpdateNotifications() {
  if (config.updateNotifications) {
    updates.startVersionRefreshLoop();
  }
}

function getBasePlugins() {
  return [
    "file:./src/modules/reply",
    "file:./src/modules/close",
    "file:./src/modules/logs",
    "file:./src/modules/block",
    "file:./src/modules/move",
    "file:./src/modules/snippets",
    "file:./src/modules/suspend",
    "file:./src/modules/greeting",
    "file:./src/modules/webserverPlugin",
    "file:./src/modules/typingProxy",
    "file:./src/modules/version",
    "file:./src/modules/newthread",
    "file:./src/modules/id",
    "file:./src/modules/alert",
    "file:./src/modules/joinLeaveNotification",
    "file:./src/modules/roles",
  ];
}

function getExternalPlugins() {
  return config.plugins;
}

function getAllPlugins() {
  return [...getBasePlugins(), ...getExternalPlugins()];
}

async function installAllPlugins() {
  const plugins = getAllPlugins();
  await installPlugins(plugins);
}

async function loadAllPlugins() {
  // Initialize command manager
  const commands = createCommandManager(bot);

  // Register command aliases
  if (config.commandAliases) {
    for (const alias in config.commandAliases) {
      commands.addAlias(config.commandAliases[alias], alias);
    }
  }

  // Load plugins
  const basePlugins = getBasePlugins();
  const externalPlugins = getExternalPlugins();
  const plugins = getAllPlugins();

  const pluginApi = getPluginAPI({ bot, knex, config, commands });
  await loadPlugins([...basePlugins, ...externalPlugins], pluginApi);

  return {
    loadedCount: plugins.length,
    baseCount: basePlugins.length,
    externalCount: externalPlugins.length,
  };
}
<commit_msg>Fix {userMention} ping in botMentionResponse (#555)<commit_after>const Eris = require("eris");
const path = require("path");

const config = require("./cfg");
const bot = require("./bot");
const knex = require("./knex");
const {messageQueue} = require("./queue");
const utils = require("./utils");
const { createCommandManager } = require("./commands");
const { getPluginAPI, installPlugins, loadPlugins } = require("./plugins");
const { callBeforeNewThreadHooks } = require("./hooks/beforeNewThread");

const blocked = require("./data/blocked");
const threads = require("./data/threads");
const updates = require("./data/updates");

const {ACCIDENTAL_THREAD_MESSAGES} = require("./data/constants");
const {getOrFetchChannel} = require("./utils");

module.exports = {
  async start() {
    console.log("Preparing plugins...");
    await installAllPlugins();

    console.log("Connecting to Discord...");

    bot.once("ready", async () => {
      console.log("Connected! Waiting for servers to become available...");

      await (new Promise(resolve => {
        const waitNoteTimeout = setTimeout(() => {
          console.log("Servers did not become available after 15 seconds, continuing start-up anyway");
          console.log("");

          const isSingleServer = config.mainServerId.includes(config.inboxServerId);
          if (isSingleServer) {
            console.log("WARNING: The bot will not work before it's invited to the server.");
          } else {
            const hasMultipleMainServers = config.mainServerId.length > 1;
            if (hasMultipleMainServers) {
              console.log("WARNING: The bot will not function correctly until it's invited to *all* main servers and the inbox server.");
            } else {
              console.log("WARNING: The bot will not function correctly until it's invited to *both* the main server and the inbox server.");
            }
          }

          console.log("");

          resolve();
        }, 15 * 1000);

        Promise.all([
          ...config.mainServerId.map(id => waitForGuild(id)),
          waitForGuild(config.inboxServerId),
        ]).then(() => {
          clearTimeout(waitNoteTimeout);
          resolve();
        });
      }));

      console.log("Initializing...");
      initStatus();
      initBaseMessageHandlers();
      initUpdateNotifications();

      console.log("Loading plugins...");
      const pluginResult = await loadAllPlugins();
      console.log(`Loaded ${pluginResult.loadedCount} plugins (${pluginResult.baseCount} built-in plugins, ${pluginResult.externalCount} external plugins)`);

      console.log("");
      console.log("Done! Now listening to DMs.");
      console.log("");
    });

    bot.connect();
  }
};

function waitForGuild(guildId) {
  if (bot.guilds.has(guildId)) {
    return Promise.resolve();
  }

  return new Promise(resolve => {
    bot.on("guildAvailable", guild => {
      if (guild.id === guildId) {
        resolve();
      }
    });
  });
}

function initStatus() {
  function applyStatus() {
    const type = {
      "playing": 0,
      "watching": 3,
      "listening": 2,
      "streaming": 1,
    }[config.statusType] || 0;
    type == 1 ? bot.editStatus(null, {name: config.status, type, url: config.statusUrl}) : bot.editStatus(null, {name: config.status, type});
  }

  if (config.status == null || config.status === "" || config.status === "none" || config.status === "off") {
    return;
  }

  // Set the bot status initially, then reapply it every hour since in some cases it gets unset
  applyStatus();
  setInterval(applyStatus, 60 * 60 * 1000);
}

function initBaseMessageHandlers() {
  /**
   * When a moderator posts in a modmail thread...
   * 1) If alwaysReply is enabled, reply to the user
   * 2) If alwaysReply is disabled, save that message as a chat message in the thread
   */
  bot.on("messageCreate", async msg => {
    if (! await utils.messageIsOnInboxServer(bot, msg)) return;
    if (msg.author.id === bot.user.id) return;

    const thread = await threads.findByChannelId(msg.channel.id);
    if (! thread) return;

    if (! msg.author.bot && (msg.content.startsWith(config.prefix) || msg.content.startsWith(config.snippetPrefix))) {
      // Save commands as "command messages"
      thread.saveCommandMessageToLogs(msg);
    } else if (! msg.author.bot && config.alwaysReply) {
      // AUTO-REPLY: If config.alwaysReply is enabled, send all chat messages in thread channels as replies
      if (! utils.isStaff(msg.member)) return; // Only staff are allowed to reply

      const replied = await thread.replyToUser(msg.member, msg.content.trim(), msg.attachments, config.alwaysReplyAnon || false);
      if (replied) msg.delete();
    } else {
      // Otherwise just save the messages as "chat" in the logs
      thread.saveChatMessageToLogs(msg);
    }
  });

  /**
   * When we get a private message...
   * 1) Find the open modmail thread for this user, or create a new one
   * 2) Post the message as a user reply in the thread
   */
  bot.on("messageCreate", async msg => {
    const channel = await getOrFetchChannel(bot, msg.channel.id);
    if (! (channel instanceof Eris.PrivateChannel)) return;
    if (msg.author.bot) return;
    if (msg.type !== 0) return; // Ignore pins etc.

    if (await blocked.isBlocked(msg.author.id)) return;

    // Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels being created
    messageQueue.add(async () => {
      let thread = await threads.findOpenThreadByUserId(msg.author.id);
      const createNewThread = (thread == null);

      // New thread
      if (createNewThread) {
        // Ignore messages that shouldn't usually open new threads, such as "ok", "thanks", etc.
        if (config.ignoreAccidentalThreads && msg.content && ACCIDENTAL_THREAD_MESSAGES.includes(msg.content.trim().toLowerCase())) return;

        thread = await threads.createNewThreadForUser(msg.author, {
          source: "dm",
          message: msg,
        });
      }

      if (thread) {
        await thread.receiveUserReply(msg);

        if (createNewThread) {
          // Send auto-reply to the user
          if (config.responseMessage) {
            const responseMessage = utils.readMultilineConfigValue(config.responseMessage);

            try {
              const postToThreadChannel = config.showResponseMessageInThreadChannel;
              await thread.sendSystemMessageToUser(responseMessage, { postToThreadChannel });
            } catch (err) {
              await thread.postSystemMessage(`**NOTE:** Could not send auto-response to the user. The error given was: \`${err.message}\``);
            }
          }
        }
      }
    });
  });

  /**
   * When a message is edited...
   * 1) If that message was in DMs, and we have a thread open with that user, post the edit as a system message in the thread
   * 2) If that message was moderator chatter in the thread, update the corresponding chat message in the DB
   */
  bot.on("messageUpdate", async (msg, oldMessage) => {
    if (! msg || ! msg.author) return;
    if (msg.author.id === bot.user.id) return;
    if (await blocked.isBlocked(msg.author.id)) return;
    if (! msg.content) return;

    const channel = await getOrFetchChannel(bot, msg.channel.id);

    // Old message content doesn't persist between bot restarts
    const oldContent = oldMessage && oldMessage.content || "*Unavailable due to bot restart*";
    const newContent = msg.content;

    // Ignore edit events with changes only in embeds etc.
    if (newContent.trim() === oldContent.trim()) return;

    // 1) If this edit was in DMs
    if (! msg.author.bot && channel instanceof Eris.PrivateChannel) {
      const thread = await threads.findOpenThreadByUserId(msg.author.id);
      if (! thread) return;

      const editMessage = utils.disableLinkPreviews(`**The user edited their message:**\n\`B:\` ${oldContent}\n\`A:\` ${newContent}`);
      thread.postSystemMessage(editMessage);
    }

    // 2) If this edit was a chat message in the thread
    else if (await utils.messageIsOnInboxServer(bot, msg) && (msg.author.bot || utils.isStaff(msg.member))) {
      const thread = await threads.findOpenThreadByChannelId(msg.channel.id);
      if (! thread) return;

      thread.updateChatMessageInLogs(msg);
    }
  });

  /**
   * When a staff message is deleted in a modmail thread, delete it from the database as well
   */
  bot.on("messageDelete", async msg => {
    if (! msg.author) return;
    if (msg.author.id === bot.user.id) return;
    if (! await utils.messageIsOnInboxServer(bot, msg)) return;
    if (! msg.author.bot && ! utils.isStaff(msg.member)) return;

    const thread = await threads.findOpenThreadByChannelId(msg.channel.id);
    if (! thread) return;

    thread.deleteChatMessageFromLogs(msg.id);
  });

  /**
   * When the bot is mentioned on the main server, ping staff in the log channel about it
   */
  bot.on("messageCreate", async msg => {
    const channel = await getOrFetchChannel(bot, msg.channel.id);
    if (! await utils.messageIsOnMainServer(bot, msg)) return;
    if (! msg.mentions.some(user => user.id === bot.user.id)) return;
    if (msg.author.bot) return;

    if (await utils.messageIsOnInboxServer(bot, msg)) {
      // For same server setups, check if the person who pinged modmail is staff. If so, ignore the ping.
      if (utils.isStaff(msg.member)) return;
    } else {
      // For separate server setups, check if the member is staff on the modmail server
      const inboxMember = utils.getInboxGuild().members.get(msg.author.id);
      if (inboxMember && utils.isStaff(inboxMember)) return;
    }

    // If the person who mentioned the bot is blocked, ignore them
    if (await blocked.isBlocked(msg.author.id)) return;

    let content;
    const mainGuilds = utils.getMainGuilds();
    const staffMention = (config.pingOnBotMention ? utils.getInboxMention() : "");
    const allowedMentions = (config.pingOnBotMention ? utils.getInboxMentionAllowedMentions() : undefined);

    const userMentionStr = `**${msg.author.username}#${msg.author.discriminator}** (\`${msg.author.id}\`)`;
    const messageLink = `https:\/\/discordapp.com\/channels\/${channel.guild.id}\/${channel.id}\/${msg.id}`;

    if (mainGuilds.length === 1) {
        content = `${staffMention}Bot mentioned in ${channel.mention} by ${userMentionStr}: "${msg.cleanContent}"\n\n<${messageLink}>`;
    } else {
        content = `${staffMention}Bot mentioned in ${channel.mention} (${channel.guild.name}) by ${userMentionStr}: "${msg.cleanContent}"\n\n<${messageLink}>`;
    }

    bot.createMessage(utils.getLogChannel().id, {
      content,
      allowedMentions,
    });

    // Send an auto-response to the mention, if enabled
    if (config.botMentionResponse) {
      const botMentionResponse = utils.readMultilineConfigValue(config.botMentionResponse);
      bot.createMessage(channel.id, {
        content: botMentionResponse.replace(/{userMention}/g, `<@${msg.author.id}>`),
        allowedMentions: {
          users: [msg.author.id]
        }
      });
    }

    // If configured, automatically open a new thread with a user who has pinged it
    if (config.createThreadOnMention) {
      const existingThread = await threads.findOpenThreadByUserId(msg.author.id);
      if (! existingThread) {
        // Only open a thread if we don't already have one
        const createdThread = await threads.createNewThreadForUser(msg.author, { quiet: true });
        await createdThread.postSystemMessage(`This thread was opened from a bot mention in <#${channel.id}>`);
        await createdThread.receiveUserReply(msg);
      }
    }
  });
}

function initUpdateNotifications() {
  if (config.updateNotifications) {
    updates.startVersionRefreshLoop();
  }
}

function getBasePlugins() {
  return [
    "file:./src/modules/reply",
    "file:./src/modules/close",
    "file:./src/modules/logs",
    "file:./src/modules/block",
    "file:./src/modules/move",
    "file:./src/modules/snippets",
    "file:./src/modules/suspend",
    "file:./src/modules/greeting",
    "file:./src/modules/webserverPlugin",
    "file:./src/modules/typingProxy",
    "file:./src/modules/version",
    "file:./src/modules/newthread",
    "file:./src/modules/id",
    "file:./src/modules/alert",
    "file:./src/modules/joinLeaveNotification",
    "file:./src/modules/roles",
  ];
}

function getExternalPlugins() {
  return config.plugins;
}

function getAllPlugins() {
  return [...getBasePlugins(), ...getExternalPlugins()];
}

async function installAllPlugins() {
  const plugins = getAllPlugins();
  await installPlugins(plugins);
}

async function loadAllPlugins() {
  // Initialize command manager
  const commands = createCommandManager(bot);

  // Register command aliases
  if (config.commandAliases) {
    for (const alias in config.commandAliases) {
      commands.addAlias(config.commandAliases[alias], alias);
    }
  }

  // Load plugins
  const basePlugins = getBasePlugins();
  const externalPlugins = getExternalPlugins();
  const plugins = getAllPlugins();

  const pluginApi = getPluginAPI({ bot, knex, config, commands });
  await loadPlugins([...basePlugins, ...externalPlugins], pluginApi);

  return {
    loadedCount: plugins.length,
    baseCount: basePlugins.length,
    externalCount: externalPlugins.length,
  };
}
<|endoftext|>


<commit_before>require('dotenv').load();

const craftai = require('craft-ai').createClient;
const fs = require('fs');
const path = require('path');
const process = require('process');

var utils = require('./utils');

const ROOM1 = 'BEDROOM_1';
const ROOM2 = 'LIVING_ROOM';
const ROOM3 = 'RESTROOM';

const GENERATOR_FILTER = [ROOM1, ROOM2, ROOM3];
const GENERATOR_NAME = 'smarthome';

const LOCAL_FILE_PATH1 = path.join(__dirname, `../data/twor_${ROOM1}.json`);
const LOCAL_FILE_PATH2 = path.join(__dirname, `../data/twor_${ROOM2}.json`);
const LOCAL_FILE_PATH3 = path.join(__dirname, `../data/twor_${ROOM3}.json`);

// 0 - Create the craft client
const CRAFT_CLIENT = craftai(process.env.CRAFT_TOKEN);

// 1 - Retrieve the prepared data
utils.readData(LOCAL_FILE_PATH1)
  .then((context) => {
    const agentName = ROOM1;
    // 2 - Cleanup the mess (agent's name can't be duplicate)
    return CRAFT_CLIENT.deleteAgent(agentName)
      // 3 - Create the agent 1
      .then(() => {
        console.log(`Creating agent ${agentName}.`);
        return CRAFT_CLIENT.createAgent({
          context: {
            movement: {
              type: 'continuous'
            },
            light: {
              type: 'enum'
            },
            tz: {
              type: 'timezone'
            },
            time: {
              type: 'time_of_day'
            }
          },
          output: ['light'],
          time_quantum: 15 * 60 // 15 min
        }, agentName);
      })
      // 4 - Send the dataset's operations
      .then(() => {
        console.log(`Adding context operations for agent ${agentName}...`);
        return CRAFT_CLIENT.addAgentContextOperations(agentName, context);
      })
      .then(() => {
        console.log(`Computing the decision model for agent ${agentName} (this may take a little while)...`);
        // 5 - Compute decision from the decision tree in order to automate the light
        // Download the tree
        return CRAFT_CLIENT.getAgentDecisionTree(agentName, context[context.length - 1].timestamp, 2);
      })
      .then((tree) => {
        console.log('Decision tree computed!');
        // 6 - Get decisions
        const d1 = craftai.interpreter.decide(
          tree,
          {
            movement: 0
          },
          new craftai.Time('2010-01-04T01:30:00+09:00')
        );
        console.log(`Decision taken:\n- The light is ${d1.output.light.predicted_value} when there is no movement at 1:30AM.`);
        const d2 = craftai.interpreter.decide(
          tree,
          {
            movement: 0
          },
          new craftai.Time('2010-01-04T08:58:00+09:00')
        );
        console.log(`- The light is ${d2.output.light.predicted_value} when there is no movement at 8:58AM.`);
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 0
            },
            new craftai.Time('2010-01-04T09:42:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is no movement at 9:42AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 2
            },
            new craftai.Time('2010-01-04T09:42:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is some movement at 9:42AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 2
            },
            new craftai.Time('2010-01-04T17:03:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is some movement at 5:03PM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 2
            },
            new craftai.Time('2010-01-04T20:55:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is some movement at 8:55PM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 2
            },
            new craftai.Time('2010-01-04T22:07:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is some movement at 10:07PM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 10
            },
            new craftai.Time('2010-01-04T09:42:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is a lot of movement at 9:42AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 10
            },
            new craftai.Time('2010-01-04T02:17:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is a lot of movement at 2:17AM.`);
        }
      })
      .then(() => {
        const deleteBulkPayload = [
          { id: ROOM1 },
          { id: ROOM2 },
          { id: ROOM3 }
        ];
        return CRAFT_CLIENT.deleteAgentBulk(deleteBulkPayload);
      })
      // 7 - Create Agent 1, 2 and 3
      .then(() => utils.readData(LOCAL_FILE_PATH1))
      .then((context_1) => {
        operations_agent_1 = utils.prepareData(context_1, ROOM1);
        return utils.readData(LOCAL_FILE_PATH2);
      })
      .then((context_2) => {
        operations_agent_2 = utils.prepareData(context_2, ROOM2);
        return utils.readData(LOCAL_FILE_PATH3);
      })
      .then((context_3) => {
        operations_agent_3 = utils.prepareData(context_3, ROOM3);
        const configuration = {
          context: {
            movement: {
              type: 'continuous'
            },
            stateChange: {
              type: 'enum'
            },
            room: {
              type: 'enum'
            },
            tz: {
              type: 'timezone'
            },
            time: {
              type: 'time_of_day'
            }
          },
          output: ['stateChange'],
          time_quantum: 15 * 60 // 15 min
        };
        const createBulkPayload = [
          { id: ROOM1, configuration: configuration },
          { id: ROOM2, configuration: configuration },
          { id: ROOM3, configuration: configuration }
        ];
        console.log(`Creating agents ${ROOM1}, ${ROOM2} and ${ROOM3}.`);
        return CRAFT_CLIENT.createAgentBulk(createBulkPayload)
      })
      .then(() => {
        const contextOperationBulkPayload = [
          { id: ROOM1, operations: operations_agent_1 },
          { id: ROOM2, operations: operations_agent_2 },
          { id: ROOM3, operations: operations_agent_3 }
        ];
        console.log(`Adding context operations for agents ${ROOM1}, ${ROOM2} and ${ROOM3}.`);
        return CRAFT_CLIENT.addAgentContextOperationsBulk(contextOperationBulkPayload)
      })
      .then(() => {
        const GENERATOR_CONFIGURATION = {
          context: {
            stateChange: {
              type: 'enum'
            },
            room: {
              type: 'enum'
            },
            tz: {
              type: 'timezone'
            },
            time: {
              type: 'time_of_day',
              is_generated: true
            }
          },
          output: [
            "stateChange"
          ],
          learning_period: 1500000,
          tree_max_operations: 15000,
          operations_as_events: true,
          filter: GENERATOR_FILTER
        };
        console.log(`Creating generator ${GENERATOR_NAME}.`);
        // 8 - Create generator
        return CRAFT_CLIENT.createGenerator(GENERATOR_CONFIGURATION, GENERATOR_NAME);
      })
      .then(() => {
        console.log(`Computing the decision model for generator ${GENERATOR_NAME} (this may take a little while)...`);
        // 9 - Compute decision from the decision tree in order to automate the light
        // Download the tree
        return CRAFT_CLIENT.getGeneratorDecisionTree(GENERATOR_NAME, context[context.length - 1].timestamp);
      })
      .then((tree) => {
        console.log('Generator decision tree computed!');
        // 10 - Get decisions
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              room: ROOM1
            },
            new craftai.Time('2010-01-04T01:30:00+09:00')
          );
          console.log(`- A change of state ${d.output.stateChange.predicted_value} is expected in the bedroom at 1:30AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              room: ROOM2
            },
            new craftai.Time('2010-01-04T08:58:00+09:00')
          );
          console.log(`- A change of state ${d.output.stateChange.predicted_value} is expected in the living room at 8:58AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              room: ROOM3
            },
            new craftai.Time('2010-01-04T09:42:00+09:00')
          );
          console.log(`- A change of state ${d.output.stateChange.predicted_value} is expected in the restroom at 9:42AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              room: ROOM1
            },
            new craftai.Time('2010-01-08T02:17:00+09:00')
          );
          console.log(`- A change of state ${d.output.stateChange.predicted_value} is expected in the bedroom at 2:17AM.`);
        }
      })
  })
  .catch((error) => {
    console.log('Error!', error);
    process.exit(1);
  });
<commit_msg>Update src/main.js<commit_after>require('dotenv').load();

const craftai = require('craft-ai').createClient;
const fs = require('fs');
const path = require('path');
const process = require('process');

var utils = require('./utils');

const ROOM1 = 'BEDROOM_1';
const ROOM2 = 'LIVING_ROOM';
const ROOM3 = 'RESTROOM';

const GENERATOR_FILTER = [ROOM1, ROOM2, ROOM3];
const GENERATOR_NAME = 'smarthome';

const LOCAL_FILE_PATH1 = path.join(__dirname, `../data/twor_${ROOM1}.json`);
const LOCAL_FILE_PATH2 = path.join(__dirname, `../data/twor_${ROOM2}.json`);
const LOCAL_FILE_PATH3 = path.join(__dirname, `../data/twor_${ROOM3}.json`);

// 0 - Create the craft client
const CRAFT_CLIENT = craftai(process.env.CRAFT_TOKEN);

// 1 - Retrieve the prepared data
utils.readData(LOCAL_FILE_PATH1)
  .then((context) => {
    const agentName = ROOM1;
    // 2 - Cleanup the mess (agent's name can't be duplicate)
    return CRAFT_CLIENT.deleteAgent(agentName)
      // 3 - Create the agent 1
      .then(() => {
        console.log(`Creating agent ${agentName}.`);
        return CRAFT_CLIENT.createAgent({
          context: {
            movement: {
              type: 'continuous'
            },
            light: {
              type: 'enum'
            },
            tz: {
              type: 'timezone'
            },
            time: {
              type: 'time_of_day'
            }
          },
          output: ['light'],
          time_quantum: 15 * 60 // 15 min
        }, agentName);
      })
      // 4 - Send the dataset's operations
      .then(() => {
        console.log(`Adding context operations for agent ${agentName}...`);
        return CRAFT_CLIENT.addAgentContextOperations(agentName, context);
      })
      .then(() => {
        console.log(`Computing the decision model for agent ${agentName} (this may take a little while)...`);
        // 5 - Compute decision from the decision tree in order to automate the light
        // Download the tree
        return CRAFT_CLIENT.getAgentDecisionTree(agentName, context[context.length - 1].timestamp, 2);
      })
      .then((tree) => {
        console.log('Decision tree computed!');
        // 6 - Get decisions
        const d1 = craftai.interpreter.decide(
          tree,
          {
            movement: 0
          },
          new craftai.Time('2010-01-04T01:30:00+09:00')
        );
        console.log(`Decision taken:\n- The light is ${d1.output.light.predicted_value} when there is no movement at 1:30AM.`);
        const d2 = craftai.interpreter.decide(
          tree,
          {
            movement: 0
          },
          new craftai.Time('2010-01-04T08:58:00+09:00')
        );
        console.log(`- The light is ${d2.output.light.predicted_value} when there is no movement at 8:58AM.`);
        const d3 = craftai.interpreter.decide(
          tree,
          {
            movement: 0
          },
          new craftai.Time('2010-01-04T09:42:00+09:00')
        );
        console.log(`- The light is ${d3.output.light.predicted_value} when there is no movement at 9:42AM.`);
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 2
            },
            new craftai.Time('2010-01-04T09:42:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is some movement at 9:42AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 2
            },
            new craftai.Time('2010-01-04T17:03:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is some movement at 5:03PM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 2
            },
            new craftai.Time('2010-01-04T20:55:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is some movement at 8:55PM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 2
            },
            new craftai.Time('2010-01-04T22:07:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is some movement at 10:07PM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 10
            },
            new craftai.Time('2010-01-04T09:42:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is a lot of movement at 9:42AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              movement: 10
            },
            new craftai.Time('2010-01-04T02:17:00+09:00')
          );
          console.log(`- The light is ${d.output.light.predicted_value} when there is a lot of movement at 2:17AM.`);
        }
      })
      .then(() => {
        const deleteBulkPayload = [
          { id: ROOM1 },
          { id: ROOM2 },
          { id: ROOM3 }
        ];
        return CRAFT_CLIENT.deleteAgentBulk(deleteBulkPayload);
      })
      // 7 - Create Agent 1, 2 and 3
      .then(() => utils.readData(LOCAL_FILE_PATH1))
      .then((context_1) => {
        operations_agent_1 = utils.prepareData(context_1, ROOM1);
        return utils.readData(LOCAL_FILE_PATH2);
      })
      .then((context_2) => {
        operations_agent_2 = utils.prepareData(context_2, ROOM2);
        return utils.readData(LOCAL_FILE_PATH3);
      })
      .then((context_3) => {
        operations_agent_3 = utils.prepareData(context_3, ROOM3);
        const configuration = {
          context: {
            movement: {
              type: 'continuous'
            },
            stateChange: {
              type: 'enum'
            },
            room: {
              type: 'enum'
            },
            tz: {
              type: 'timezone'
            },
            time: {
              type: 'time_of_day'
            }
          },
          output: ['stateChange'],
          time_quantum: 15 * 60 // 15 min
        };
        const createBulkPayload = [
          { id: ROOM1, configuration: configuration },
          { id: ROOM2, configuration: configuration },
          { id: ROOM3, configuration: configuration }
        ];
        console.log(`Creating agents ${ROOM1}, ${ROOM2} and ${ROOM3}.`);
        return CRAFT_CLIENT.createAgentBulk(createBulkPayload)
      })
      .then(() => {
        const contextOperationBulkPayload = [
          { id: ROOM1, operations: operations_agent_1 },
          { id: ROOM2, operations: operations_agent_2 },
          { id: ROOM3, operations: operations_agent_3 }
        ];
        console.log(`Adding context operations for agents ${ROOM1}, ${ROOM2} and ${ROOM3}.`);
        return CRAFT_CLIENT.addAgentContextOperationsBulk(contextOperationBulkPayload)
      })
      .then(() => {
        const GENERATOR_CONFIGURATION = {
          context: {
            stateChange: {
              type: 'enum'
            },
            room: {
              type: 'enum'
            },
            tz: {
              type: 'timezone'
            },
            time: {
              type: 'time_of_day',
              is_generated: true
            }
          },
          output: [
            "stateChange"
          ],
          learning_period: 1500000,
          tree_max_operations: 15000,
          operations_as_events: true,
          filter: GENERATOR_FILTER
        };
        console.log(`Creating generator ${GENERATOR_NAME}.`);
        // 8 - Create generator
        return CRAFT_CLIENT.createGenerator(GENERATOR_CONFIGURATION, GENERATOR_NAME);
      })
      .then(() => {
        console.log(`Computing the decision model for generator ${GENERATOR_NAME} (this may take a little while)...`);
        // 9 - Compute decision from the decision tree in order to automate the light
        // Download the tree
        return CRAFT_CLIENT.getGeneratorDecisionTree(GENERATOR_NAME, context[context.length - 1].timestamp);
      })
      .then((tree) => {
        console.log('Generator decision tree computed!');
        // 10 - Get decisions
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              room: ROOM1
            },
            new craftai.Time('2010-01-04T01:30:00+09:00')
          );
          console.log(`- A change of state ${d.output.stateChange.predicted_value} is expected in the bedroom at 1:30AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              room: ROOM2
            },
            new craftai.Time('2010-01-04T08:58:00+09:00')
          );
          console.log(`- A change of state ${d.output.stateChange.predicted_value} is expected in the living room at 8:58AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              room: ROOM3
            },
            new craftai.Time('2010-01-04T09:42:00+09:00')
          );
          console.log(`- A change of state ${d.output.stateChange.predicted_value} is expected in the restroom at 9:42AM.`);
        }
        {
          const d = craftai.interpreter.decide(
            tree,
            {
              room: ROOM1
            },
            new craftai.Time('2010-01-08T02:17:00+09:00')
          );
          console.log(`- A change of state ${d.output.stateChange.predicted_value} is expected in the bedroom at 2:17AM.`);
        }
      })
  })
  .catch((error) => {
    console.log('Error!', error);
    process.exit(1);
  });
<|endoftext|>


<commit_before>var sizeOf = require('image-size'),
	Q = require('q'),
	http = require('http'),
	exec = require('child_process').exec,
	fs = require('fs'),
    path = require('path'),
    Base62 = require('base62'),
    port = 8080;

// spawn server
http.createServer(function (req, res) {
	// argument stream: /fetch/filename hash/file name to download as
	var pdfExp = /^\/fetch\/(.*)\/(.*)/;

	// resolve route
	if (req.url === '/create-pdf') {
		// request validation
		validateRequest(req)
			// parse body stream and convert to JSON
			.then(getJsonRequestBody)
			// prepare files and merge
			.then(responseHandler(req, res))
			// success handler
			.then(function (data) {
				outputResponse(res, 200, {
					url: 'http://' + req.headers.host + '/fetch/' + data.hash + '/' + encodeURIComponent(data.name)
				});
			})
			// fail handler
			.catch(function (e) {
				outputResponse(res, 500, {data: e.toString()});
			});
	} else if (pdfExp.test(req.url)) {
		// get path and stream out data
		pdfExists(req.url.match(pdfExp)[1])
			.then(function (path) {
				// get file stat
				var stat = fs.statSync(path);

				// headers
				res.writeHead(200, {
					'Content-Type': 'application/pdf',
					'Content-Length': stat.size,
					// force download the file
					'Content-Disposition': 'attachment; filename="' + (req.url.match(pdfExp)[2]) + '.pdf"'
				});

				// stream out file
				fs.createReadStream(path).pipe(res);
			})
			.catch(function (e) {
				outputResponse(res, 500, {data: e.toString()});
			});
	} else {
		outputResponse(res, 404, {data: 'page does not exist'});
	}
}).listen(port, '0.0.0.0');

/**
 * Validates PDF
 * @todo  probably overkill to use a defer for this, but I may need to do additional checks
 *        in the future if files are off loaded to external server
 * @param  {String} fileName
 * @return {Object} promise
 */
function pdfExists(fileName) {
	var deferred = Q.defer(),
		// construct path
		filePath = path.join(__dirname, 'output', fileName + '.pdf');

	try {
		// check if file exists
		if (!fs.existsSync(filePath)) {
			throw new Error('PDF does not exist');
		}

		deferred.resolve(filePath);
	} catch (e) {
		deferred.reject(e);
	}

	return deferred.promise;
}

/**
 * Outputs HTTP response
 * @param  {Object} res
 * @param  {Number} status
 * @param  {Object} data
 * @return {undefined}
 */
function outputResponse(res, status, data) {
	res.writeHead(status, {
		'Content-Type': 'application/json'
	});

	// resolve to provided response, if non provide generic
	res.end(JSON.stringify(data || {'data': 'OK'}));
}

/**
 * Validates Request
 * @param  {Object} req
 * @return {Object}
 */
function validateRequest(req) {
	var deferred = Q.defer();

	try {
		// post check
		if (req.method !== 'POST') {
			throw new Error('HTTP method must be a POST');
		}

		deferred.resolve(req);
	} catch (e) {
		deferred.reject(e);
	}

	return deferred.promise;
}

/**
 * Parses raw body stream into JSON
 * @param  {Object} req
 * @return {Function}
 */
function getJsonRequestBody(req) {
	var deferred = Q.defer();

	var responseBody = '';

	req.on('data', function (data) {
		responseBody += data;
	});

	req.on('end', function () {
		deferred.resolve(JSON.parse(responseBody));
	});

	return deferred.promise;
}

/**
 * Handles executing PDF actions
 * @param  {Object} req
 * @param  {Object} res
 * @return {Function}
 */
function responseHandler(req, res) {
	return function (data) {
		return prepareFiles(data.inputFiles)
			.then(mergePdfs)
			.then(function (hash) {
				return {
					"name": data.uniquePkgName,
					"hash": hash
				}
			});
	};
}

/**
 * Execute PhantomJS to PDF service
 * Arguments are forwarded onto execution args
 *
 * @return {Object} promise
 */
function phantom() {
	var deferred = Q.defer();

	exec(['phantomjs', __dirname + '/rasterize.js'].concat([].slice.call(arguments)).join(' '), function (err) {
		// error handler
		if (err) {
			console.log('endpoint failed to translate to PDF');
			deferred.reject(err);
		}

		deferred.resolve(arguments[0]); // return output path
	});

	return deferred.promise;
}

/**
 * Translates all files to individual PDFs then moves them to `tmp`
 * @todo  abstract out PDFer and let this resolve moving files
 * @param  {Array} files
 * @return {Object} promise
 */
function prepareFiles(files) {
	console.log('...preparing files for normalization', files);

	function basename(file) {
		return file.substr(0, file.lastIndexOf('.'));
	}

	function isPdf(file) {
		return file.substr(-4) === '.pdf';
	}

	function isHttpProtocol(file) {
		return file.substr(0, 7) === 'http://' || file.substr(0, 8) === 'https://';
	}

    function fileProcess(file) {
        var deferred = Q.defer();

        function resolve(inputPath) {
            deferred.resolve(inputPath);
        }

        // PDF a website
        if (isHttpProtocol(file)) {
            var ouputFilename = guidGenerator() + '.pdf';

            phantom(file, ouputFilename, '2000px*3000px').then(function () {
                resolve(ouputFilename);
            });

            // if its a PDF, skip it
            // we are assuming all input files are JPG or PNG
        } else if (!isPdf(file)) {
            // configurations
            var sourcePath = __dirname + '/input/' + file,
                outputBase = __dirname + '/tmp/' + basename(file),
                size = sizeOf(sourcePath),
                xAxisLarger = size.width > size.height,
                scale = 2000,
                aspectRatio = xAxisLarger ? (size.width / size.height) : (size.height / size.width),
                errorPx = 5,
                width = (xAxisLarger ? scale : scale / aspectRatio) + errorPx,
                height = (xAxisLarger ? scale / aspectRatio : scale) + errorPx;

            // validate path
            if (!fs.existsSync(sourcePath)) {
                deferred.reject(sourcePath + ' does not exist!');

                // break loop
                return false;
            }

            // create PDF
            phantom(sourcePath, outputBase + '.pdf', width +'px*' + height + 'px').then(function () {
                exec(['pdftk', outputBase + '.pdf', 'cat', '1-1', 'output', outputBase + 'x.pdf'].join (' '), function (err) {
                    // ignore errors for this one, pdftk will fail if there isn't 2 pages
                    if (err) {
                        resolve(outputBase + '.pdf');
                    } else {
                        resolve(outputBase + 'x.pdf');
                        fs.unlinkSync(outputBase + '.pdf');
                    }
                });
            });
        } else {
            // copy it to tmp directory
            fs.createReadStream(__dirname + '/input/' + file)
                .pipe(fs.createWriteStream(__dirname + '/tmp/' + file));

            resolve(__dirname + '/tmp/' + file);
        }

        return deferred.promise;
    }

    return (function (processes) {
        return processes.reduce(Q.when, Q([]));
    }(files.map(function (file) {
        return function (list) {
            var d = Q.defer();

            fileProcess(file).then(function (path) {
                list.push(path);

                d.resolve(list);

                console.log('processed file ' + path + ' on ', new Date);
            }).catch(function () {
                d.reject('file failed');
            });

            return d.promise;
        };
    })));
}

/**
 * Merges PDFs into a single PDF from given list
 * @param  {Array} pdfList
 * @return {Object}  promise
 */
function mergePdfs(pdfList) {
	var deferred = Q.defer(),
		fileName = Base62.encode((new Date).getTime()),
		outputFile = __dirname + '/output/' + fileName + '.pdf';

	console.log('preparing to merge PDFs')

	pdfList.forEach(function (sourcePath) {
		if (!fs.existsSync(sourcePath)) {
			console.log('...  ' + sourcePath + ' does not existxxx!');
		} else {
			console.log('...  ' + sourcePath + ' exists!');
		}
	});

	// merge PDFs
	exec(['pdftk', pdfList.join(' '), 'cat', 'output', outputFile].join(' '), function (err) {
		if (err) {
			console.log('');
			console.log(err);
			deferred.reject(err);
		} else {
			// removes files in tmp
			pdfList.forEach(function (file) {
                try {
				    fs.unlinkSync(file);
                } catch (e) {
                    console.log('could not unlink ' + file);
                }
			});

			console.log('...PDFs merged: ' + outputFile);

			// return hash
			deferred.resolve(fileName);
		}
	});

	return deferred.promise;
}

function guidGenerator() {
    var S4 = function() {
        return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
    };
    return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}<commit_msg>enable socket keep-alive<commit_after>var sizeOf = require('image-size'),
	Q = require('q'),
	http = require('http'),
	exec = require('child_process').exec,
	fs = require('fs'),
    path = require('path'),
    Base62 = require('base62'),
    port = 8080;

// spawn server
http.createServer(function (req, res) {
	// enable socket keep-alive
	req.socket.setKeepAlive(true);

	// argument stream: /fetch/filename hash/file name to download as
	var pdfExp = /^\/fetch\/(.*)\/(.*)/;

	// resolve route
	if (req.url === '/create-pdf') {
		// request validation
		validateRequest(req)
			// parse body stream and convert to JSON
			.then(getJsonRequestBody)
			// prepare files and merge
			.then(responseHandler(req, res))
			// success handler
			.then(function (data) {
				outputResponse(res, 200, {
					url: 'http://' + req.headers.host + '/fetch/' + data.hash + '/' + encodeURIComponent(data.name)
				});
			})
			// fail handler
			.catch(function (e) {
				outputResponse(res, 500, {data: e.toString()});
			});
	} else if (pdfExp.test(req.url)) {
		// get path and stream out data
		pdfExists(req.url.match(pdfExp)[1])
			.then(function (path) {
				// get file stat
				var stat = fs.statSync(path);

				// headers
				res.writeHead(200, {
					'Content-Type': 'application/pdf',
					'Content-Length': stat.size,
					// force download the file
					'Content-Disposition': 'attachment; filename="' + (req.url.match(pdfExp)[2]) + '.pdf"'
				});

				// stream out file
				fs.createReadStream(path).pipe(res);
			})
			.catch(function (e) {
				outputResponse(res, 500, {data: e.toString()});
			});
	} else {
		outputResponse(res, 404, {data: 'page does not exist'});
	}
}).listen(port, '0.0.0.0');

/**
 * Validates PDF
 * @todo  probably overkill to use a defer for this, but I may need to do additional checks
 *        in the future if files are off loaded to external server
 * @param  {String} fileName
 * @return {Object} promise
 */
function pdfExists(fileName) {
	var deferred = Q.defer(),
		// construct path
		filePath = path.join(__dirname, 'output', fileName + '.pdf');

	try {
		// check if file exists
		if (!fs.existsSync(filePath)) {
			throw new Error('PDF does not exist');
		}

		deferred.resolve(filePath);
	} catch (e) {
		deferred.reject(e);
	}

	return deferred.promise;
}

/**
 * Outputs HTTP response
 * @param  {Object} res
 * @param  {Number} status
 * @param  {Object} data
 * @return {undefined}
 */
function outputResponse(res, status, data) {
	res.writeHead(status, {
		'Content-Type': 'application/json'
	});

	// resolve to provided response, if non provide generic
	res.end(JSON.stringify(data || {'data': 'OK'}));
}

/**
 * Validates Request
 * @param  {Object} req
 * @return {Object}
 */
function validateRequest(req) {
	var deferred = Q.defer();

	try {
		// post check
		if (req.method !== 'POST') {
			throw new Error('HTTP method must be a POST');
		}

		deferred.resolve(req);
	} catch (e) {
		deferred.reject(e);
	}

	return deferred.promise;
}

/**
 * Parses raw body stream into JSON
 * @param  {Object} req
 * @return {Function}
 */
function getJsonRequestBody(req) {
	var deferred = Q.defer();

	var responseBody = '';

	req.on('data', function (data) {
		responseBody += data;
	});

	req.on('end', function () {
		deferred.resolve(JSON.parse(responseBody));
	});

	return deferred.promise;
}

/**
 * Handles executing PDF actions
 * @param  {Object} req
 * @param  {Object} res
 * @return {Function}
 */
function responseHandler(req, res) {
	return function (data) {
		return prepareFiles(data.inputFiles)
			.then(mergePdfs)
			.then(function (hash) {
				return {
					"name": data.uniquePkgName,
					"hash": hash
				}
			});
	};
}

/**
 * Execute PhantomJS to PDF service
 * Arguments are forwarded onto execution args
 *
 * @return {Object} promise
 */
function phantom() {
	var deferred = Q.defer();

	exec(['phantomjs', __dirname + '/rasterize.js'].concat([].slice.call(arguments)).join(' '), function (err) {
		// error handler
		if (err) {
			console.log('endpoint failed to translate to PDF');
			deferred.reject(err);
		}

		deferred.resolve(arguments[0]); // return output path
	});

	return deferred.promise;
}

/**
 * Translates all files to individual PDFs then moves them to `tmp`
 * @todo  abstract out PDFer and let this resolve moving files
 * @param  {Array} files
 * @return {Object} promise
 */
function prepareFiles(files) {
	console.log('...preparing files for normalization', files);

	function basename(file) {
		return file.substr(0, file.lastIndexOf('.'));
	}

	function isPdf(file) {
		return file.substr(-4) === '.pdf';
	}

	function isHttpProtocol(file) {
		return file.substr(0, 7) === 'http://' || file.substr(0, 8) === 'https://';
	}

    function fileProcess(file) {
        var deferred = Q.defer();

        function resolve(inputPath) {
            deferred.resolve(inputPath);
        }

        // PDF a website
        if (isHttpProtocol(file)) {
            var ouputFilename = guidGenerator() + '.pdf';

            phantom(file, ouputFilename, '2000px*3000px').then(function () {
                resolve(ouputFilename);
            });

            // if its a PDF, skip it
            // we are assuming all input files are JPG or PNG
        } else if (!isPdf(file)) {
            // configurations
            var sourcePath = __dirname + '/input/' + file,
                outputBase = __dirname + '/tmp/' + basename(file),
                size = sizeOf(sourcePath),
                xAxisLarger = size.width > size.height,
                scale = 2000,
                aspectRatio = xAxisLarger ? (size.width / size.height) : (size.height / size.width),
                errorPx = 5,
                width = (xAxisLarger ? scale : scale / aspectRatio) + errorPx,
                height = (xAxisLarger ? scale / aspectRatio : scale) + errorPx;

            // validate path
            if (!fs.existsSync(sourcePath)) {
                deferred.reject(sourcePath + ' does not exist!');

                // break loop
                return false;
            }

            // create PDF
            phantom(sourcePath, outputBase + '.pdf', width +'px*' + height + 'px').then(function () {
                exec(['pdftk', outputBase + '.pdf', 'cat', '1-1', 'output', outputBase + 'x.pdf'].join (' '), function (err) {
                    // ignore errors for this one, pdftk will fail if there isn't 2 pages
                    if (err) {
                        resolve(outputBase + '.pdf');
                    } else {
                        resolve(outputBase + 'x.pdf');
                        fs.unlinkSync(outputBase + '.pdf');
                    }
                });
            });
        } else {
            // copy it to tmp directory
            fs.createReadStream(__dirname + '/input/' + file)
                .pipe(fs.createWriteStream(__dirname + '/tmp/' + file));

            resolve(__dirname + '/tmp/' + file);
        }

        return deferred.promise;
    }

    return (function (processes) {
        return processes.reduce(Q.when, Q([]));
    }(files.map(function (file) {
        return function (list) {
            var d = Q.defer();

            fileProcess(file).then(function (path) {
                list.push(path);

                d.resolve(list);

                console.log('processed file ' + path + ' on ', new Date);
            }).catch(function () {
                d.reject('file failed');
            });

            return d.promise;
        };
    })));
}

/**
 * Merges PDFs into a single PDF from given list
 * @param  {Array} pdfList
 * @return {Object}  promise
 */
function mergePdfs(pdfList) {
	var deferred = Q.defer(),
		fileName = Base62.encode((new Date).getTime()),
		outputFile = __dirname + '/output/' + fileName + '.pdf';

	console.log('preparing to merge PDFs')

	pdfList.forEach(function (sourcePath) {
		if (!fs.existsSync(sourcePath)) {
			console.log('...  ' + sourcePath + ' does not existxxx!');
		} else {
			console.log('...  ' + sourcePath + ' exists!');
		}
	});

	// merge PDFs
	exec(['pdftk', pdfList.join(' '), 'cat', 'output', outputFile].join(' '), function (err) {
		if (err) {
			console.log('');
			console.log(err);
			deferred.reject(err);
		} else {
			// removes files in tmp
			pdfList.forEach(function (file) {
                try {
				    fs.unlinkSync(file);
                } catch (e) {
                    console.log('could not unlink ' + file);
                }
			});

			console.log('...PDFs merged: ' + outputFile);

			// return hash
			deferred.resolve(fileName);
		}
	});

	return deferred.promise;
}

function guidGenerator() {
    var S4 = function() {
        return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
    };
    return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}<|endoftext|>


<commit_before>const Player = require('./Player');
const makeEditor = require('./makeEditor');
const stayBottom = require('./stayBottom');
const coverAll = require('./coverAll');
const Element = require('./createElementWithEvent');
const partial = require('../templates/');

const erd = require('element-resize-detector')({
  strategy: "scroll" //<- For ultra performance.
});

const DomInterface = require('./DomInterface');

require('../scss/main.scss');

const src = 'http://localhost:3000/game.html';

const init = (namespace) => {
  const selectors = require('./selectors')(namespace);
  const containers = document.querySelectorAll(selectors.container);

  const players =
    Array.prototype.slice.call(containers)
    .map(container => {
      // An instance of h4p.Player
      const player = new Player({src});

      // An editor instance as a singleton
      const editor = makeEditor();

      // DOM renderer interface
      const dom = new DomInterface({
        container,
        selectors,
        template: partial.content,
        partial
      });

      dom.classNames = selectors.htmlClass;
      const dragSizer = (event) => {
        const left = (event.clientX / innerWidth * 100) >> 0;
        const top = (event.clientY / innerHeight * 100) >> 0;
        const dock = dom.dock;
        const align = dock.align;

        dom.dock = Object.assign({}, dom.dock, {
          width:
            align === 'top' || align === 'bottom' ? '100vw' :
            align === 'left' ? (left + 'vw') : (100 - left + 'vw'),
          height:
            align === 'left' || align === 'right' ? '100vh' :
            align === 'top' ? (top + 'vh') : (100 - top + 'vh')
        });
      };
      dom.dock = {
        visibility: 'hidden',
        align: 'right',
        width: '50vw',
        height: '100vh',
        sizer: Element({ onDragEnd: dragSizer })
      };

      const toggleDock = () => {
        const current = dom.dock.visibility;
        const visibility = current === 'visible' ? 'hidden' : 'visible';
        dom.dock = Object.assign({}, dom.dock, {visibility});
      };

      dom.menuButtons = [
        Element({ label: 'HACK', onClick: toggleDock }),
        Element({ label: 'RELOAD', onClick: () => player.restart() })
      ];

      const run = () => {
        const code = editor.getValue();
        player.start([{ name: 'main', code }]);
      };
      const alignDock = (align) =>
        () => dom.dock = Object.assign({}, dom.dock, {
          align,
          width: align === 'top' || align === 'bottom' ? '100vw' : '50vw',
          height: align === 'left' || align === 'right' ? '100vh' : '50vh'
        });
      dom.editorButtons = [
        Element({ label: 'RUN', onClick: run }),
        Element({ label: 'T', onClick: alignDock('top') }),
        Element({ label: 'R', onClick: alignDock('right') }),
        Element({ label: 'B', onClick: alignDock('bottom') }),
        Element({ label: 'L', onClick: alignDock('left') })
      ];

      // Inline script
      const query = container.getAttribute('data-target');
      const code = query && document.querySelector(query).textContent;

      editor.setValue(code.replace(/\n    /g, '\n').substr(1));

      const resizeTask = stayBottom(dom);
      dom.addEventListener('screen.resize', resizeTask);
      player.addEventListener('resize', resizeTask);
      dom.addEventListener('editor.resize', coverAll({dom, editor, element: editor.display.wrapper}));

      player.start([{
        name: 'main',
        code
      }]);

      return player;
    });

  return players;
};
// export global
window.h4p = (...args) =>
  new Promise((resolve, reject) => {
    addEventListener('load', () => {
      return resolve(init(...args));
    });
  });

h4p.Player = Player;
h4p.trigger = require('./keyEvent')('h4p').trigger;
<commit_msg>import from CDN<commit_after>const Player = require('./Player');
const makeEditor = require('./makeEditor');
const stayBottom = require('./stayBottom');
const coverAll = require('./coverAll');
const Element = require('./createElementWithEvent');
const partial = require('../templates/');

const erd = require('element-resize-detector')({
  strategy: "scroll" //<- For ultra performance.
});

const DomInterface = require('./DomInterface');

require('../scss/main.scss');

const src = 'https://embed.hackforplay.xyz/open-source/game/alpha.html'; // CDN

const init = (namespace) => {
  const selectors = require('./selectors')(namespace);
  const containers = document.querySelectorAll(selectors.container);

  const players =
    Array.prototype.slice.call(containers)
    .map(container => {
      // An instance of h4p.Player
      const player = new Player({src});

      // An editor instance as a singleton
      const editor = makeEditor();

      // DOM renderer interface
      const dom = new DomInterface({
        container,
        selectors,
        template: partial.content,
        partial
      });

      dom.classNames = selectors.htmlClass;
      const dragSizer = (event) => {
        const left = (event.clientX / innerWidth * 100) >> 0;
        const top = (event.clientY / innerHeight * 100) >> 0;
        const dock = dom.dock;
        const align = dock.align;

        dom.dock = Object.assign({}, dom.dock, {
          width:
            align === 'top' || align === 'bottom' ? '100vw' :
            align === 'left' ? (left + 'vw') : (100 - left + 'vw'),
          height:
            align === 'left' || align === 'right' ? '100vh' :
            align === 'top' ? (top + 'vh') : (100 - top + 'vh')
        });
      };
      dom.dock = {
        visibility: 'hidden',
        align: 'right',
        width: '50vw',
        height: '100vh',
        sizer: Element({ onDragEnd: dragSizer })
      };

      const toggleDock = () => {
        const current = dom.dock.visibility;
        const visibility = current === 'visible' ? 'hidden' : 'visible';
        dom.dock = Object.assign({}, dom.dock, {visibility});
      };

      dom.menuButtons = [
        Element({ label: 'HACK', onClick: toggleDock }),
        Element({ label: 'RELOAD', onClick: () => player.restart() })
      ];

      const run = () => {
        const code = editor.getValue();
        player.start([{ name: 'main', code }]);
      };
      const alignDock = (align) =>
        () => dom.dock = Object.assign({}, dom.dock, {
          align,
          width: align === 'top' || align === 'bottom' ? '100vw' : '50vw',
          height: align === 'left' || align === 'right' ? '100vh' : '50vh'
        });
      dom.editorButtons = [
        Element({ label: 'RUN', onClick: run }),
        Element({ label: 'T', onClick: alignDock('top') }),
        Element({ label: 'R', onClick: alignDock('right') }),
        Element({ label: 'B', onClick: alignDock('bottom') }),
        Element({ label: 'L', onClick: alignDock('left') })
      ];

      // Inline script
      const query = container.getAttribute('data-target');
      const code = query && document.querySelector(query).textContent;

      editor.setValue(code.replace(/\n    /g, '\n').substr(1));

      const resizeTask = stayBottom(dom);
      dom.addEventListener('screen.resize', resizeTask);
      player.addEventListener('resize', resizeTask);
      dom.addEventListener('editor.resize', coverAll({dom, editor, element: editor.display.wrapper}));

      player.start([{
        name: 'main',
        code
      }]);

      return player;
    });

  return players;
};
// export global
window.h4p = (...args) =>
  new Promise((resolve, reject) => {
    addEventListener('load', () => {
      return resolve(init(...args));
    });
  });

h4p.Player = Player;
h4p.trigger = require('./keyEvent')('h4p').trigger;
<|endoftext|>


<commit_before>// @echo header
(function (factory) {
    'use strict';

    if (ko && jQuery) {
        //global knockout and jQuery references already present, so use these regardless of whether this module has been included in CommonJS/AMD
        factory(ko, jQuery);
    } else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
        // CommonJS/Node.js
        factory(require('knockout'), require('jquery'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(['knockout', 'jquery'], factory);
    } else {
        console.error('Could not locate current context reference to knockout and jQuery in order to load Knockstrap');
    }

})(function (ko, $) {
    'use strict';

    // @include utils.js

    // @include templates.js

    // @include bindings.js
});
<commit_msg>checks for global 'ko' and 'jQuery' variables now don't cause reference errors if they don't exist<commit_after>// @echo header
(function (factory) {
    'use strict';

    if (typeof ko !== undefined && typeof jQuery !== undefined) {
        //global knockout and jQuery references already present, so use these regardless of whether this module has been included in CommonJS/AMD
        factory(ko, jQuery);
    } else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
        // CommonJS/Node.js
        factory(require('knockout'), require('jquery'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(['knockout', 'jquery'], factory);
    } else {
        console.error('Could not locate current context reference to knockout and jQuery in order to load Knockstrap');
    }

})(function (ko, $) {
    'use strict';

    // @include utils.js

    // @include templates.js

    // @include bindings.js
});
<|endoftext|>


<commit_before>const traceContour = (imageData, i) => {
  const start = i
  const contour = [start]

  let direction = 0
  let p = start

  let t = 20
  do {

    const n = neighbours(imageData, p, direction)
    const pIdx = n.findIndex(idx => imageData.data[idx * 4])

    direction = pIdx
    p = n[pIdx]

    if(p && p != start) {
      contour.push(p)
    }

  } while (p && p != start && t-- > 0)

  return contour
}

// list of neighbours to visit
const neighbours = (image, i, start) => {
  const w = image.width

  const mask = []

  if((i % w) === 0) {
    mask[0] = mask[6] = mask[7] = -1
  }

  if((i+1 % w) === 0) {
    mask[2] = mask[3] = mask[4] = -1
  }

  // hack - vertical edging matters less because
  // it will get ignored by matching it to the source

  return offset([
    mask[0] || i - w - 1,
    mask[1] || i - w,
    mask[2] || i - w + 1,
    mask[3] || i + 1,
    mask[4] || i + w + 1,
    mask[5] || i + w,
    mask[6] || i + w - 1,
    mask[7] || i - 1
  ], start)
}

const offset = (array, by) =>
  array.map( (_v, i) =>
    array[(i + by) % array.length]
  )

function contourFinder (imageData) {

  const contours = []
  const seen = []

  for (var i = 0; i < imageData.data.length; i++) {
    if(imageData.data[i * 4] && ! seen[i]) {
      var contour = traceContour(imageData, i)
      contours.push(contour)

      // this could be a _lot_ more efficient
      contour.forEach(c => {
        seen[c] = true
      })
    }
  }

  return contours

}

// export for testing
contourFinder._ = {traceContour, neighbours, offset}

export default contourFinder
<commit_msg>Fix a huge bug<commit_after>const traceContour = (imageData, i) => {
  const start = i
  const contour = [start]

  let direction = 0
  let p = start

  let t = 20
  do {

    const n = neighbours(imageData, p, direction)
    const pIdx = n.findIndex(idx => imageData.data[idx * 4])

    direction = pIdx
    p = n[pIdx]

    if(p && p != start) {
      contour.push(p)
    }

  } while (p && p != start && t-- > 0)

  return contour
}

// list of neighbours to visit
const neighbours = (image, i, start) => {
  const w = image.width

  const mask = []

  if((i % w) === 0) {
    mask[0] = mask[6] = mask[7] = -1
  }

  if(((i+1) % w) === 0) {
    mask[2] = mask[3] = mask[4] = -1
  }

  // hack - vertical edging matters less because
  // it will get ignored by matching it to the source

  return offset([
    mask[0] || i - w - 1,
    mask[1] || i - w,
    mask[2] || i - w + 1,
    mask[3] || i + 1,
    mask[4] || i + w + 1,
    mask[5] || i + w,
    mask[6] || i + w - 1,
    mask[7] || i - 1
  ], start)
}

const offset = (array, by) =>
  array.map( (_v, i) =>
    array[(i + by) % array.length]
  )

function contourFinder (imageData) {

  const contours = []
  const seen = []

  for (var i = 0; i < imageData.data.length; i++) {
    if(imageData.data[i * 4] && ! seen[i]) {
      var contour = traceContour(imageData, i)
      contours.push(contour)

      // this could be a _lot_ more efficient
      contour.forEach(c => {
        seen[c] = true
      })
    }
  }

  return contours

}

// export for testing
contourFinder._ = {traceContour, neighbours, offset}

export default contourFinder
<|endoftext|>


<commit_before>'use strict';

const electron = require('electron');
const app = electron.app;  // Module to control application life.
const BrowserWindow = electron.BrowserWindow;  // Module to create native browser window.

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
var mainWindow = null;

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function () {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  mainWindow.loadURL('file://' + __dirname + '/index.html');

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
});
<commit_msg>GLobal shortcuts, for now fullscreen mode<commit_after>'use strict';

const electron = require('electron');
const app = electron.app;  // Module to control application life.
const BrowserWindow = electron.BrowserWindow;  // Module to create native browser window.
const globalShortcut = electron.globalShortcut; // Module to define global shortcuts.

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
var mainWindow = null;

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function () {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  mainWindow.loadURL('file://' + __dirname + '/index.html');

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });

  // Register global shortcuts.
  globalShortcut.register('cmd+ctrl+f', function () {
    if (mainWindow.isFullScreen()) {
      mainWindow.setFullScreen(false);
      return;
    }
    mainWindow.setFullScreen(true);
  });
});

app.on('will-quit', function () {
  // Unregister all shortcuts.
  globalShortcut.unregisterAll();
});
<|endoftext|>


<commit_before>var $ = require('jquery');
var Backbone = require('backbone');
Backbone.$ = $;
require('jquery.bootstrap');
require('backbone.syphon');

var Application = require('./application/application');

Application.on('start', function() {
  Backbone.history.start();
});

$(function() {
  Application.start();
});
<commit_msg>Remove App.start from $.ready<commit_after>var $ = require('jquery');
var Backbone = require('backbone');
Backbone.$ = $;
require('jquery.bootstrap');
require('backbone.syphon');

var Application = require('./application/application');

Application.on('start', function() {
  Backbone.history.start();
});

Application.start();
<|endoftext|>


<commit_before>'use strict';
Object.defineProperty(exports, '__esModule', { value: true });

const electron = require('electron');
const wpilib_NT = require('wpilib-nt-client');
const client = new wpilib_NT.Client();

// The client will try to reconnect after 1 second
//client.setReconnectDelay(1000);

/** Module to control application life. */
const app = electron.app;

/** Module to create native browser window.*/
const BrowserWindow = electron.BrowserWindow;

/** Module for receiving messages from the BrowserWindow */
const ipc = electron.ipcMain;

require('electron-debug')({showDevTools: true});

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
/**
 * The Main Window of the Program
 * @type {Electron.BrowserWindow}
 * */
let mainWindow;

let connectedFunc,
    ready = false;

let clientDataListener = (key, val, valType, mesgType, id, flags) => {
    if (val === 'true' || val === 'false') {
        val = val === 'true';
    }
    mainWindow.webContents.send(mesgType, {
        key,
        val,
        valType,
        id,
        flags
    });
};
function createWindow() {
    // Attempt to connect to the localhost
    client.start((con, err) => {

        let connectFunc = () => {
            console.log('Sending status');
            mainWindow.webContents.send('connected', con);
        };

        // If the Window is ready than send the connection status to it
        if (ready) {
            connectFunc();
        }
        connectedFunc = connectFunc;
    });
    // When the script starts running in the window set the ready variable
    ipc.on('ready', (ev, mesg) => {
        console.log('NetworkTables is ready');
        ready = mainWindow != null;

        // Remove old Listener
        client.removeListener(clientDataListener);

        // Add new listener with immediate callback
        client.addListener(clientDataListener, true);

        // Send connection message to the window if if the message is ready
        if (connectedFunc) connectedFunc();
    });
    // When the user chooses the address of the bot than try to connect
    ipc.on('connect', (ev, address, port) => {
        console.log(`Trying to connect to ${address}` + (port ? ':' + port : ''));
        let callback = (connected, err) => {
            console.log('Sending status');
            mainWindow.webContents.send('connected', connected);
        };
        if (port) {
            client.start(callback, address, port);
        } else {
            client.start(callback, address);
        }
    });
    ipc.on('add', (ev, mesg) => {
        client.Assign(mesg.val, mesg.key, (mesg.flags & 1) === 1);
    });
    ipc.on('update', (ev, mesg) => {
        client.Update(mesg.id, mesg.val);
    });
    ipc.on('error', (ev, error) => {
        console.log(error);
    });
    // Create the browser window.
    mainWindow = new BrowserWindow({
        width: 1366,
        height: 525,
        // 1366x570 is a good standard height, but you may want to change this to fit your DriverStation's screen better.
        // It's best if the dashboard takes up as much space as possible without covering the DriverStation application.
        // The window is closed until the python server is ready
        show: false,
		icon: __dirname + '/../images/icon.png'
    });
    // Move window to top (left) of screen.
    mainWindow.setPosition(0, 0);
    // Load window.
    mainWindow.loadURL(`file://${__dirname}/index.html`);
    // Once the python server is ready, load window contents.
    mainWindow.once('ready-to-show', () => {
        console.log('main window is ready to be shown');
        mainWindow.show();
    });

    // Remove menu
    mainWindow.setMenu(null);
    // Emitted when the window is closed.
    mainWindow.on('closed', () => {
        console.log('main window closed');
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null;
        ready = false;
        connectedFunc = null;
        client.removeListener(clientDataListener);
    });
    mainWindow.on('unresponsive', () => {
        console.log('Main Window is unresponsive');
    });
    mainWindow.webContents.on('did-fail-load', () => {
        console.log('window failed load');
    });
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', () => {
    console.log('app is ready');
    createWindow();
});

// Quit when all windows are closed.
app.on('window-all-closed', function() {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q.
    // Not like we're creating a consumer application though.
    // Let's just kill it anyway.
    // If you want to restore the standard behavior, uncomment the next line.

    // if (process.platform !== 'darwin')
    app.quit();
});

app.on('quit', function() {
    console.log('Application quit.');
});

app.on('activate', function() {
    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (mainWindow == null) createWindow();
});
<commit_msg>window fixed<commit_after>'use strict';
Object.defineProperty(exports, '__esModule', { value: true });

const electron = require('electron');
const wpilib_NT = require('wpilib-nt-client');
const client = new wpilib_NT.Client();

// The client will try to reconnect after 1 second
//client.setReconnectDelay(1000);

/** Module to control application life. */
const app = electron.app;

/** Module to create native browser window.*/
const BrowserWindow = electron.BrowserWindow;

/** Module for receiving messages from the BrowserWindow */
const ipc = electron.ipcMain;

require('electron-debug')({showDevTools: true});

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
/**
 * The Main Window of the Program
 * @type {Electron.BrowserWindow}
 * */
let mainWindow;

let connectedFunc,
    ready = false;

let clientDataListener = (key, val, valType, mesgType, id, flags) => {
    if (val === 'true' || val === 'false') {
        val = val === 'true';
    }
    mainWindow.webContents.send(mesgType, {
        key,
        val,
        valType,
        id,
        flags
    });
};
function createWindow() {
    // Attempt to connect to the localhost
    client.start((con, err) => {

        let connectFunc = () => {
            console.log('Sending status');
            mainWindow.webContents.send('connected', con);
        };

        // If the Window is ready than send the connection status to it
        if (ready) {
            connectFunc();
        }
        connectedFunc = connectFunc;
    });
    // When the script starts running in the window set the ready variable
    ipc.on('ready', (ev, mesg) => {
        console.log('NetworkTables is ready');
        ready = mainWindow != null;

        // Remove old Listener
        client.removeListener(clientDataListener);

        // Add new listener with immediate callback
        client.addListener(clientDataListener, true);

        // Send connection message to the window if if the message is ready
        if (connectedFunc) connectedFunc();
    });
    // When the user chooses the address of the bot than try to connect
    ipc.on('connect', (ev, address, port) => {
        console.log(`Trying to connect to ${address}` + (port ? ':' + port : ''));
        let callback = (connected, err) => {
            console.log('Sending status');
            mainWindow.webContents.send('connected', connected);
        };
        if (port) {
            client.start(callback, address, port);
        } else {
            client.start(callback, address);
        }
    });
    ipc.on('add', (ev, mesg) => {
        client.Assign(mesg.val, mesg.key, (mesg.flags & 1) === 1);
    });
    ipc.on('update', (ev, mesg) => {
        client.Update(mesg.id, mesg.val);
    });
    ipc.on('error', (ev, error) => {
        console.log(error);
    });
    // Create the browser window.
    mainWindow = new BrowserWindow({
        width: 1366,
        height: 562,
        // 1366x570 is a good standard height, but you may want to change this to fit your DriverStation's screen better.
        // It's best if the dashboard takes up as much space as possible without covering the DriverStation application.
        // The window is closed until the python server is ready
        show: false,
		icon: __dirname + '/../images/icon.png'
    });
    // Move window to top (left) of screen.
    mainWindow.setPosition(0, 0);
    // Load window.
    mainWindow.loadURL(`file://${__dirname}/index.html`);
    // Once the python server is ready, load window contents.
    mainWindow.once('ready-to-show', () => {
        console.log('main window is ready to be shown');
        mainWindow.show();
    });

    // Remove menu
    mainWindow.setMenu(null);
    // Emitted when the window is closed.
    mainWindow.on('closed', () => {
        console.log('main window closed');
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null;
        ready = false;
        connectedFunc = null;
        client.removeListener(clientDataListener);
    });
    mainWindow.on('unresponsive', () => {
        console.log('Main Window is unresponsive');
    });
    mainWindow.webContents.on('did-fail-load', () => {
        console.log('window failed load');
    });
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', () => {
    console.log('app is ready');
    createWindow();
});

// Quit when all windows are closed.
app.on('window-all-closed', function() {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q.
    // Not like we're creating a consumer application though.
    // Let's just kill it anyway.
    // If you want to restore the standard behavior, uncomment the next line.

    // if (process.platform !== 'darwin')
    app.quit();
});

app.on('quit', function() {
    console.log('Application quit.');
});

app.on('activate', function() {
    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (mainWindow == null) createWindow();
});
<|endoftext|>