<commit_before>'use strict';

const config = require('./config/env');

const path = require('path');
const Koa = require('koa');
const mount = require('koa-mount');
const compress = require('koa-compress');
const session = require('koa-session');
const serveStatic = require('koa-static');
const convert = require('koa-convert');
const helmet = require('koa-helmet');
const favicon = require('koa-favicon');
const isEmpty = require('lodash.isempty');

const { logger, Logger } = require('./services/logger');
const index = require('./routes/index');
const apiRouter = require('./routes/proxy');
const sysUtils = require('./config/utils');
const isSSREnabled = config.isSSREnabled();

//React SSR
let SSR = isSSREnabled ? require('./build/node/ssr') : null;

const PORT = config.getListeningPort();
const DEV_MODE = config.isDevMode();
const DEFAULT_PREFIX_KEY = 'defaultPrefix';
const API_ENDPOINTS = config.getApiEndPoints();
const isHMREnabled = config.isHMREnabled();

//and initialize it with
const app = new Koa();
app.env = config.getNodeEnv() || 'development';
app.keys = ['koa-web-kit'];
app.proxy = true;

app.use(Logger.createMorganLogger());
app.use(logger.createRequestsLogger());
app.use(helmet());

(async function() {
  initProxy();
  if (SSR) {
    await SSR.preloadAll();
  }
  await initHMR();
  initApp();
  logger.info(`${isHMREnabled ? 'HMR & ' : ''}Koa App initialized!`);
})();

function initApp() {
  if (!DEV_MODE) {
    app.use(compress());
  }

  app.use(favicon(__dirname + '/src/assets/static/favicon.ico'));

  let staticPrefix = path.join(
    config.getAppPrefix(),
    config.getStaticPrefix() || '/'
  );
  if (sysUtils.isWindows()) {
    staticPrefix = sysUtils.replaceBackwardSlash(staticPrefix);
  }
  app.use(
    mount(
      staticPrefix,
      serveStatic(path.join(process.cwd(), 'build/app'), {
        // one month cache for prod
        maxage: DEV_MODE ? 0 : 2592000000,
        gzip: false,
      })
    )
  );

  app.use(session(app));

  app.use(index.routes());

  app.on('error', err => {
    logger.error(err.stack);
  });

  //and then give it a port to listen for
  app.listen(PORT, '0.0.0.0');
  logger.info(`Koa listening on port ${PORT}`);
}

async function initHMR() {
  let HMRInitialized = false;
  //enable hmr
  if (isHMREnabled) {
    logger.info('HMR enabled, initializing HMR...');
    const koaWebpack = require('koa-webpack');
    const historyApiFallback = require('koa-history-api-fallback');
    // const getPort = require('get-port');
    const webpack = require('webpack');
    // const availPort = await getPort();
    const webpackConfig = require('./config/webpack.config.dev');
    const compiler = webpack(
      Object.assign({}, webpackConfig, {
        stats: {
          modules: false,
          colors: true,
        },
      })
    );
    await koaWebpack({
      compiler,
      hotClient: {
        // port: availPort,
        // logLevel: 'warn',
        hmr: true,
        reload: true,
      },
      devMiddleware: {
        index: 'index.html',
        publicPath: webpackConfig.output.publicPath,
        watchOptions: {
          aggregateTimeout: 0,
        },
        writeToDisk: true,
        stats: {
          modules: false,
          colors: true,
          children: false,
        },
      },
    })
      .then(instance => {
        if (!HMRInitialized) {
          HMRInitialized = true;
          // app.use(instance);
          app.use(convert(historyApiFallback()));
          app.use(instance);
        }
      })
      .catch(err => {
        logger.error('[koa-webpack]:', err);
      });
  }
}

function initProxy() {
  //api proxy
  if (config.isNodeProxyEnabled() && !isEmpty(API_ENDPOINTS)) {
    for (const prefix in API_ENDPOINTS) {
      if (
        API_ENDPOINTS.hasOwnProperty(prefix) &&
        prefix !== DEFAULT_PREFIX_KEY
      ) {
        let endPoint = API_ENDPOINTS[prefix];
        if ('string' !== typeof endPoint) {
          endPoint = endPoint.endpoint;
        }
        app.use(apiRouter.handleApiRequests(prefix, endPoint));
        logger.info('Node proxy[' + endPoint + '] enabled for path: ' + prefix);
      }
    }
  }
}
<commit_msg>:wrench:Only log errors with hot-client<commit_after>'use strict';

const config = require('./config/env');

const path = require('path');
const Koa = require('koa');
const mount = require('koa-mount');
const compress = require('koa-compress');
const session = require('koa-session');
const serveStatic = require('koa-static');
const convert = require('koa-convert');
const helmet = require('koa-helmet');
const favicon = require('koa-favicon');
const isEmpty = require('lodash.isempty');

const { logger, Logger } = require('./services/logger');
const index = require('./routes/index');
const apiRouter = require('./routes/proxy');
const sysUtils = require('./config/utils');
const isSSREnabled = config.isSSREnabled();

//React SSR
let SSR = isSSREnabled ? require('./build/node/ssr') : null;

const PORT = config.getListeningPort();
const DEV_MODE = config.isDevMode();
const DEFAULT_PREFIX_KEY = 'defaultPrefix';
const API_ENDPOINTS = config.getApiEndPoints();
const isHMREnabled = config.isHMREnabled();

//and initialize it with
const app = new Koa();
app.env = config.getNodeEnv() || 'development';
app.keys = ['koa-web-kit'];
app.proxy = true;

app.use(Logger.createMorganLogger());
app.use(logger.createRequestsLogger());
app.use(helmet());

(async function() {
  initProxy();
  if (SSR) {
    await SSR.preloadAll();
  }
  await initHMR();
  initApp();
  logger.info(`${isHMREnabled ? 'HMR & ' : ''}Koa App initialized!`);
})();

function initApp() {
  if (!DEV_MODE) {
    app.use(compress());
  }

  app.use(favicon(__dirname + '/src/assets/static/favicon.ico'));

  let staticPrefix = path.join(
    config.getAppPrefix(),
    config.getStaticPrefix() || '/'
  );
  if (sysUtils.isWindows()) {
    staticPrefix = sysUtils.replaceBackwardSlash(staticPrefix);
  }
  app.use(
    mount(
      staticPrefix,
      serveStatic(path.join(process.cwd(), 'build/app'), {
        // one month cache for prod
        maxage: DEV_MODE ? 0 : 2592000000,
        gzip: false,
      })
    )
  );

  app.use(session(app));

  app.use(index.routes());

  app.on('error', err => {
    logger.error(err.stack);
  });

  //and then give it a port to listen for
  app.listen(PORT, '0.0.0.0');
  logger.info(`Koa listening on port ${PORT}`);
}

async function initHMR() {
  let HMRInitialized = false;
  //enable hmr
  if (isHMREnabled) {
    logger.info('HMR enabled, initializing HMR...');
    const koaWebpack = require('koa-webpack');
    const historyApiFallback = require('koa-history-api-fallback');
    // const getPort = require('get-port');
    const webpack = require('webpack');
    // const availPort = await getPort();
    const webpackConfig = require('./config/webpack.config.dev');
    const compiler = webpack(
      Object.assign({}, webpackConfig, {
        stats: {
          modules: false,
          colors: true,
        },
      })
    );
    await koaWebpack({
      compiler,
      hotClient: {
        // port: availPort,
        logLevel: 'error',
        hmr: true,
        reload: true,
      },
      devMiddleware: {
        index: 'index.html',
        publicPath: webpackConfig.output.publicPath,
        watchOptions: {
          aggregateTimeout: 0,
        },
        writeToDisk: true,
        stats: {
          modules: false,
          colors: true,
          children: false,
        },
      },
    })
      .then(instance => {
        if (!HMRInitialized) {
          HMRInitialized = true;
          // app.use(instance);
          app.use(convert(historyApiFallback()));
          app.use(instance);
        }
      })
      .catch(err => {
        logger.error('[koa-webpack]:', err);
      });
  }
}

function initProxy() {
  //api proxy
  if (config.isNodeProxyEnabled() && !isEmpty(API_ENDPOINTS)) {
    for (const prefix in API_ENDPOINTS) {
      if (
        API_ENDPOINTS.hasOwnProperty(prefix) &&
        prefix !== DEFAULT_PREFIX_KEY
      ) {
        let endPoint = API_ENDPOINTS[prefix];
        if ('string' !== typeof endPoint) {
          endPoint = endPoint.endpoint;
        }
        app.use(apiRouter.handleApiRequests(prefix, endPoint));
        logger.info('Node proxy[' + endPoint + '] enabled for path: ' + prefix);
      }
    }
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

/* jshint node: true, devel: true */
'use strict';

const
  bodyParser = require('body-parser'),
  cloudinary = require('cloudinary'),
  config = require('config'),
  crypto = require('crypto'),
  express = require('express'),
  https = require('https'),
  request = require('request');

var app = express();

app.set('port', process.env.PORT || 5000);
app.use(bodyParser.json({ verify: verifyRequestSignature }));
app.use(express.static('public'));

/*
 * Be sure to setup your config values before running this code. You can
 * set them using environment variables or modifying the config file in /config.
 *
 */

// App Secret can be retrieved from the App Dashboard
const APP_SECRET = (process.env.MESSENGER_APP_SECRET) ?
  process.env.MESSENGER_APP_SECRET :
  config.get('appSecret');

// Arbitrary value used to validate a webhook
const VALIDATION_TOKEN = (process.env.MESSENGER_VALIDATION_TOKEN) ?
  (process.env.MESSENGER_VALIDATION_TOKEN) :
  config.get('validationToken');

// Generate a page access token for your page from the App Dashboard
const PAGE_ACCESS_TOKEN = (process.env.MESSENGER_PAGE_ACCESS_TOKEN) ?
  (process.env.MESSENGER_PAGE_ACCESS_TOKEN) :
  config.get('pageAccessToken');

if (!(APP_SECRET && VALIDATION_TOKEN && PAGE_ACCESS_TOKEN)) {
  console.error("Missing config values");
  process.exit(1);
}

/*
 * Use your own validation token. Check that the token used in the Webhook
 * setup is the same token used here.
 *
 */
app.get('/webhook', function(req, res) {
  if (req.query['hub.mode'] === 'subscribe' &&
      req.query['hub.verify_token'] === VALIDATION_TOKEN) {
    console.log("Validating webhook");
    res.status(200).send(req.query['hub.challenge']);
  } else {
    console.error("Failed validation. Make sure the validation tokens match.");
    res.sendStatus(403);
  }
});

/*
 * All callbacks for Messenger are POST-ed. They will be sent to the same
 * webhook. Be sure to subscribe your app to your page to receive callbacks
 * for your page.
 * https://developers.facebook.com/docs/messenger-platform/implementation#subscribe_app_pages
 *
 */
app.post('/webhook', function (req, res) {
  var data = req.body;

  // Make sure this is a page subscription
  if (data.object == 'page') {
    // Iterate over each entry
    // There may be multiple if batched
    data.entry.forEach(function(pageEntry) {
      var pageID = pageEntry.id;
      var timeOfEvent = pageEntry.time;

      // Iterate over each messaging event
      pageEntry.messaging.forEach(function(messagingEvent) {
        if (messagingEvent.optin) {
          receivedAuthentication(messagingEvent);
        } else if (messagingEvent.message) {
          receivedMessage(messagingEvent);
        } else if (messagingEvent.delivery) {
          receivedDeliveryConfirmation(messagingEvent);
        } else if (messagingEvent.postback) {
          receivedPostback(messagingEvent);
        } else {
          console.log("Webhook received unknown messagingEvent: ", messagingEvent);
        }
      });
    });

    // Assume all went well.
    //
    // You must send back a 200, within 20 seconds, to let us know you've
    // successfully received the callback. Otherwise, the request will time out.
    res.sendStatus(200);
  }
});

/*
 * Verify that the callback came from Facebook. Using the App Secret from
 * the App Dashboard, we can verify the signature that is sent with each
 * callback in the x-hub-signature field, located in the header.
 *
 * https://developers.facebook.com/docs/graph-api/webhooks#setup
 *
 */
function verifyRequestSignature(req, res, buf) {
  var signature = req.headers["x-hub-signature"];

  if (!signature) {
    // For testing, let's log an error. In production, you should throw an
    // error.
    console.error("Couldn't validate the signature.");
  } else {
    var elements = signature.split('=');
    var method = elements[0];
    var signatureHash = elements[1];

    var expectedHash = crypto.createHmac('sha1', APP_SECRET)
                        .update(buf)
                        .digest('hex');

    if (signatureHash != expectedHash) {
      throw new Error("Couldn't validate the request signature.");
    }
  }
}

/*
 * Authorization Event
 *
 * The value for 'optin.ref' is defined in the entry point. For the "Send to
 * Messenger" plugin, it is the 'data-ref' field. Read more at
 * https://developers.facebook.com/docs/messenger-platform/webhook-reference#auth
 *
 */
function receivedAuthentication(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var timeOfAuth = event.timestamp;

  // The 'ref' field is set in the 'Send to Messenger' plugin, in the 'data-ref'
  // The developer can set this to an arbitrary value to associate the
  // authentication callback with the 'Send to Messenger' click event. This is
  // a way to do account linking when the user clicks the 'Send to Messenger'
  // plugin.
  var passThroughParam = event.optin.ref;

  console.log("Received authentication for user %d and page %d with pass " +
    "through param '%s' at %d", senderID, recipientID, passThroughParam,
    timeOfAuth);

  // When an authentication is received, we'll send a message back to the sender
  // to let them know it was successful.
  sendTextMessage(senderID, "Authentication successful");
}

/*
 * Message Event
 *
 * This event is called when a message is sent to your page. The 'message'
 * object format can vary depending on the kind of message that was received.
 * Read more at https://developers.facebook.com/docs/messenger-platform/webhook-reference#received_message
 *
 * For this example, we're going to echo any text that we get. If we get some
 * special keywords ('button', 'generic', 'receipt'), then we'll send back
 * examples of those bubbles to illustrate the special message bubbles we've
 * created. If we receive a message with an attachment (image, video, audio),
 * then we'll simply confirm that we've received the attachment.
 *
 */
function receivedMessage(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var timeOfMessage = event.timestamp;
  var message = event.message;

  console.log("Received message for user %d and page %d at %d with message:",
    senderID, recipientID, timeOfMessage);
  console.log(JSON.stringify(message));

  var messageId = message.mid;

  // You may get a text or attachment but not both
  var messageText = message.text;
  var messageAttachments = message.attachments;

  if (messageText) {

    // If we receive a text message, check to see if it matches any special
    // keywords and send back the corresponding example. Otherwise, just echo
    // the text we received.
    switch (messageText) {
      case 'image':
        sendImageMessage(senderID);
        break;

      case 'button':
        sendButtonMessage(senderID);
        break;

      default:
        sendHelpMessage(senderID);
        break;
    }
  } else if (messageAttachments) {
    var first_attachment = messageAttachments[0];
    if (first_attachment.type === 'image') {
      var image_url = first_attachment.payload.url;
      sendTextMessage("Got image: " + image_url);

      // cloudinary.uploader.upload(image_url,
      //   function(result) {
      //     sendTextMessage("Result: " + result);
      //   }
      // )
    } else {
      sendTextMessage(senderID, "Message with attachment received");
    }
  }
}

/*
 * Delivery Confirmation Event
 *
 * This event is sent to confirm the delivery of a message. Read more about
 * these fields at https://developers.facebook.com/docs/messenger-platform/webhook-reference#message_delivery
 *
 */
function receivedDeliveryConfirmation(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var delivery = event.delivery;
  var messageIDs = delivery.mids;
  var watermark = delivery.watermark;
  var sequenceNumber = delivery.seq;

  if (messageIDs) {
    messageIDs.forEach(function(messageID) {
      console.log("Received delivery confirmation for message ID: %s",
        messageID);
    });
  }

  console.log("All message before %d were delivered.", watermark);
}

/*
 * Postback Event
 *
 * This event is called when a postback is tapped on a Structured Message. Read
 * more at https://developers.facebook.com/docs/messenger-platform/webhook-reference#postback
 *
 */
function receivedPostback(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var timeOfPostback = event.timestamp;

  // The 'payload' param is a developer-defined field which is set in a postback
  // button for Structured Messages.
  var payload = event.postback.payload;

  console.log("Received postback for user %d and page %d with payload '%s' " +
    "at %d", senderID, recipientID, payload, timeOfPostback);

  // When a postback is called, we'll send a message back to the sender to
  // let them know it was successful
  sendTextMessage(senderID, "Postback called");
}

/*
 * Send a message with an using the Send API.
 *
 */
function sendImageMessage(recipientId) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      attachment: {
        type: "image",
        payload: {
          url: "http://i.imgur.com/zYIlgBl.png"
        }
      }
    }
  };

  callSendAPI(messageData);
}

/*
 * Send a text message using the Send API.
 *
 */
function sendHelpMessage(recipientId) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      text: "Send an image to get started.\n"
          + "Write top <text> or bottom <text> to set text."
    }
  };

  callSendAPI(messageData);
}

/*
 * Send a text message using the Send API.
 *
 */
function sendTextMessage(recipientId, messageText) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      text: messageText
    }
  };

  callSendAPI(messageData);
}

/*
 * Send a button message using the Send API.
 *
 */
function sendButtonMessage(recipientId) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      attachment: {
        type: "template",
        payload: {
          template_type: "button",
          text: "This is test text",
          buttons:[{
            type: "web_url",
            url: "https://www.oculus.com/en-us/rift/",
            title: "Open Web URL"
          }, {
            type: "postback",
            title: "Call Postback",
            payload: "Developer defined postback"
          }]
        }
      }
    }
  };

  callSendAPI(messageData);
}

/*
 * Call the Send API. The message data goes in the body. If successful, we'll
 * get the message id in a response
 *
 */
function callSendAPI(messageData) {
  request({
    uri: 'https://graph.facebook.com/v2.6/me/messages',
    qs: { access_token: PAGE_ACCESS_TOKEN },
    method: 'POST',
    json: messageData

  }, function (error, response, body) {
    if (!error && response.statusCode == 200) {
      var recipientId = body.recipient_id;
      var messageId = body.message_id;

      console.log("Successfully sent generic message with id %s to recipient %s",
        messageId, recipientId);
    } else {
      console.error("Unable to send message.");
      console.error(response);
      console.error(error);
    }
  });
}

// Start server
// Webhooks must be available via SSL with a certificate signed by a valid
// certificate authority.
app.listen(app.get('port'), function() {
  console.log('Node app is running on port', app.get('port'));
});

module.exports = app;

<commit_msg>Debugging image uploads<commit_after>/*
 * Copyright 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

/* jshint node: true, devel: true */
'use strict';

const
  bodyParser = require('body-parser'),
  cloudinary = require('cloudinary'),
  config = require('config'),
  crypto = require('crypto'),
  express = require('express'),
  https = require('https'),
  request = require('request');

var app = express();

app.set('port', process.env.PORT || 5000);
app.use(bodyParser.json({ verify: verifyRequestSignature }));
app.use(express.static('public'));

/*
 * Be sure to setup your config values before running this code. You can
 * set them using environment variables or modifying the config file in /config.
 *
 */

// App Secret can be retrieved from the App Dashboard
const APP_SECRET = (process.env.MESSENGER_APP_SECRET) ?
  process.env.MESSENGER_APP_SECRET :
  config.get('appSecret');

// Arbitrary value used to validate a webhook
const VALIDATION_TOKEN = (process.env.MESSENGER_VALIDATION_TOKEN) ?
  (process.env.MESSENGER_VALIDATION_TOKEN) :
  config.get('validationToken');

// Generate a page access token for your page from the App Dashboard
const PAGE_ACCESS_TOKEN = (process.env.MESSENGER_PAGE_ACCESS_TOKEN) ?
  (process.env.MESSENGER_PAGE_ACCESS_TOKEN) :
  config.get('pageAccessToken');

if (!(APP_SECRET && VALIDATION_TOKEN && PAGE_ACCESS_TOKEN)) {
  console.error("Missing config values");
  process.exit(1);
}

/*
 * Use your own validation token. Check that the token used in the Webhook
 * setup is the same token used here.
 *
 */
app.get('/webhook', function(req, res) {
  if (req.query['hub.mode'] === 'subscribe' &&
      req.query['hub.verify_token'] === VALIDATION_TOKEN) {
    console.log("Validating webhook");
    res.status(200).send(req.query['hub.challenge']);
  } else {
    console.error("Failed validation. Make sure the validation tokens match.");
    res.sendStatus(403);
  }
});

/*
 * All callbacks for Messenger are POST-ed. They will be sent to the same
 * webhook. Be sure to subscribe your app to your page to receive callbacks
 * for your page.
 * https://developers.facebook.com/docs/messenger-platform/implementation#subscribe_app_pages
 *
 */
app.post('/webhook', function (req, res) {
  var data = req.body;

  // Make sure this is a page subscription
  if (data.object == 'page') {
    // Iterate over each entry
    // There may be multiple if batched
    data.entry.forEach(function(pageEntry) {
      var pageID = pageEntry.id;
      var timeOfEvent = pageEntry.time;

      // Iterate over each messaging event
      pageEntry.messaging.forEach(function(messagingEvent) {
        if (messagingEvent.optin) {
          receivedAuthentication(messagingEvent);
        } else if (messagingEvent.message) {
          receivedMessage(messagingEvent);
        } else if (messagingEvent.delivery) {
          receivedDeliveryConfirmation(messagingEvent);
        } else if (messagingEvent.postback) {
          receivedPostback(messagingEvent);
        } else {
          console.log("Webhook received unknown messagingEvent: ", messagingEvent);
        }
      });
    });

    // Assume all went well.
    //
    // You must send back a 200, within 20 seconds, to let us know you've
    // successfully received the callback. Otherwise, the request will time out.
    res.sendStatus(200);
  }
});

/*
 * Verify that the callback came from Facebook. Using the App Secret from
 * the App Dashboard, we can verify the signature that is sent with each
 * callback in the x-hub-signature field, located in the header.
 *
 * https://developers.facebook.com/docs/graph-api/webhooks#setup
 *
 */
function verifyRequestSignature(req, res, buf) {
  var signature = req.headers["x-hub-signature"];

  if (!signature) {
    // For testing, let's log an error. In production, you should throw an
    // error.
    console.error("Couldn't validate the signature.");
  } else {
    var elements = signature.split('=');
    var method = elements[0];
    var signatureHash = elements[1];

    var expectedHash = crypto.createHmac('sha1', APP_SECRET)
                        .update(buf)
                        .digest('hex');

    if (signatureHash != expectedHash) {
      throw new Error("Couldn't validate the request signature.");
    }
  }
}

/*
 * Authorization Event
 *
 * The value for 'optin.ref' is defined in the entry point. For the "Send to
 * Messenger" plugin, it is the 'data-ref' field. Read more at
 * https://developers.facebook.com/docs/messenger-platform/webhook-reference#auth
 *
 */
function receivedAuthentication(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var timeOfAuth = event.timestamp;

  // The 'ref' field is set in the 'Send to Messenger' plugin, in the 'data-ref'
  // The developer can set this to an arbitrary value to associate the
  // authentication callback with the 'Send to Messenger' click event. This is
  // a way to do account linking when the user clicks the 'Send to Messenger'
  // plugin.
  var passThroughParam = event.optin.ref;

  console.log("Received authentication for user %d and page %d with pass " +
    "through param '%s' at %d", senderID, recipientID, passThroughParam,
    timeOfAuth);

  // When an authentication is received, we'll send a message back to the sender
  // to let them know it was successful.
  sendTextMessage(senderID, "Authentication successful");
}

/*
 * Message Event
 *
 * This event is called when a message is sent to your page. The 'message'
 * object format can vary depending on the kind of message that was received.
 * Read more at https://developers.facebook.com/docs/messenger-platform/webhook-reference#received_message
 *
 * For this example, we're going to echo any text that we get. If we get some
 * special keywords ('button', 'generic', 'receipt'), then we'll send back
 * examples of those bubbles to illustrate the special message bubbles we've
 * created. If we receive a message with an attachment (image, video, audio),
 * then we'll simply confirm that we've received the attachment.
 *
 */
function receivedMessage(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var timeOfMessage = event.timestamp;
  var message = event.message;

  console.log("Received message for user %d and page %d at %d with message:",
    senderID, recipientID, timeOfMessage);
  console.log(JSON.stringify(message));

  var messageId = message.mid;

  // You may get a text or attachment but not both
  var messageText = message.text;
  var messageAttachments = message.attachments;

  if (messageText) {

    // If we receive a text message, check to see if it matches any special
    // keywords and send back the corresponding example. Otherwise, just echo
    // the text we received.
    switch (messageText) {
      case 'image':
        sendImageMessage(senderID);
        break;

      case 'button':
        sendButtonMessage(senderID);
        break;

      default:
        sendHelpMessage(senderID);
        break;
    }
  } else if (messageAttachments) {
    sendTextMessage("Got an attachment");
    // var first_attachment = messageAttachments[0];
    // if (first_attachment.type === 'image') {
    //   var image_url = first_attachment.payload.url;
    //   sendTextMessage("Got image: " + image_url);
    //
    //   // cloudinary.uploader.upload(image_url,
    //   //   function(result) {
    //   //     sendTextMessage("Result: " + result);
    //   //   }
    //   // )
    // }
  }
}

/*
 * Delivery Confirmation Event
 *
 * This event is sent to confirm the delivery of a message. Read more about
 * these fields at https://developers.facebook.com/docs/messenger-platform/webhook-reference#message_delivery
 *
 */
function receivedDeliveryConfirmation(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var delivery = event.delivery;
  var messageIDs = delivery.mids;
  var watermark = delivery.watermark;
  var sequenceNumber = delivery.seq;

  if (messageIDs) {
    messageIDs.forEach(function(messageID) {
      console.log("Received delivery confirmation for message ID: %s",
        messageID);
    });
  }

  console.log("All message before %d were delivered.", watermark);
}

/*
 * Postback Event
 *
 * This event is called when a postback is tapped on a Structured Message. Read
 * more at https://developers.facebook.com/docs/messenger-platform/webhook-reference#postback
 *
 */
function receivedPostback(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var timeOfPostback = event.timestamp;

  // The 'payload' param is a developer-defined field which is set in a postback
  // button for Structured Messages.
  var payload = event.postback.payload;

  console.log("Received postback for user %d and page %d with payload '%s' " +
    "at %d", senderID, recipientID, payload, timeOfPostback);

  // When a postback is called, we'll send a message back to the sender to
  // let them know it was successful
  sendTextMessage(senderID, "Postback called");
}

/*
 * Send a message with an using the Send API.
 *
 */
function sendImageMessage(recipientId) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      attachment: {
        type: "image",
        payload: {
          url: "http://i.imgur.com/zYIlgBl.png"
        }
      }
    }
  };

  callSendAPI(messageData);
}

/*
 * Send a text message using the Send API.
 *
 */
function sendHelpMessage(recipientId) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      text: "Send an image to get started.\n"
          + "Write top <text> or bottom <text> to set text."
    }
  };

  callSendAPI(messageData);
}

/*
 * Send a text message using the Send API.
 *
 */
function sendTextMessage(recipientId, messageText) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      text: messageText
    }
  };

  callSendAPI(messageData);
}

/*
 * Send a button message using the Send API.
 *
 */
function sendButtonMessage(recipientId) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      attachment: {
        type: "template",
        payload: {
          template_type: "button",
          text: "This is test text",
          buttons:[{
            type: "web_url",
            url: "https://www.oculus.com/en-us/rift/",
            title: "Open Web URL"
          }, {
            type: "postback",
            title: "Call Postback",
            payload: "Developer defined postback"
          }]
        }
      }
    }
  };

  callSendAPI(messageData);
}

/*
 * Call the Send API. The message data goes in the body. If successful, we'll
 * get the message id in a response
 *
 */
function callSendAPI(messageData) {
  request({
    uri: 'https://graph.facebook.com/v2.6/me/messages',
    qs: { access_token: PAGE_ACCESS_TOKEN },
    method: 'POST',
    json: messageData

  }, function (error, response, body) {
    if (!error && response.statusCode == 200) {
      var recipientId = body.recipient_id;
      var messageId = body.message_id;

      console.log("Successfully sent generic message with id %s to recipient %s",
        messageId, recipientId);
    } else {
      console.error("Unable to send message.");
      console.error(response);
      console.error(error);
    }
  });
}

// Start server
// Webhooks must be available via SSL with a certificate signed by a valid
// certificate authority.
app.listen(app.get('port'), function() {
  console.log('Node app is running on port', app.get('port'));
});

module.exports = app;

<|endoftext|>


<commit_before>var express = require('express');
var app = express();
var http = require('http');
var common = require('./common');
var data = require('./data');
var device = require('device');

var server = http.createServer(app);

var bodyParser = require('body-parser');

// for parsing application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: true}));

// parse application/json
app.use(bodyParser.json());

app.get('/api/logs/', function (req, res) {

    var startDate = req.query.startDate;
    var endDate = req.query.endDate;

    //console.log(startDate, endDate);

    data.getLogs(startDate, endDate, 100).then(function (logs) {
        res.json(logs);
    }, function () {
        res.status(500).send('data.getLogs failed');
    });

});

app.delete('/api/logs/', function (req, res) {

    // PLACE HERE

    data.deleteAllLogs()
        .then(function () {
            res.json();
        }, function () {
            res.status(500).send('data.getLogs failed');
        });

});

app.get('/api/download/', function (req, res) {

    var startDate = req.query.startDate;
    var endDate = req.query.endDate;

    data.getLogs(startDate, endDate)
        .then(function (logs) {
            common.getCsvLogs(logs)
                .then(function (csvData) {
                    res.setHeader('Content-disposition', 'attachment; filename=report.csv');
                    res.setHeader('Content-type', 'text/csv');
                    res.write(csvData);
                    res.end();
                });
        });
});

app.post('/api/report/', function (req, res) {
    var clientData = req.body;

    common.getIPAddressInfo(clientData.ip)
        .then(function (ipAddressInfo) {
            var logData = {
                ip: clientData.ip,
                time: clientData.time,
                referrer: clientData.referrer,
                query: clientData.query,
                userAgent: clientData.userAgent,
                url: clientData.url,

                country: ipAddressInfo.country,
                region: ipAddressInfo.region,
                isp: ipAddressInfo.isp,
                org: ipAddressInfo.org,
                city: ipAddressInfo.city
            };

            data.saveLog(logData).then(function () {
                res.send("success");

            }, function () {
                res.status(500).send('data.saveLog failed');
            });

        }, function () {
            res.status(500).send('common.getIPAddressInfo failed');
        });

});

app.get('/', function (req, res) {
    res.sendFile(__dirname + "/site.html");
});

var port = 8080;
server.listen(process.env.PORT || port);
console.log("listen to port", port);

<commit_msg>Changed port (8080 to 8000).<commit_after>var express = require('express');
var app = express();
var http = require('http');
var common = require('./common');
var data = require('./data');
var device = require('device');

var server = http.createServer(app);

var bodyParser = require('body-parser');

// for parsing application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: true}));

// parse application/json
app.use(bodyParser.json());

app.get('/api/logs/', function (req, res) {

    var startDate = req.query.startDate;
    var endDate = req.query.endDate;

    //console.log(startDate, endDate);

    data.getLogs(startDate, endDate, 100).then(function (logs) {
        res.json(logs);
    }, function () {
        res.status(500).send('data.getLogs failed');
    });

});

app.delete('/api/logs/', function (req, res) {

    // PLACE HERE

    data.deleteAllLogs()
        .then(function () {
            res.json();
        }, function () {
            res.status(500).send('data.getLogs failed');
        });

});

app.get('/api/download/', function (req, res) {

    var startDate = req.query.startDate;
    var endDate = req.query.endDate;

    data.getLogs(startDate, endDate)
        .then(function (logs) {
            common.getCsvLogs(logs)
                .then(function (csvData) {
                    res.setHeader('Content-disposition', 'attachment; filename=report.csv');
                    res.setHeader('Content-type', 'text/csv');
                    res.write(csvData);
                    res.end();
                });
        });
});

app.post('/api/report/', function (req, res) {
    var clientData = req.body;

    common.getIPAddressInfo(clientData.ip)
        .then(function (ipAddressInfo) {
            var logData = {
                ip: clientData.ip,
                time: clientData.time,
                referrer: clientData.referrer,
                query: clientData.query,
                userAgent: clientData.userAgent,
                url: clientData.url,

                country: ipAddressInfo.country,
                region: ipAddressInfo.region,
                isp: ipAddressInfo.isp,
                org: ipAddressInfo.org,
                city: ipAddressInfo.city
            };

            data.saveLog(logData).then(function () {
                res.send("success");

            }, function () {
                res.status(500).send('data.saveLog failed');
            });

        }, function () {
            res.status(500).send('common.getIPAddressInfo failed');
        });

});

app.get('/', function (req, res) {
    res.sendFile(__dirname + "/site.html");
});

var port = 8000;
server.listen(process.env.PORT || port);
console.log("listen to port", port);

<|endoftext|>


<commit_before>var express = require('express');
var app = express();
var ExpressPeerServer = require('peer').ExpressPeerServer;
var sha256 = require('js-sha256');
var fs = require('fs');

// pages = [{'route': '/content', 'file': 'content.html'}]
pages = {'/content': 'content.html'}

// Peer format: {'id': 'peerID', 'currentPage': '/content', 'pageHash': theirhash}
var peers = [];
var page_hashes = {}

var server = app.listen(9000);

var options = {
    debug: true
}

peerServer = ExpressPeerServer(server, options);

app.use('/api', peerServer);

app.get('/', function(req, res, next) {
	// console.log("///////////////");

	res.sendFile('index.html', { root : __dirname});
	// sendjson = {'peer_id': 'peer_id', 'content_hash': 'abcd'};
	// res.end(JSON.stringify(sendjson));

});

app.get('/static/*', function(req, res, next) {
	// console.log("staticstaticstaticstaticstaticstaticstaticstatic");
	res.sendFile(req.url.slice(1,req.url.length), { root : __dirname});
});

app.get('/*/*_metadata', function(req, res, next) {
	// console.log("ASDFGHJKL");
	// url = req.url.slice(0, req.url.length - 9)
	url = req.url.split("/");
	peer = url[1];
	url = url.slice(2,url.length);
	url = '/' + url.join('/');
	url = url.slice(0, url.length - 9)
	// console.log(url)
	if (url in page_hashes) {

		// by default, tell them to get the content from themselves. This means get it from the server.
		var to_connect_to = peer;

		for (var i = 0; i < peers.length; i++) {
			console.log(peers[i]['id']);

			if (peers[i]['id'] == peer) {
				// Update where this person is.
				peers[i]['currentPage'] = url;
				peers[i]['pageHash'] = page_hashes[url];
			} else {
				if (peers[i]['currentPage'] == url && peers[i]['pageHash'] == page_hashes[url]) {
					// Found a peer we can use to get the content
					to_connect_to = peers[i]['id'];
				}
			}
		}

		sendjson = {'peer_id': to_connect_to, 'content_hash': page_hashes[url]};
		res.send(JSON.stringify(sendjson));
	} else {
		res.send('404 page not found');
	}
});

app.get('/*/*', function(req, res, next) {
	url = req.url.split("/");
	peer = url[1];
	url = url.slice(2,url.length);
	url = '/' + url.join('/');

	if (url in page_hashes) {
		res.sendFile(pages[url],  { root : __dirname});
	} else {
		res.send('404 page not found: ' + url);
	}
});

peerServer.on('connection', function(id) {
	// send content if no others fully updated, otherwise send a client to connect to
	console.log("Connected! id: " + id);
	peers.push({'id': id, 'currentPage': undefined, 'pageHash': undefined});
	console.log('Peers: ' + peers);
});

peerServer.on('disconnect', function(id) {
	// Remove client from list

	var tempPeers = [];
	for(var p in peers) {
		if(p['id'] != id) {
			tempPeers.push(p);
		}
	}
	peers = tempPeers;

});

// Go through the configuration data, get the hash for the files and initialize the people on each page.
for (var route in pages) {
	var filepath = pages[route]
	var contents = fs.readFileSync(filepath, 'utf8');
	page_hashes[route] = sha256(contents);
}

console.log(page_hashes);<commit_msg>Error handling from the client<commit_after>var express = require('express');
var app = express();
var ExpressPeerServer = require('peer').ExpressPeerServer;
var sha256 = require('js-sha256');
var fs = require('fs');

// pages = [{'route': '/content', 'file': 'content.html'}]
pages = {'/content': 'content.html'}

// Peer format: {'id': 'peerID', 'currentPage': '/content', 'pageHash': theirhash}
var peers = [];
var page_hashes = {}

remove_peer = function(id) {
	var temp = [];
	for (var i = 0; i < peers.length; i++) {
		if (peers[i]['id'] != id) {
			temp.push(peers[i]);
		}
	}
	peers = temp;
}

var server = app.listen(9000);

var options = {
    debug: true
}

peerServer = ExpressPeerServer(server, options);

app.use('/api', peerServer);

app.get('/', function(req, res, next) {
	// console.log("///////////////");

	res.sendFile('index.html', { root : __dirname});
	// sendjson = {'peer_id': 'peer_id', 'content_hash': 'abcd'};
	// res.end(JSON.stringify(sendjson));

});

app.get('/static/*', function(req, res, next) {
	// console.log("staticstaticstaticstaticstaticstaticstaticstatic");
	res.sendFile(req.url.slice(1,req.url.length), { root : __dirname});
});

app.get('/error/*/*', function(req, res, next) {
	// Got an error from the client. Drop the offending peer and send the file they want.
	url = req.url.split('/');
	// null = url[0];
	// error = url[1];
	peer_id = url[2];
	request = '/' + url.slice(3, url.length).join('/')

	if (request in pages) {
		res.sendFile(pages[request], { root : __dirname});
	} else {
		res.send('404 page not found: ' + request)
	}

	// if()
	for (var i = peers.length - 1; i > -1; i--) {
		if (peers[i]['id'] == peer_id || peers[i]['pageHash'] != page_hashes[peers[i]['currentPage']]) {
			// delete peers[i];
			remove_peer(peer_id);
		}
	}
});

app.get('/*/*_metadata', function(req, res, next) {
	// console.log("ASDFGHJKL");
	// url = req.url.slice(0, req.url.length - 9)
	url = req.url.split("/");
	peer = url[1];
	url = url.slice(2,url.length);
	url = '/' + url.join('/');
	url = url.slice(0, url.length - 9)
	// console.log(url)
	if (url in page_hashes) {

		// by default, tell them to get the content from themselves. This means get it from the server.
		var to_connect_to = peer;

		for (var i = 0; i < peers.length; i++) {
			console.log(peers[i]['id']);
			// var minIndex = 0;
			var min = 999;

			if (peers[i]['id'] == peer) {
				// Update where this person is.
				peers[i]['currentPage'] = url;
				peers[i]['pageHash'] = page_hashes[url];
			} else {
				if (peers[i]['currentPage'] == url && peers[i]['pageHash'] == page_hashes[url]) {
					// Found a peer we can use to get the content
					if (peers[i]['peopleConnected'].length < min) {
						to_connect_to = peers[i]['id'];
						min = peers[i]['peopleConnected'].length;
					}

				}
			}
		}

		sendjson = {'peer_id': to_connect_to, 'content_hash': page_hashes[url]};
		res.send(JSON.stringify(sendjson));
	} else {
		res.send('404 page not found');
	}
});

app.get('/*/*', function(req, res, next) {
	url = req.url.split("/");
	peer = url[1];
	url = url.slice(2,url.length);
	url = '/' + url.join('/');

	if (url in page_hashes) {
		res.sendFile(pages[url],  { root : __dirname});
	} else {
		res.send('404 page not found: ' + url);
	}
});

peerServer.on('connection', function(id) {
	// send content if no others fully updated, otherwise send a client to connect to
	console.log("Connected! id: " + id);
	peers.push({'id': id, 'currentPage': undefined, 'pageHash': undefined, 'peopleConnected': []});
	console.log('Peers: ' + peers);
});

peerServer.on('disconnect', function(id) {
	// Remove client from list

	var tempPeers = [];
	for(var p in peers) {
		if(p['id'] != id) {
			tempPeers.push(p);
		}
	}
	peers = tempPeers;

});

// Go through the configuration data, get the hash for the files and initialize the people on each page.
for (var route in pages) {
	var filepath = pages[route]
	var contents = fs.readFileSync(filepath, 'utf8');
	page_hashes[route] = sha256(contents);
}

console.log(page_hashes);<|endoftext|>


<commit_before>/**
 * Dependencies
 */
var assign         = require('lodash-node/modern/objects/assign')
var bodyParser     = require('body-parser')
var compress       = require('compression')
var cookieParser   = require('cookie-parser')
var csp            = require('helmet-csp')
var csrf           = require('csurf')
var error          = require('./lib/error')
var errorHandler   = require('errorhandler')()
var express        = require('express')
var favicon        = require('serve-favicon')
var hbs            = require('hbs').__express
var isRegExp       = require('lodash-node/modern/objects/isRegExp')
var logger         = require('morgan')
var methodOverride = require('method-override')
var moment         = require('moment')
var mongoose       = require('mongoose')
var nodemailer     = require('nodemailer')
var passport       = require('passport')
var redis          = require('./lib/redis')
var session        = require('express-session')
var teamspeak      = require('./lib/teamspeak')
var utils          = require('./lib/utils')
var validator      = require('express-validator')
var RedisStore     = require('connect-redis')(session)

/**
 * Create Express server
 */
var app = module.exports = express()

require('./lib/helpers/flash')(app)
require('./lib/helpers/returnWith')(app)
require('./lib/helpers/handleMongooseError')(app)
require('./lib/helpers/resolveUrl')(app)
require('./lib/helpers/render')(app)

/**
 * Configuration
 */
var config = app.config = require('./lib/config')
var mincer = require('./lib/mincer')

/**
 * Redis
 */
redis.$.on('connect', function() {
  console.log('✔ Redis connection established.')
})
redis.$.on('error', function() {
  console.error('✗ Unable to connect to Redis.')
})

/**
 * Mongoose config
 */
mongoose.connect(config.secrets.db)
mongoose.connection.on('connected', function() {
  console.log('✔ MongoDB connection established.')
})
mongoose.connection.on('error', function() {
  console.error('✗ Unable to connect to MongoDB.')
})

/**
 * Teamspeak config
 */
teamspeak.connect(config.teamspeak)
teamspeak.on('connect', function() {
  console.log('✔ TeamSpeak Server Query connection established.')
})
teamspeak.on('error', function() {
  console.error('✗ Unable to connect to the TeamSpeak Server Query.')
})

if(!module.parent)
  require('./lib/teamspeak/statusMonitor')

/**
 * moment
 */
moment.locale('de')
app.moment = moment

/**
 * Express configuration
 */

// General setup
// --------------------------------
if(app.get('env') === 'production')
  app.enable('trust proxy')
app.disable('x-powered-by')
app.set('port', config.port || process.env.PORT || 3000)
app.set('views', __dirname + '/views')
app.set('view engine', 'jade')
app.engine('html', hbs)
app.engine('txt', hbs)
app.use(mincer.assets())
app.use(compress())
app.use(favicon(__dirname + '/public/favicon.ico'))
app.use(logger(process.env.LOGFORMAT || 'dev', {
  skip: function(req) { return /apple-touch-icon|browserconfig|favicon|mstile|robots|assets/.test(req.originalUrl.split('/')[1]) }
}))
app.use(cookieParser())
app.use(bodyParser.json())
app.use(bodyParser.json({ type: 'application/csp-report' }))
app.use(bodyParser.urlencoded({ extended: true }))
app.use(validator())
app.use(methodOverride())
app.use('/api', require('./lib/controllers/api'))
app.use(session({
  name: 'sid',
  resave: true,
  saveUninitialized: true,
  secret: config.secrets.session,
  genid: function() { return '' + new mongoose.Types.ObjectId },
  store: new RedisStore({
    client: redis.$
  })
}))
app.use(csp({
  defaultSrc: ["'self'", 'serve.gamekeller.net'],
  scriptSrc: ["'self'", "'unsafe-inline'", 'www.google-analytics.com'],
  styleSrc: ["'self'", "'unsafe-inline'", 'fonts.googleapis.com'],
  imgSrc: ["'self'", '0.gravatar.com', 'camo.gamekeller.net'],
  fontSrc: ["'self'", 'fonts.googleapis.com', 'fonts.gstatic.com'],
  connectSrc: ["'self'", 'www.reddit.com'],
  sandbox: ['allow-forms', 'allow-same-origin', 'allow-scripts'],
  reportUri: '/report-csp-violation'
}))
app.post('/report-csp-violation', function(req, res) {
  if(req.headers['content-type'] !== 'application/csp-report')
    return res.status(400).end()

  console.error('CSP Violation:', JSON.stringify(req.body, null, 2))
  res.status(200).end()
})
app.use(csrf())

// Mailer
// --------------------------------
var Mailer = require('./lib/mailer')
app.mailer = new Mailer(nodemailer.createTransport(config.mail), app.render.bind(app))

// Passport
// --------------------------------
app.use(passport.initialize())
app.use(passport.session())
require('./lib/helpers/auth')(app)

// Asset configuration
// --------------------------------
if(app.get('env') === 'development')
  app.use('/assets', mincer.createServer())

if(app.get('env') === 'development' || process.env.SIMULATE_PRODUCTION)
  app.use(express.static(__dirname + '/public'))

// Session updates
// --------------------------------
app.use(function(req, res, next) {
  var now  = Date.now()
  var sess = req.session

  sess.userAgent = req.headers['user-agent']
  sess.ipAddress = req.ip
  sess.usedAt    = now

  if(!sess.createdAt)
    sess.createdAt = now

  sess.save(next)
})

// Locals
// --------------------------------
app.use(function(req, res, next) {
  var user = req.user

  assign(res.locals, {
    req: req,
    config: config,
    user: user,
    moment: moment,
    url: req.resolveUrl.bind(req),
    utils: utils
  })

  utils.navItemIsActive = function(item) {
    return item.active || (isRegExp(item.view) && item.view.test(res.activeView)) || item.view === res.activeView
  }

  // Navigation items
  var nav = res.locals.nav = {
    main: [],
    right: []
  }

  if(user) {
    nav.main.push(
      { href: '/members', name: 'Mitglieder', view: 'members' }
    )

    if(user.admin)
      nav.main.push({ href: '/admin', name: 'Administration', view: /^admin/ })

    nav.right.push(
      { href: '/' + user.username, content: ' ' + user.username },
      { href: '/account', content: '', title: 'Einstellungen' },
      { href: '/logout', form: true, content: '<button type="submit" class="btn btn-link" title="Ausloggen"></button>' }
    )
  } else
    nav.right.push(
      { href: '/login', name: 'Login' },
      { href: '/signup', name: 'Registrieren' }
    )

  // Return path
  var route = req.path.split('/')[1]

  if(req.method === 'POST' || /login|logout|signup|session/i.test(route) || app.get('env') === 'development' && /apple-touch-icon|browserconfig|favicon|mstile|robots|assets/.test(route))
    return next()

  req.session.returnTo = req.path

  next()
})

/**
 * Routes
 */
app.use(require('./lib/controllers/common'))

// Authentication
app.use('/signup', require('./lib/controllers/auth/signup'))
app.use('/login', require('./lib/controllers/auth/login'))
app.use('/logout', require('./lib/controllers/auth/logout'))
app.use('/forgot', require('./lib/controllers/auth/forgot'))

app.use('/account', require('./lib/controllers/account'))
app.use('/session', require('./lib/controllers/session'))
app.use('/admin', require('./lib/controllers/admin'))
app.use(require('./lib/controllers/profile'))

// 404
app.use(function(req, res, next) {
  return next(new error.NotFound)
})

// Error handler
// --------------------------------
app.use(function(err, req, res, next) {
  if(err.code === 'EBADCSRFTOKEN')
    err = new error.Forbidden('Invalid CSRF token')

  if(!(err instanceof error.HTTPError))
    if(app.get('env') === 'production') {
      console.error(err.stack)
      err = new error.HTTPError(500)
    } else {
      return errorHandler.apply(null, Array.prototype.slice.call(arguments))
    }

  var isApi = req.url.indexOf('/api') === 0

  res.status(err.status)

  if(req.accepts('html') && !isApi)
    res.type('html').send(err.toHTMLString())
  else if(req.accepts('json'))
    res.json(err)
  else
    res.type('txt').send(err.toString())
})

/**
 * Start the server
 */
if(!module.parent)
  app.listen(app.get('port'), function() {
    console.log('✔ Express server listening on port %d in %s mode.', app.get('port'), app.get('env'))
  })<commit_msg>CSP: Add GA to allowed image sources<commit_after>/**
 * Dependencies
 */
var assign         = require('lodash-node/modern/objects/assign')
var bodyParser     = require('body-parser')
var compress       = require('compression')
var cookieParser   = require('cookie-parser')
var csp            = require('helmet-csp')
var csrf           = require('csurf')
var error          = require('./lib/error')
var errorHandler   = require('errorhandler')()
var express        = require('express')
var favicon        = require('serve-favicon')
var hbs            = require('hbs').__express
var isRegExp       = require('lodash-node/modern/objects/isRegExp')
var logger         = require('morgan')
var methodOverride = require('method-override')
var moment         = require('moment')
var mongoose       = require('mongoose')
var nodemailer     = require('nodemailer')
var passport       = require('passport')
var redis          = require('./lib/redis')
var session        = require('express-session')
var teamspeak      = require('./lib/teamspeak')
var utils          = require('./lib/utils')
var validator      = require('express-validator')
var RedisStore     = require('connect-redis')(session)

/**
 * Create Express server
 */
var app = module.exports = express()

require('./lib/helpers/flash')(app)
require('./lib/helpers/returnWith')(app)
require('./lib/helpers/handleMongooseError')(app)
require('./lib/helpers/resolveUrl')(app)
require('./lib/helpers/render')(app)

/**
 * Configuration
 */
var config = app.config = require('./lib/config')
var mincer = require('./lib/mincer')

/**
 * Redis
 */
redis.$.on('connect', function() {
  console.log('✔ Redis connection established.')
})
redis.$.on('error', function() {
  console.error('✗ Unable to connect to Redis.')
})

/**
 * Mongoose config
 */
mongoose.connect(config.secrets.db)
mongoose.connection.on('connected', function() {
  console.log('✔ MongoDB connection established.')
})
mongoose.connection.on('error', function() {
  console.error('✗ Unable to connect to MongoDB.')
})

/**
 * Teamspeak config
 */
teamspeak.connect(config.teamspeak)
teamspeak.on('connect', function() {
  console.log('✔ TeamSpeak Server Query connection established.')
})
teamspeak.on('error', function() {
  console.error('✗ Unable to connect to the TeamSpeak Server Query.')
})

if(!module.parent)
  require('./lib/teamspeak/statusMonitor')

/**
 * moment
 */
moment.locale('de')
app.moment = moment

/**
 * Express configuration
 */

// General setup
// --------------------------------
if(app.get('env') === 'production')
  app.enable('trust proxy')
app.disable('x-powered-by')
app.set('port', config.port || process.env.PORT || 3000)
app.set('views', __dirname + '/views')
app.set('view engine', 'jade')
app.engine('html', hbs)
app.engine('txt', hbs)
app.use(mincer.assets())
app.use(compress())
app.use(favicon(__dirname + '/public/favicon.ico'))
app.use(logger(process.env.LOGFORMAT || 'dev', {
  skip: function(req) { return /apple-touch-icon|browserconfig|favicon|mstile|robots|assets/.test(req.originalUrl.split('/')[1]) }
}))
app.use(cookieParser())
app.use(bodyParser.json())
app.use(bodyParser.json({ type: 'application/csp-report' }))
app.use(bodyParser.urlencoded({ extended: true }))
app.use(validator())
app.use(methodOverride())
app.use('/api', require('./lib/controllers/api'))
app.use(session({
  name: 'sid',
  resave: true,
  saveUninitialized: true,
  secret: config.secrets.session,
  genid: function() { return '' + new mongoose.Types.ObjectId },
  store: new RedisStore({
    client: redis.$
  })
}))
app.use(csp({
  defaultSrc: ["'self'", 'serve.gamekeller.net'],
  scriptSrc: ["'self'", "'unsafe-inline'", 'www.google-analytics.com'],
  styleSrc: ["'self'", "'unsafe-inline'", 'fonts.googleapis.com'],
  imgSrc: ["'self'", '0.gravatar.com', 'camo.gamekeller.net', 'www.google-analytics.com'],
  fontSrc: ["'self'", 'fonts.googleapis.com', 'fonts.gstatic.com'],
  connectSrc: ["'self'", 'www.reddit.com'],
  sandbox: ['allow-forms', 'allow-same-origin', 'allow-scripts'],
  reportUri: '/report-csp-violation'
}))
app.post('/report-csp-violation', function(req, res) {
  if(req.headers['content-type'] !== 'application/csp-report')
    return res.status(400).end()

  console.error('CSP Violation:', JSON.stringify(req.body, null, 2))
  res.status(200).end()
})
app.use(csrf())

// Mailer
// --------------------------------
var Mailer = require('./lib/mailer')
app.mailer = new Mailer(nodemailer.createTransport(config.mail), app.render.bind(app))

// Passport
// --------------------------------
app.use(passport.initialize())
app.use(passport.session())
require('./lib/helpers/auth')(app)

// Asset configuration
// --------------------------------
if(app.get('env') === 'development')
  app.use('/assets', mincer.createServer())

if(app.get('env') === 'development' || process.env.SIMULATE_PRODUCTION)
  app.use(express.static(__dirname + '/public'))

// Session updates
// --------------------------------
app.use(function(req, res, next) {
  var now  = Date.now()
  var sess = req.session

  sess.userAgent = req.headers['user-agent']
  sess.ipAddress = req.ip
  sess.usedAt    = now

  if(!sess.createdAt)
    sess.createdAt = now

  sess.save(next)
})

// Locals
// --------------------------------
app.use(function(req, res, next) {
  var user = req.user

  assign(res.locals, {
    req: req,
    config: config,
    user: user,
    moment: moment,
    url: req.resolveUrl.bind(req),
    utils: utils
  })

  utils.navItemIsActive = function(item) {
    return item.active || (isRegExp(item.view) && item.view.test(res.activeView)) || item.view === res.activeView
  }

  // Navigation items
  var nav = res.locals.nav = {
    main: [],
    right: []
  }

  if(user) {
    nav.main.push(
      { href: '/members', name: 'Mitglieder', view: 'members' }
    )

    if(user.admin)
      nav.main.push({ href: '/admin', name: 'Administration', view: /^admin/ })

    nav.right.push(
      { href: '/' + user.username, content: ' ' + user.username },
      { href: '/account', content: '', title: 'Einstellungen' },
      { href: '/logout', form: true, content: '<button type="submit" class="btn btn-link" title="Ausloggen"></button>' }
    )
  } else
    nav.right.push(
      { href: '/login', name: 'Login' },
      { href: '/signup', name: 'Registrieren' }
    )

  // Return path
  var route = req.path.split('/')[1]

  if(req.method === 'POST' || /login|logout|signup|session/i.test(route) || app.get('env') === 'development' && /apple-touch-icon|browserconfig|favicon|mstile|robots|assets/.test(route))
    return next()

  req.session.returnTo = req.path

  next()
})

/**
 * Routes
 */
app.use(require('./lib/controllers/common'))

// Authentication
app.use('/signup', require('./lib/controllers/auth/signup'))
app.use('/login', require('./lib/controllers/auth/login'))
app.use('/logout', require('./lib/controllers/auth/logout'))
app.use('/forgot', require('./lib/controllers/auth/forgot'))

app.use('/account', require('./lib/controllers/account'))
app.use('/session', require('./lib/controllers/session'))
app.use('/admin', require('./lib/controllers/admin'))
app.use(require('./lib/controllers/profile'))

// 404
app.use(function(req, res, next) {
  return next(new error.NotFound)
})

// Error handler
// --------------------------------
app.use(function(err, req, res, next) {
  if(err.code === 'EBADCSRFTOKEN')
    err = new error.Forbidden('Invalid CSRF token')

  if(!(err instanceof error.HTTPError))
    if(app.get('env') === 'production') {
      console.error(err.stack)
      err = new error.HTTPError(500)
    } else {
      return errorHandler.apply(null, Array.prototype.slice.call(arguments))
    }

  var isApi = req.url.indexOf('/api') === 0

  res.status(err.status)

  if(req.accepts('html') && !isApi)
    res.type('html').send(err.toHTMLString())
  else if(req.accepts('json'))
    res.json(err)
  else
    res.type('txt').send(err.toString())
})

/**
 * Start the server
 */
if(!module.parent)
  app.listen(app.get('port'), function() {
    console.log('✔ Express server listening on port %d in %s mode.', app.get('port'), app.get('env'))
  })<|endoftext|>


<commit_before>
/**
* Module dependencies.
*/

var express = require('express');
var expressValidator = require('express-validator');
var http = require('http');
var path = require('path');
var json2csv = require('json2csv');
var fs = require('fs');

var app = express();
var storage = require('./storage')();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(expressValidator());
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

app.get('/', function(req, res) {
  storage.all(function(err, data) {
    shuffle(data);
    res.render('index', {reports: data, thanks: false, errors: {}});
  })
})

function shuffle(array) {
    var counter = array.length, temp, index;
    while (counter > 0) {
        index = Math.floor(Math.random() * counter);
        counter--;
        temp = array[counter];
        array[counter] = array[index];
        array[index] = temp;
    }
    return array;
}

var fields = ["time_of_month", "month", "year", "fee", "currency", "client", "where", "job", "time_amount", "time_unit", "medium", "experience", "gender", "working_years", "also"];
var fieldNames = ["Time of month", "Month", "Year", "Fee", "Currency", "Client", "Where", "Job", "Time amount", "Time unit", "Medium", "Experience", "Gender", "Working years", "Also"];

app.get('/data.csv', function(req, res) {
  storage.all(function(err, data) {
    shuffle(data);
    json2csv({data: data, fields: fields, fieldNames: fieldNames}, function(err, csv) {
      res.attachment('data.csv');
      res.set('Content-type', 'text/csv');
      res.send(csv);
    })
  })
})

app.get('/data.json', function(req, res) {
  storage.all(function(err, data) {
    shuffle(data);
    res.attachment('data.json');
    res.set('Content-type', 'application/json');
    res.send(data);
  })
})

function getTimeOfMonth(date) {
  var day = date.getDate();
  if(day < 10) {
    return "early";
  } else if(day < 20) {
    return "mid";
  } else {
    return "late";
  }
}

var currencies = ['USD', 'EUR', 'GBP', 'JPY'];
var jobs = ['', 'workshop', 'talk/lecture', 'panel', 'teaching position', 'residency', 'grant', 'commission', 'exhibition', 'performance'];
var timeUnits = ['minutes', 'hours', 'days', 'weeks', 'months'];
var experiences = ['', 'unusually good', 'good', 'bad', 'unusually bad'];
var genders = ['person', 'woman', 'man'];

expressValidator.validator.extend('isPositiveOptional', function (str) {
  if(str && str.length > 0) {
    return parseInt(str) >= 0;
  } else {
    return true;
  }
});

app.post('/', function(req, res) {
  req.assert('fee', 'Fee is too long or contains non-digits. Try rounding the value.').matches(/^\d{0,8}$/);
  req.assert('currency', 'Currency must be one of: ' + currencies.join(',') + '.').isIn(currencies);
  req.assert('client', 'Client is too long.').isLength(0, 80);
  req.assert('job', 'Job must be one of: ' + jobs.join(',') + '.').isIn(jobs);
  req.assert('where', 'Where is too long.').isLength(0, 80);
  req.assert('time_amount', 'Time amount is invalid.').matches(/^\d{0,4}$/);
  req.assert('time_unit', 'Time unit must be one of: ' + timeUnits.join(',') + '.').isIn(timeUnits);
  req.assert('medium', 'Medium is too long.').isLength(0, 80);
  req.assert('experience', 'Experience must be one of: ' + experiences.join(',') + '.').isIn(experiences);
  req.assert('gender', 'Gender must be one of: ' + genders.join(',') + '.').isIn(genders);
  req.assert('working_years', 'Working years are invalid.').matches(/^\d{0,2}$/);
  req.assert('also', 'Also is too long.').isLength(0, 500);

  var errors = req.validationErrors();
  if( !( req.body.fee || req.body.client || req.body.job || req.body.where ) ) {
    errors = [{msg: 'Please fill out at least one field.'}];
  }

  if(errors) {
    storage.all(function(err, data) {
      shuffle(data);
      res.render('index', {reports: data, thanks: false, errors: errors});
    })
  } else {
    var date = new Date();
    var report = {
      // generated
      time_of_month: getTimeOfMonth(date),
      month: date.getMonth(),
      year: date.getFullYear()
    };
    // submitted
    if(req.body.fee || req.body.fee == '0') {
      report.fee = parseInt(req.body.fee);
      report.currency = req.body.currency;
    }
    addField(report, 'client', (req.body.client));
    addField(report, 'job', req.body.job);
    addField(report, 'where', (req.body.where));
    if(req.body.time_amount) {
      report.time_amount = parseInt(req.body.time_amount);
      report.time_unit = req.body.time_unit;
    }
    addField(report, 'experience', req.body.experience);
    addField(report, 'gender', req.body.gender);
    addField(report, 'medium', req.body.medium);
    addField(report, 'working_years', parseInt(req.body.working_years));
    addField(report, 'also', (req.body.also));

    // ignore a few things
    ignoreField(report, 'medium', 'art');
    ignoreField(report, 'medium', 'this');

    storage.insert(report, function(doc) {
      storage.all(function(err, data) {
        shuffle(data);
        res.render('index', {reports: data, thanks: true, reportId: doc._id, errors: errors});
      });
    });
  }
});

function addField(object, field, data) {
  if(data || data == 0) {
    object[field] = data;
  }
}

function ignoreField(object, field, data) {
  if(object[field] && object[field] == data) {
    delete object[field];
  }
}

app.get('/:id', function(req, res) {
  storage.get(req.params.id, function(err, data) {
    res.render('item', {report: data.length ? data[0] : {}, err: err});
  })
})
<commit_msg>ignore empty fields properly<commit_after>
/**
* Module dependencies.
*/

var express = require('express');
var expressValidator = require('express-validator');
var http = require('http');
var path = require('path');
var json2csv = require('json2csv');
var fs = require('fs');

var app = express();
var storage = require('./storage')();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(expressValidator());
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

app.get('/', function(req, res) {
  storage.all(function(err, data) {
    shuffle(data);
    res.render('index', {reports: data, thanks: false, errors: {}});
  })
})

function shuffle(array) {
    var counter = array.length, temp, index;
    while (counter > 0) {
        index = Math.floor(Math.random() * counter);
        counter--;
        temp = array[counter];
        array[counter] = array[index];
        array[index] = temp;
    }
    return array;
}

var fields = ["time_of_month", "month", "year", "fee", "currency", "client", "where", "job", "time_amount", "time_unit", "medium", "experience", "gender", "working_years", "also"];
var fieldNames = ["Time of month", "Month", "Year", "Fee", "Currency", "Client", "Where", "Job", "Time amount", "Time unit", "Medium", "Experience", "Gender", "Working years", "Also"];

app.get('/data.csv', function(req, res) {
  storage.all(function(err, data) {
    shuffle(data);
    json2csv({data: data, fields: fields, fieldNames: fieldNames}, function(err, csv) {
      res.attachment('data.csv');
      res.set('Content-type', 'text/csv');
      res.send(csv);
    })
  })
})

app.get('/data.json', function(req, res) {
  storage.all(function(err, data) {
    shuffle(data);
    res.attachment('data.json');
    res.set('Content-type', 'application/json');
    res.send(data);
  })
})

function getTimeOfMonth(date) {
  var day = date.getDate();
  if(day < 10) {
    return "early";
  } else if(day < 20) {
    return "mid";
  } else {
    return "late";
  }
}

var currencies = ['USD', 'EUR', 'GBP', 'JPY'];
var jobs = ['', 'workshop', 'talk/lecture', 'panel', 'teaching position', 'residency', 'grant', 'commission', 'exhibition', 'performance'];
var timeUnits = ['minutes', 'hours', 'days', 'weeks', 'months'];
var experiences = ['', 'unusually good', 'good', 'bad', 'unusually bad'];
var genders = ['person', 'woman', 'man'];

expressValidator.validator.extend('isPositiveOptional', function (str) {
  if(str && str.length > 0) {
    return parseInt(str) >= 0;
  } else {
    return true;
  }
});

app.post('/', function(req, res) {
  req.assert('fee', 'Fee is too long or contains non-digits. Try rounding the value.').matches(/^\d{0,8}$/);
  req.assert('currency', 'Currency must be one of: ' + currencies.join(',') + '.').isIn(currencies);
  req.assert('client', 'Client is too long.').isLength(0, 80);
  req.assert('job', 'Job must be one of: ' + jobs.join(',') + '.').isIn(jobs);
  req.assert('where', 'Where is too long.').isLength(0, 80);
  req.assert('time_amount', 'Time amount is invalid.').matches(/^\d{0,4}$/);
  req.assert('time_unit', 'Time unit must be one of: ' + timeUnits.join(',') + '.').isIn(timeUnits);
  req.assert('medium', 'Medium is too long.').isLength(0, 80);
  req.assert('experience', 'Experience must be one of: ' + experiences.join(',') + '.').isIn(experiences);
  req.assert('gender', 'Gender must be one of: ' + genders.join(',') + '.').isIn(genders);
  req.assert('working_years', 'Working years are invalid.').matches(/^\d{0,2}$/);
  req.assert('also', 'Also is too long.').isLength(0, 500);

  var errors = req.validationErrors();
  if( !( req.body.fee || req.body.client || req.body.job || req.body.where ) ) {
    errors = [{msg: 'Please fill out at least one field.'}];
  }

  if(errors) {
    storage.all(function(err, data) {
      shuffle(data);
      res.render('index', {reports: data, thanks: false, errors: errors});
    })
  } else {
    var date = new Date();
    var report = {
      // generated
      time_of_month: getTimeOfMonth(date),
      month: date.getMonth(),
      year: date.getFullYear()
    };
    // submitted
    if(req.body.fee || req.body.fee == '0') {
      report.fee = parseInt(req.body.fee);
      report.currency = req.body.currency;
    }
    addField(report, 'client', (req.body.client));
    addField(report, 'job', req.body.job);
    addField(report, 'where', (req.body.where));
    if(req.body.time_amount) {
      report.time_amount = parseInt(req.body.time_amount);
      report.time_unit = req.body.time_unit;
    }
    addField(report, 'experience', req.body.experience);
    addField(report, 'gender', req.body.gender);
    addField(report, 'medium', req.body.medium);
    addField(report, 'working_years', parseInt(req.body.working_years));
    addField(report, 'also', (req.body.also));

    // ignore a few things
    ignoreField(report, 'medium', 'art');
    ignoreField(report, 'medium', 'this');

    storage.insert(report, function(doc) {
      storage.all(function(err, data) {
        shuffle(data);
        res.render('index', {reports: data, thanks: true, reportId: doc._id, errors: errors});
      });
    });
  }
});

function addField(object, field, data) {
  // a string with length > 0
  // or a number that is 0 or greater
  if((typeof data == 'string' && data.length > 0) ||
    (typeof data == 'number' && data >= 0)) {
    object[field] = data;
  }
}

function ignoreField(object, field, data) {
  if(object[field] && object[field] == data) {
    delete object[field];
  }
}

app.get('/:id', function(req, res) {
  storage.get(req.params.id, function(err, data) {
    res.render('item', {report: data.length ? data[0] : {}, err: err});
  })
})
<|endoftext|>


<commit_before>// Design:
//
// * There are several regions in the game;
//   Sheep Stock,
//   Field,
//   Hand,
//   Deck,
//   Discard pile,
//   Exile.
// * A region contains a pile of cards.
// * A pile of cards is expressed as an array.
// * The first card in an array is corresponding to
//   the bottom card in a pile or the first card put into the pile.
// * The last card in an array is corresponding to
//   the top card in a pile or the last card put into the pile.

var shephy = {};

(function (S, $) {
  S.RANKS = [1, 3, 10, 30, 100, 300, 1000];

  function random(n) {
    return Math.floor(Math.random() * n);
  }

  function shuffle(xs) {
    for (var i = 0; i < xs.length; i++) {
      var j = random(xs.length - i);
      var tmp = xs[i];
      xs[i] = xs[j];
      xs[j] = tmp;
    }
  }

  S.delay = function(expressionAsFunction) {
    var result;
    var isEvaluated = false;

    return function () {
      if (!isEvaluated) {
        result = expressionAsFunction();
        isEvaluated = true;
      }
      return result;
    };
  };

  S.force = function (promise) {
    return promise();
  };

  S.clone = function (x) {
    return JSON.parse(JSON.stringify(x));
  };

  function makeSheepCard(rank) {
    return {
      name: rank + '',
      rank: rank
    };
  }

  function makeSheepStockPile(n) {
    var cards = [];
    for (var i = 0; i < 7; i++) {
      cards.push({
        name: n + '',
        rank: n
      });
    }
    return cards;
  }

  function makeEventCard(name) {
    // TODO: Implement effects when a card is played.
    return {
      name: name
    };
  }

  function makeInitalDeck() {
    var names = [
      'All-purpose Sheep',
      'Be Fruitful',
      'Be Fruitful',
      'Be Fruitful',
      'Crowding',
      'Dominion',
      'Dominion',
      'Falling Rock',
      'Fill the Earth',
      'Flourish',
      'Golden Hooves',
      'Inspiration',
      'Lightning',
      'Meteor',
      'Multiply',
      'Plague',
      'Planning Sheep',
      'Sheep Dog',
      'Shephion',
      'Slump',
      'Storm',
      'Wolves'
    ];
    var cards = names.map(makeEventCard);
    shuffle(cards);
    return cards;
  }

  S.makeInitalWorld = function () {
    var sheepStock = {};
    S.RANKS.forEach(function (rank) {
      sheepStock[rank] = makeSheepStockPile(rank);
    });

    var initialSheepCard = sheepStock[1].pop();

    return {
      sheepStock: sheepStock,
      field: [initialSheepCard],
      enemySheepCount: 1,
      deck: makeInitalDeck(),
      hand: [],
      discardPile: [],
      exile: []
    };
  };

  S.gainX = function (world, rank) {
    if (world.sheepStock[rank].length == 0)
      return;
    if (7 - world.field.length <= 0)
      return;

    world.field.push(world.sheepStock[rank].pop());
  };

  S.releaseX = function (world, fieldIndex) {
    var c = world.field.splice(fieldIndex, 1)[0];
    world.sheepStock[c.rank].push(c);
  };

  S.discardX = function (world, handIndex) {
    var c = world.hand.splice(handIndex, 1)[0];
    world.discardPile.push(c);
  };

  S.exileX = function (world, region, index) {
    var c = region.splice(index, 1)[0];
    world.exile.push(c);
  };

  S.drawX = function (world) {
    if (world.deck.length == 0)
      return;
    if (5 - world.hand.length <= 0)
      return;

    world.hand.push(world.deck.pop());
  };

  S.remakeDeckX = function (world) {
    world.deck.push.apply(world.deck, world.discardPile);
    world.discardPile = [];
    shuffle(world.deck);
  };

  S.shouldDraw = function (world) {
    return world.hand.length < 5 && 0 < world.deck.length;
  };

  S.judgeGame = function (world) {
    if (world.field.some(function (c) {return c.rank == 1000;})) {
      return {
        result: 'win',
        description: 'You win!'
      };
    }
    if (world.enemySheepCount == 1000) {
      return {
        result: 'lose',
        description: 'Enemies reached 1000 sheep - you lose.'
      };
    }
    if (world.field.length == 0) {
      return {
        result: 'lose',
        description: 'You lost all your sheep - you lose.'
      };
    }

    throw 'Invalid operation';
  };

  S.makeGameTree = function (world, opt_state) {
    return {
      world: world,
      moves: S.listPossibleMoves(world, opt_state)
    };
  };

  S.listPossibleMoves = function (world, opt_state) {
    if (opt_state === undefined)
      return S.listPossibleMovesForBasicRules(world);
    else
      return S.listPossibleMovesForPlayingCard(world, opt_state);
  }

  S.listPossibleMovesForBasicRules = function (world) {
    // TODO: Add an option to continue the current game to compete high score.
    if (world.field.some(function (c) {return c.rank == 1000;}))
      return [];

    if (1000 <= world.enemySheepCount)
      return [];

    if (world.field.length == 0)
      return [];

    if (world.hand.length == 0 && world.deck.length == 0) {
      return [
        {
          description: 'Remake Deck then fill Hand',
          gameTreePromise: S.delay(function () {
            var wn = S.clone(world);
            S.remakeDeckX(wn);
            while (S.shouldDraw(wn))
              S.drawX(wn);
            wn.enemySheepCount *= 10;
            return S.makeGameTree(wn);
          })
        }
      ];
    }

    if (S.shouldDraw(world)) {
      return [
        {
          description:
            5 - world.hand.length == 1
            ? 'Draw a card'
            : 'Draw cards',
          gameTreePromise: S.delay(function () {
            var wn = S.clone(world);
            while (S.shouldDraw(wn))
              S.drawX(wn);
            return S.makeGameTree(wn);
          })
        }
      ];
    }

    return world.hand.map(function (c, i) {
      return {
        description: 'Play ' + c.name,
        gameTreePromise: S.delay(function () {
          var wn = S.clone(world);
          S.discardX(wn, i);
          return S.makeGameTree(wn, {step: c.name});
        })
      };
    });
  };

  S.listPossibleMovesForPlayingCard = function (world, state) {
    var moves = [];

    // TODO: Implement more cards.
    switch (state.step) {
      case 'Fill the Earth':
        if (world.field.length < 7) {
          moves.push({
            description: 'Gain a 1 Sheep card',
            gameTreePromise: S.delay(function () {
              var wn = S.clone(world);
              S.gainX(wn, 1);
              return S.makeGameTree(wn, state);
            })
          });
        }
        moves.push({
          description: 'Cancel',
          gameTreePromise: S.delay(function () {
            return S.makeGameTree(world);
          })
        });
        break;
      case 'Multiply':
        if (world.field.length < 7 && 0 < world.sheepStock[3].length) {
          moves.push({
            description: 'Gain a 3 Sheep card',
            gameTreePromise: S.delay(function () {
              var wn = S.clone(world);
              S.gainX(wn, 3);
              return S.makeGameTree(wn);
            })
          });
        } else {
          moves.push({
            description: 'Nothing happened',
            gameTreePromise: S.delay(function () {
              return S.makeGameTree(world);
            })
          });
        }
        break;
      case 'Planning Sheep':
        world.hand.forEach(function (c, i) {
          moves.push({
            description: 'Exile ' + c.name,
            gameTreePromise: S.delay(function () {
              var wn = S.clone(world);
              S.exileX(wn, wn.hand, i);
              return S.makeGameTree(wn);
            })
          });
        });
        if (world.hand.length == 0) {
          moves.push({
            description: 'Nothing happened',
            gameTreePromise: S.delay(function () {
              return S.makeGameTree(world);
            })
          });
        }
        break;
      case 'Sheep Dog':
        world.hand.forEach(function (c, i) {
          moves.push({
            description: 'Discard ' + c.name,
            gameTreePromise: S.delay(function () {
              var wn = S.clone(world);
              S.discardX(wn, i);
              return S.makeGameTree(wn);
            })
          });
        });
        if (world.hand.length == 0) {
          moves.push({
            description: 'Nothing happened',
            gameTreePromise: S.delay(function () {
              return S.makeGameTree(world);
            })
          });
        }
        break;
      default:
        throw 'Invalid operation: state = ' + JSON.stringify(state);
    }

    return moves;
  };

  // UI  {{{1
  // TODO: Add UI to start a new game after finishing a game.
  // TODO: Add UI to quit the current game.
  // TODO: Choose a move automatically if it doesn't express a user's choice.
  //       Examples: "Draw cards" and "Remake Deck and fill Hand".
  // TODO: Render cards as a stack of card-like shapes, not text.
  // TODO: Make #world elements clickable to choose a move.
  //       For example:
  //       - Click a card in Hand to play the card.
  //       - Click a card in Field to duplicate by playing Be Fruitful.
  // TODO: Show a card text if the cursor is hovered on the card.

  function textizeCards(cs) {
    if (cs.length == 0)
      return '-';
    else
      return cs.map(function (c) {return c.name;}).join(', ');
  }

  function nodizeMove(m) {
    var $m = $('<input>');
    $m.attr({
      type: 'button',
      value: m.description
    });
    $m.click(function () {
      drawGameTree(S.force(m.gameTreePromise));
    });
    return $m;
  }

  function drawGameTree(gameTree) {
    var w = gameTree.world;
    S.RANKS.forEach(function (rank) {
      $('#sheepStock' + rank + ' > .count').text(w.sheepStock[rank].length);
    });
    $('#enemySheepCount > .count').text(w.enemySheepCount);
    $('#field > .cards').text(
      w.field
      .map(function (c) {return c.rank;})
      .join(', ')
    );
    $('#hand > .cards').text(textizeCards(w.hand));
    $('#deck > .count').text(w.deck.length);
    $('#discardPile > .cards').text(textizeCards(w.discardPile));
    $('#exile > .cards').text(textizeCards(w.exile));

    $('#message').text(
      gameTree.moves.length == 0
      ? S.judgeGame(gameTree.world).description
      : 'Choose a move:'
    );
    $('#moves').empty().append(gameTree.moves.map(nodizeMove));
  }

  // Bootstrap  {{{1
  // TODO: Revise UI to start the first game after page load.
  //       (Show "Start a game" instead of "Draw cards)

  $(function () {
    drawGameTree(S.makeGameTree(S.makeInitalWorld()));
  });

  //}}}1
})(shephy, jQuery);

// vim: expandtab softtabstop=2 shiftwidth=2 foldmethod=marker
<commit_msg>Use makeSheepCard<commit_after>// Design:
//
// * There are several regions in the game;
//   Sheep Stock,
//   Field,
//   Hand,
//   Deck,
//   Discard pile,
//   Exile.
// * A region contains a pile of cards.
// * A pile of cards is expressed as an array.
// * The first card in an array is corresponding to
//   the bottom card in a pile or the first card put into the pile.
// * The last card in an array is corresponding to
//   the top card in a pile or the last card put into the pile.

var shephy = {};

(function (S, $) {
  S.RANKS = [1, 3, 10, 30, 100, 300, 1000];

  function random(n) {
    return Math.floor(Math.random() * n);
  }

  function shuffle(xs) {
    for (var i = 0; i < xs.length; i++) {
      var j = random(xs.length - i);
      var tmp = xs[i];
      xs[i] = xs[j];
      xs[j] = tmp;
    }
  }

  S.delay = function(expressionAsFunction) {
    var result;
    var isEvaluated = false;

    return function () {
      if (!isEvaluated) {
        result = expressionAsFunction();
        isEvaluated = true;
      }
      return result;
    };
  };

  S.force = function (promise) {
    return promise();
  };

  S.clone = function (x) {
    return JSON.parse(JSON.stringify(x));
  };

  function makeSheepCard(rank) {
    return {
      name: rank + '',
      rank: rank
    };
  }

  function makeSheepStockPile(n) {
    var cards = [];
    for (var i = 0; i < 7; i++)
      cards.push(makeSheepCard(n));
    return cards;
  }

  function makeEventCard(name) {
    // TODO: Implement effects when a card is played.
    return {
      name: name
    };
  }

  function makeInitalDeck() {
    var names = [
      'All-purpose Sheep',
      'Be Fruitful',
      'Be Fruitful',
      'Be Fruitful',
      'Crowding',
      'Dominion',
      'Dominion',
      'Falling Rock',
      'Fill the Earth',
      'Flourish',
      'Golden Hooves',
      'Inspiration',
      'Lightning',
      'Meteor',
      'Multiply',
      'Plague',
      'Planning Sheep',
      'Sheep Dog',
      'Shephion',
      'Slump',
      'Storm',
      'Wolves'
    ];
    var cards = names.map(makeEventCard);
    shuffle(cards);
    return cards;
  }

  S.makeInitalWorld = function () {
    var sheepStock = {};
    S.RANKS.forEach(function (rank) {
      sheepStock[rank] = makeSheepStockPile(rank);
    });

    var initialSheepCard = sheepStock[1].pop();

    return {
      sheepStock: sheepStock,
      field: [initialSheepCard],
      enemySheepCount: 1,
      deck: makeInitalDeck(),
      hand: [],
      discardPile: [],
      exile: []
    };
  };

  S.gainX = function (world, rank) {
    if (world.sheepStock[rank].length == 0)
      return;
    if (7 - world.field.length <= 0)
      return;

    world.field.push(world.sheepStock[rank].pop());
  };

  S.releaseX = function (world, fieldIndex) {
    var c = world.field.splice(fieldIndex, 1)[0];
    world.sheepStock[c.rank].push(c);
  };

  S.discardX = function (world, handIndex) {
    var c = world.hand.splice(handIndex, 1)[0];
    world.discardPile.push(c);
  };

  S.exileX = function (world, region, index) {
    var c = region.splice(index, 1)[0];
    world.exile.push(c);
  };

  S.drawX = function (world) {
    if (world.deck.length == 0)
      return;
    if (5 - world.hand.length <= 0)
      return;

    world.hand.push(world.deck.pop());
  };

  S.remakeDeckX = function (world) {
    world.deck.push.apply(world.deck, world.discardPile);
    world.discardPile = [];
    shuffle(world.deck);
  };

  S.shouldDraw = function (world) {
    return world.hand.length < 5 && 0 < world.deck.length;
  };

  S.judgeGame = function (world) {
    if (world.field.some(function (c) {return c.rank == 1000;})) {
      return {
        result: 'win',
        description: 'You win!'
      };
    }
    if (world.enemySheepCount == 1000) {
      return {
        result: 'lose',
        description: 'Enemies reached 1000 sheep - you lose.'
      };
    }
    if (world.field.length == 0) {
      return {
        result: 'lose',
        description: 'You lost all your sheep - you lose.'
      };
    }

    throw 'Invalid operation';
  };

  S.makeGameTree = function (world, opt_state) {
    return {
      world: world,
      moves: S.listPossibleMoves(world, opt_state)
    };
  };

  S.listPossibleMoves = function (world, opt_state) {
    if (opt_state === undefined)
      return S.listPossibleMovesForBasicRules(world);
    else
      return S.listPossibleMovesForPlayingCard(world, opt_state);
  }

  S.listPossibleMovesForBasicRules = function (world) {
    // TODO: Add an option to continue the current game to compete high score.
    if (world.field.some(function (c) {return c.rank == 1000;}))
      return [];

    if (1000 <= world.enemySheepCount)
      return [];

    if (world.field.length == 0)
      return [];

    if (world.hand.length == 0 && world.deck.length == 0) {
      return [
        {
          description: 'Remake Deck then fill Hand',
          gameTreePromise: S.delay(function () {
            var wn = S.clone(world);
            S.remakeDeckX(wn);
            while (S.shouldDraw(wn))
              S.drawX(wn);
            wn.enemySheepCount *= 10;
            return S.makeGameTree(wn);
          })
        }
      ];
    }

    if (S.shouldDraw(world)) {
      return [
        {
          description:
            5 - world.hand.length == 1
            ? 'Draw a card'
            : 'Draw cards',
          gameTreePromise: S.delay(function () {
            var wn = S.clone(world);
            while (S.shouldDraw(wn))
              S.drawX(wn);
            return S.makeGameTree(wn);
          })
        }
      ];
    }

    return world.hand.map(function (c, i) {
      return {
        description: 'Play ' + c.name,
        gameTreePromise: S.delay(function () {
          var wn = S.clone(world);
          S.discardX(wn, i);
          return S.makeGameTree(wn, {step: c.name});
        })
      };
    });
  };

  S.listPossibleMovesForPlayingCard = function (world, state) {
    var moves = [];

    // TODO: Implement more cards.
    switch (state.step) {
      case 'Fill the Earth':
        if (world.field.length < 7) {
          moves.push({
            description: 'Gain a 1 Sheep card',
            gameTreePromise: S.delay(function () {
              var wn = S.clone(world);
              S.gainX(wn, 1);
              return S.makeGameTree(wn, state);
            })
          });
        }
        moves.push({
          description: 'Cancel',
          gameTreePromise: S.delay(function () {
            return S.makeGameTree(world);
          })
        });
        break;
      case 'Multiply':
        if (world.field.length < 7 && 0 < world.sheepStock[3].length) {
          moves.push({
            description: 'Gain a 3 Sheep card',
            gameTreePromise: S.delay(function () {
              var wn = S.clone(world);
              S.gainX(wn, 3);
              return S.makeGameTree(wn);
            })
          });
        } else {
          moves.push({
            description: 'Nothing happened',
            gameTreePromise: S.delay(function () {
              return S.makeGameTree(world);
            })
          });
        }
        break;
      case 'Planning Sheep':
        world.hand.forEach(function (c, i) {
          moves.push({
            description: 'Exile ' + c.name,
            gameTreePromise: S.delay(function () {
              var wn = S.clone(world);
              S.exileX(wn, wn.hand, i);
              return S.makeGameTree(wn);
            })
          });
        });
        if (world.hand.length == 0) {
          moves.push({
            description: 'Nothing happened',
            gameTreePromise: S.delay(function () {
              return S.makeGameTree(world);
            })
          });
        }
        break;
      case 'Sheep Dog':
        world.hand.forEach(function (c, i) {
          moves.push({
            description: 'Discard ' + c.name,
            gameTreePromise: S.delay(function () {
              var wn = S.clone(world);
              S.discardX(wn, i);
              return S.makeGameTree(wn);
            })
          });
        });
        if (world.hand.length == 0) {
          moves.push({
            description: 'Nothing happened',
            gameTreePromise: S.delay(function () {
              return S.makeGameTree(world);
            })
          });
        }
        break;
      default:
        throw 'Invalid operation: state = ' + JSON.stringify(state);
    }

    return moves;
  };

  // UI  {{{1
  // TODO: Add UI to start a new game after finishing a game.
  // TODO: Add UI to quit the current game.
  // TODO: Choose a move automatically if it doesn't express a user's choice.
  //       Examples: "Draw cards" and "Remake Deck and fill Hand".
  // TODO: Render cards as a stack of card-like shapes, not text.
  // TODO: Make #world elements clickable to choose a move.
  //       For example:
  //       - Click a card in Hand to play the card.
  //       - Click a card in Field to duplicate by playing Be Fruitful.
  // TODO: Show a card text if the cursor is hovered on the card.

  function textizeCards(cs) {
    if (cs.length == 0)
      return '-';
    else
      return cs.map(function (c) {return c.name;}).join(', ');
  }

  function nodizeMove(m) {
    var $m = $('<input>');
    $m.attr({
      type: 'button',
      value: m.description
    });
    $m.click(function () {
      drawGameTree(S.force(m.gameTreePromise));
    });
    return $m;
  }

  function drawGameTree(gameTree) {
    var w = gameTree.world;
    S.RANKS.forEach(function (rank) {
      $('#sheepStock' + rank + ' > .count').text(w.sheepStock[rank].length);
    });
    $('#enemySheepCount > .count').text(w.enemySheepCount);
    $('#field > .cards').text(
      w.field
      .map(function (c) {return c.rank;})
      .join(', ')
    );
    $('#hand > .cards').text(textizeCards(w.hand));
    $('#deck > .count').text(w.deck.length);
    $('#discardPile > .cards').text(textizeCards(w.discardPile));
    $('#exile > .cards').text(textizeCards(w.exile));

    $('#message').text(
      gameTree.moves.length == 0
      ? S.judgeGame(gameTree.world).description
      : 'Choose a move:'
    );
    $('#moves').empty().append(gameTree.moves.map(nodizeMove));
  }

  // Bootstrap  {{{1
  // TODO: Revise UI to start the first game after page load.
  //       (Show "Start a game" instead of "Draw cards)

  $(function () {
    drawGameTree(S.makeGameTree(S.makeInitalWorld()));
  });

  //}}}1
})(shephy, jQuery);

// vim: expandtab softtabstop=2 shiftwidth=2 foldmethod=marker
<|endoftext|>


<commit_before>var express = require('express'), app = express(), swig = require('swig');
var bodyParser = require('body-parser');
var https = require('https'), config = require('./config');
var exec = require('child_process').exec, child;

app.engine('html', swig.renderFile);

app.set('view engine', 'html');
app.set('views', __dirname + '/pages');

app.set('view cache', false);
swig.setDefaults({ cache: false });

app.use(express.static(__dirname + '/public'));
app.use(bodyParser());
app.use(bodyParser.json({ type: 'application/vnd.api+json' }));

app.get('/', function(req, res) {
	res.render('index', { 'foo': 'hi' });
});

app.post('/hook', function(req, res) {
	if (req.body && (req.body.after || req.body.pull_request && (req.body.after = req.body.pull_request.head.sha))) {
		console.log("checking: " + req.body.after);
		sendStatus(req.body.after, 'pending', 'PiCi: Running commands.');
		var cmd = function(n) {
			var inner = function(c) {
				child = exec(config.tasks[n].commands[c], function(error, stdout, stderr) {
					if (error == null) {
						if ((c + 1) < config.tasks[n].commands.length) {
							inner(c + 1);
						}
						else if ((n + 1) < config.tasks.length) {
							cmd(n + 1);
						}
						else {
							sendStatus(req.body.after, 'success', 'PiCi: All commands passed.');
						}
					}
					else {
						console.log(error);
						sendStatus(req.body.after, 'failure', 'PiCi: ' + error.toString());
					}
				});
			}
			inner(0);
		};
		cmd(0);
	}
	console.log(req.body);
	res.end();
});

function sendStatus(sha, state, message) {
	var request = https.request({ 'host': 'api.github.com',
		'path': '/repos/' + config.repoURL + '/statuses/' + sha + '?access_token=' + config.githubToken,
		'method': 'POST',
		'headers': { 'User-Agent': 'PiCi' }}, function(res) {
			res.on('data', function(data) {
				console.log(data.toString());
			});
		});
	request.write(JSON.stringify({ 'state': state, 'description': message || '' }));
	request.end();
}

app.listen(2345);

<commit_msg>Clean up<commit_after>var config = require('./config');
var https = require('https');
var express = require('express');
var bodyParser = require('body-parser');
var swig = require('swig');
var exec = require('child_process').exec;
var app = express();

app.engine('html', swig.renderFile);

app.set('view engine', 'html');
app.set('views', __dirname + '/pages');

app.set('view cache', false);
swig.setDefaults({ cache: false });

app.use(express.static(__dirname + '/public'));
app.use(bodyParser());
app.use(bodyParser.json({ type: 'application/vnd.api+json' }));

app.get('/', function(req, res) {
	res.render('index', { 'foo': 'hi' });
});

app.post('/hook', function(req, res) {
	if (req.body && (req.body.after || req.body.pull_request && (req.body.after = req.body.pull_request.head.sha))) {
		console.log("checking: " + req.body.after);
		sendStatus(req.body.after, 'pending', 'PiCi: Running commands.');
		var cmd = function(n) {
			var inner = function(c) {
				exec(config.tasks[n].commands[c], function(error, stdout, stderr) {
					if (error == null) {
						if (c < config.tasks[n].commands.length - 1)
							inner(c + 1);
						else if (n < config.tasks.length - 1)
							cmd(n + 1);
						else
							sendStatus(req.body.after, 'success', 'PiCi: All commands passed.');
					}
					else {
						console.log(error);
						sendStatus(req.body.after, 'failure', 'PiCi: ' + error.toString());
					}
				});
			};
			inner(0);
		};
		cmd(0);
	}
	console.log(req.body);
	res.end();
});

function sendStatus(sha, state, message) {
	var request = https.request({ 'host': 'api.github.com',
		'path': '/repos/' + config.repoURL + '/statuses/' + sha + '?access_token=' + config.githubToken,
		'method': 'POST',
		'headers': { 'User-Agent': 'PiCi' }}, function(res) {
			res.on('data', function(data) {
				console.log(data.toString());
			});
		});
	request.write(JSON.stringify({ 'state': state, 'description': message || '' }));
	request.end();
}

app.listen(2345);

<|endoftext|>