<commit_before>var util = require('util'),
    twitter = require('twitter');
var twit;
var merge = require('merge');
var fs = require('fs')
var colors = require('colors');

var DUMMY_SEARCH = false;
var DUMMY_TWEET = false;
var WAIT_SECONDS = 120;
var QUERY = "";
var TWEET_CALLBACK = null;
var DB = null;

var COUNTDOWN_OUTPUT = false;

var TWITTER_ACCOUNT_NAME = "weathermcr";

var DEFAULT_STREAM_SLEEP_SECONDS = 5;

// --- SQL ---
var sql = [];
sql['createSeenTweets'] = 'CREATE TABLE IF NOT EXISTS seen_tweets ' +
                          '(id INTEGER PRIMARY KEY, time DATETIME DEFAULT current_timestamp, text TEXT, user_id INTEGER,' +
                          ' username TEXT, action_taken TEXT, streamed BOOLEAN)';
sql['createSentTweets'] = 'CREATE TABLE IF NOT EXISTS sent_tweets ' +
                          '(id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT, related_tweet_id INTEGER)';
sql['createAPILogin'] =   'CREATE TABLE IF NOT EXISTS api_login ' +
                          '(id INTEGER PRIMARY KEY AUTOINCREMENT, access_token_key TEXT, access_token_secret TEXT)';

sql['selectNewestAPI'] =     'SELECT * FROM api_login ORDER BY id DESC LIMIT 1';
sql['selectNewestTweet'] =   'SELECT id FROM seen_tweets WHERE streamed = 0 ORDER BY id DESC LIMIT 1';
sql['selectExistingTweet'] = "SELECT 1 as 'exist' FROM seen_tweets WHERE id = $tweet_id LIMIT 1";

sql['updateActionTaken'] = 'UPDATE seen_tweets SET action_taken = $action WHERE id = $id';

sql['insertSeenTweet'] = 'INSERT INTO seen_tweets (id, text, user_id, username, streamed) ' +
                         'VALUES ($id, $text, $user_id, $username, $streamed)';
sql['logSentTweet'] =    'INSERT INTO sent_tweets (text, related_tweet_id) VALUES ($text, $related_id)';
sql['insertAPILogin'] =  'INSERT INTO api_login (access_token_key, access_token_secret) VALUES ($key, $secret)';

// --- public
exports.config = function(config, wait_seconds, database) {
    twit = new twitter(config);
    WAIT_SECONDS = wait_seconds;
    DB = database;
}

exports.dummy = function(search, tweets) {
    DUMMY_SEARCH = search;
    DUMMY_TWEET = tweets;
    if(DUMMY_SEARCH)
      console.log("Using dummy twitter search");
    if(DUMMY_TWEET)
      console.log("Using dummy twitter tweet");
}

exports.start = function(query, tweet_callback, callback) {
    QUERY = query;
    TWEET_CALLBACK = tweet_callback;

    // Ensure the database is ready for use
    DB.serialize(function() {
      DB.run(sql['createSeenTweets']);
      DB.run(sql['createSentTweets']);
      DB.run(sql['createAPILogin']);
    });

    DB.get(sql['selectNewestAPI'], function(err, result) {

      if(result) {
          console.info("Using stored authentication");
          params = { 'since_id': (result.id - 1500) } // for some reason this needs to be done...

          twit.options.access_token_key = result.access_token_key;
          twit.options.access_token_secret = result.access_token_secret;

          start(callback);
      } else {
          console.info("Authentication is needed before bot can start.");
          requireAuthentication(function() {
              //reusable API access
              var insert = DB.prepare(sql['insertAPILogin']);
              insert.run({ $key: twit.options.access_token_key, $secret: twit.options.access_token_secret });

              start(callback);
          });

      }

    });
}

exports.updateActionTaken = function(tweet, actionTaken) {
    var update = DB.prepare(sql['updateActionTaken']);
    update.bind({$id: tweet.id, $action: actionTaken });
    update.run();
}

exports.sendReply = function(reply_to, message, callback, disable_dummy_tweet) {
    if(typeof disable_dummy_tweet === 'undefined')
        disable_dummy_tweet = false;

    // Must include @ for reply, else twitter rejects it
    if(reply_to.user.screen_name)
      message = "@" + reply_to.user.screen_name + " " + message;

    // Twitter rejects us if we provide an incorrect reply_to.id
    params = {}
    if(reply_to.id)
      params.in_reply_to_status_id = reply_to.id;

    if(message.length > 140)
        console.error("Message over 140 letters: " + message);

    console.log("Attempting to send", message);

    if(DUMMY_TWEET && !disable_dummy_tweet) {
      var dummy = { text: message };
      logSentTweet(dummy, params, false);
      callback(dummy);
    } else {
      twit.updateStatus(message, params, function(reply) {
          if(reply.id) {
            logSentTweet(reply, params);
            callback(reply);
          } else {
            console.error("Error sending tweet", reply);
          }
      });
    }

}

// --- private
function start(callback) {
    updateTweets();
    streamWeatherTweets();
    streamUserReplies();
    callback();
}

function requireAuthentication(callback) {
    var express = require('express');
    var app = express();
    var server;

    app.get('/', twit.gatekeeper('/login'), function(req, res){
      res.send('Authentification successfull');
      console.log("Auth complete, closing authentication server.");
      server.close();
      callback();
    });
    app.get('/twauth', twit.login());

    console.log('Listening on 1200 for auth requests to Twitter');
    server = app.listen(1200);
}

function logSentTweet(tweet, params, real) {
    if(typeof params === 'undefined')
      params = {};

    if(typeof real === 'undefined')
      real = true;

    var insert = DB.prepare(sql['logSentTweet']);
    insert.run({ $text: tweet.text, $related_id: params.in_reply_to_status_id });
    console.log("Tweet sent successfully" + (real ? "" : " (Mock)"));
}

function selectTweets(params, callback) {
    defaultParams = {
        'q': QUERY,
        'lang': 'en',
        'result_type': 'recent', //mixed, recent, popular
        'f': 'realtime',
        'include_entities': 'false',
        'count': 100
    };

    params = merge(defaultParams, params);

    if(DUMMY_SEARCH) callback();
    else twit.get('/search/tweets.json', params, callback);
}

function updateTweets() {
    console.log("Querying twitter for updates using polling");
    DB.get(sql['selectNewestTweet'], function(err, result) {
        if(result) params = { 'since_id': (result.id + 1)  }
        else params = {}

        selectTweets(params, function(data) {
            if(!data || !data.statuses) {
                console.warn("No data received from Twitter from search poll");
            }
            else
            {
                var count = data.statuses.length;
                if(count < 1) {
                    console.warn("No tweets received from Twitter from search poll");
                }

                for(i=0;i<count;i++) {
                    var tweet = data.statuses[i];
                    seenTweet(tweet);
                }
            }

            updateComplete();
        });
    });
}

// Connect to a twitter stream
function streamTweets(type, params, dataCallback, reconnectSleepSeconds) {
    if(typeof reconnectSleepSeconds === 'undefined')
        reconnectSleepSeconds = DEFAULT_STREAM_SLEEP_SECONDS;

    twit.stream(type, params, function(stream) {
        console.info("Connected to twitter "+type+" stream");

        var resetStream = setTimeout(function() {
            console.info("Restarting "+type+" stream as it has been open for 30 minutes");
            stream.destroy();
        }, 30 * 60 * 1000);

        stream.on('data', function(data) {

            // reset done here as 'connect' fires even on rejection
            reconnectSleepSeconds = DEFAULT_STREAM_SLEEP_SECONDS;

            dataCallback(data);

        });

        stream.on('error', function(error) {
            if(error.errorSource) console.error("Twitter "+type+" stream error:", error);
            else console.error("Other Twitter "+type+" stream error");
        });

        stream.on('end', function(end) {
            console.info("Stream "+type+" ended, will attempt to reconnect in "+reconnectSleepSeconds+" seconds");
            clearTimeout(resetStream);
            setTimeout(function() {
              streamTweets(type, params, dataCallback, reconnectSleepSeconds * 2);
            }, reconnectSleepSeconds * 1000);
        });

    });

}

// Twitter side
var weather_keywords = ['weather', 'forecast', 'sunny', ' rain', 'sunshine', 'thunder', 'lightning',
                        'umbrella', ' snow', 'hail', ' warm', 'cold', 'brolly', 'boiling',
                        '#snow', '#rain', '#hail', '#sun', '#thunder'];

// Local
var ignored_users = ['galgateweather', 'mennewsdesk', 'metoffice', 'chadWeather', 'myweather_man',
                     'uk_storms', 'wx_manchester', 'widdop_weather', 'weather', TWITTER_ACCOUNT_NAME,
                     'LordChapman'];

var ignored_keywords = ['rt @', '[Manchester Weather] Your Weekend Forecast', 'weatherspoons',
                        'manchester, nh', 'train', '@MetOffice', '@coldplay', 'Cold cave'];

function isWeatherTweet(tweet) {
    return arrayInString(tweet.text, weather_keywords, true)
           && !arrayInString(tweet.user.screen_name, ignored_users)
           && !arrayInString(tweet.text, ignored_keywords)
}

function highlightMatches(string, array) {
    text = string;
    for(i=0;i<array.length;i++) {
      text = text.replace(new RegExp('(' + array[i] + ')','ig'), "$1".red);
    }
}

// Get tweets about the weather
function streamWeatherTweets() {

    // Bounding boxes do not act as filters for other filter parameters.
    // For example track=twitter&locations=-122.75,36.8,-121.75,37.8 would match
    // any tweets containing the term Twitter (even non-geo tweets) OR coming from the San Francisco area.
    track = []

    for(i=0;i<weather_keywords.length;i++) {
      track.push(weather_keywords[i] + ' manchester');
      track.push(weather_keywords[i] + ' #mcr');
    }

    streamTweets('filter',
        {
            track: track,
            locations: '-2.275,53.45,-2.2,53.5' // bottom right, top left - lon/lat
        },
        function(data) {

            // @todo should probably validate data, twitter might return a none tweet
            // the steam may contain "non weather" tweets we must filter first
            if(isWeatherTweet(data)) {

                textWithMatches = highlightMatches(data.text, weather_keywords);
                console.info("Matched streamed weather tweet @", data.user.screen_name, " ", textWithMatches);
                seenTweet(data, true);

            } else {
                fs.appendFile('ignoredTweets.txt', data.user.screen_name
                                                    + ":\t"
                                                    + data.text.replace(/(\r\n|\n|\r)/gm," \\ ")
                                                    + "\n");
            }

        }
    );
}

function streamUserReplies() {
    streamTweets('user',
        {
            replies: 'all', // By default @replies are only sent if the current user follows the sender and receiver
            with: 'users' // When set to "users", only messages targeted directly at a user will be delivered
        },
        function(data) {
            // Upon establishing a User Stream connection Twitter will send
            // a preamble before starting regular message delivery
            if(data.friends) {
                return;
            }

            else if(data.text && data.text.toLowerCase().indexOf(TWITTER_ACCOUNT_NAME) != -1) {
                console.log("Recieved mention in user stream @", data.user.screen_name, data.text);

                // Avoid loops, if we sent the message and it was a reply to US
                if(data.in_reply_to_screen_name
                    && data.in_reply_to_screen_name.toLowerCase() == TWITTER_ACCOUNT_NAME
                    && data.user.screen_name.toLowerCase() == TWITTER_ACCOUNT_NAME) {
                    console.warn('Ignored self reply message');
                    return;
                }

                seenTweet(data, true, true);
            }
        }
    );
}

function arrayInString(string, array) {
    if(typeof string !== 'string')
        throw new Error('The term to search must be a string');

    for(i=0;i<array.length;i++) {
        if(string.toLowerCase().indexOf(array[i].toLowerCase()) != -1) return true;
    }

    return false;

}

function seenTweet(tweet, streamed, mention) {
    if(typeof streamed === 'undefined')
        streamed = false;

    if(typeof mention === 'undefined')
        mention = tweet.text.toLowerCase().indexOf(TWITTER_ACCOUNT_NAME) != -1;

    var select = DB.prepare(sql['selectExistingTweet']);
    select.get({$tweet_id: tweet.id}, function(err, row) {
        if(!row) { // If the result set is empty, the second parameter is undefined
            var insert = DB.prepare(sql['insertSeenTweet']);
            insert.run({
              $id: tweet.id,
              $text: tweet.text,
              $user_id: tweet.user.id,
              $username: tweet.user.screen_name,
              $streamed: streamed ? 1 : 0  // 0/1 as SQLlite has no boolean support
            });
            TWEET_CALLBACK(tweet, mention);
            console.info("Logged: ", tweet.text, tweet.created_at);
        } else {
            console.info("Ignored duplicate: ", tweet.text, tweet.created_at);
            // @todo if streamed == false, I might need to mark this as non-streamed
            // to prevent the non-stream falling behind if the stream is capturing everything
        }
    });
}

function updateComplete() {
    console.info("\n"+'Update complete, waiting '+WAIT_SECONDS+' seconds before the next update');
    countdown(WAIT_SECONDS, updateTweets, "seconds until next search...");
}

function countdown(seconds, callback, message) {
    var countdownInterval;
    var countdownSecondsLeft;
    var countdownCallback;

    countdownInterval = setInterval(function(){
        if(COUNTDOWN_OUTPUT) {
            process.stdout.clearLine();  // clear current text
            process.stdout.cursorTo(0);  // move cursor to beginning of line
            process.stdout.write(countdownSecondsLeft-- + " " + message);
        }

        if(countdownSecondsLeft <= 0) {
            clearInterval(countdownInterval);
            process.stdout.write("\n");
            if(countdownCallback) {
                temp = countdownCallback;
                countdownCallback = null;
                temp();
            }
        }
    }, 1000);

    countdownSecondsLeft = seconds;
    if(typeof callback !== 'undefined') {
        countdownCallback = callback;
    }
}<commit_msg>Array in string can now ignore matches in @mentions<commit_after>var util = require('util'),
    twitter = require('twitter');
var twit;
var merge = require('merge');
var fs = require('fs')
var colors = require('colors');

var DUMMY_SEARCH = false;
var DUMMY_TWEET = false;
var WAIT_SECONDS = 120;
var QUERY = "";
var TWEET_CALLBACK = null;
var DB = null;

var COUNTDOWN_OUTPUT = false;

var TWITTER_ACCOUNT_NAME = "weathermcr";

var DEFAULT_STREAM_SLEEP_SECONDS = 5;

// --- SQL ---
var sql = [];
sql['createSeenTweets'] = 'CREATE TABLE IF NOT EXISTS seen_tweets ' +
                          '(id INTEGER PRIMARY KEY, time DATETIME DEFAULT current_timestamp, text TEXT, user_id INTEGER,' +
                          ' username TEXT, action_taken TEXT, streamed BOOLEAN)';
sql['createSentTweets'] = 'CREATE TABLE IF NOT EXISTS sent_tweets ' +
                          '(id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT, related_tweet_id INTEGER)';
sql['createAPILogin'] =   'CREATE TABLE IF NOT EXISTS api_login ' +
                          '(id INTEGER PRIMARY KEY AUTOINCREMENT, access_token_key TEXT, access_token_secret TEXT)';

sql['selectNewestAPI'] =     'SELECT * FROM api_login ORDER BY id DESC LIMIT 1';
sql['selectNewestTweet'] =   'SELECT id FROM seen_tweets WHERE streamed = 0 ORDER BY id DESC LIMIT 1';
sql['selectExistingTweet'] = "SELECT 1 as 'exist' FROM seen_tweets WHERE id = $tweet_id LIMIT 1";

sql['updateActionTaken'] = 'UPDATE seen_tweets SET action_taken = $action WHERE id = $id';

sql['insertSeenTweet'] = 'INSERT INTO seen_tweets (id, text, user_id, username, streamed) ' +
                         'VALUES ($id, $text, $user_id, $username, $streamed)';
sql['logSentTweet'] =    'INSERT INTO sent_tweets (text, related_tweet_id) VALUES ($text, $related_id)';
sql['insertAPILogin'] =  'INSERT INTO api_login (access_token_key, access_token_secret) VALUES ($key, $secret)';

// --- public
exports.config = function(config, wait_seconds, database) {
    twit = new twitter(config);
    WAIT_SECONDS = wait_seconds;
    DB = database;
}

exports.dummy = function(search, tweets) {
    DUMMY_SEARCH = search;
    DUMMY_TWEET = tweets;
    if(DUMMY_SEARCH)
      console.log("Using dummy twitter search");
    if(DUMMY_TWEET)
      console.log("Using dummy twitter tweet");
}

exports.start = function(query, tweet_callback, callback) {
    QUERY = query;
    TWEET_CALLBACK = tweet_callback;

    // Ensure the database is ready for use
    DB.serialize(function() {
      DB.run(sql['createSeenTweets']);
      DB.run(sql['createSentTweets']);
      DB.run(sql['createAPILogin']);
    });

    DB.get(sql['selectNewestAPI'], function(err, result) {

      if(result) {
          console.info("Using stored authentication");
          params = { 'since_id': (result.id - 1500) } // for some reason this needs to be done...

          twit.options.access_token_key = result.access_token_key;
          twit.options.access_token_secret = result.access_token_secret;

          start(callback);
      } else {
          console.info("Authentication is needed before bot can start.");
          requireAuthentication(function() {
              //reusable API access
              var insert = DB.prepare(sql['insertAPILogin']);
              insert.run({ $key: twit.options.access_token_key, $secret: twit.options.access_token_secret });

              start(callback);
          });

      }

    });
}

exports.updateActionTaken = function(tweet, actionTaken) {
    var update = DB.prepare(sql['updateActionTaken']);
    update.bind({$id: tweet.id, $action: actionTaken });
    update.run();
}

exports.sendReply = function(reply_to, message, callback, disable_dummy_tweet) {
    if(typeof disable_dummy_tweet === 'undefined')
        disable_dummy_tweet = false;

    // Must include @ for reply, else twitter rejects it
    if(reply_to.user.screen_name)
      message = "@" + reply_to.user.screen_name + " " + message;

    // Twitter rejects us if we provide an incorrect reply_to.id
    params = {}
    if(reply_to.id)
      params.in_reply_to_status_id = reply_to.id;

    if(message.length > 140)
        console.error("Message over 140 letters: " + message);

    console.log("Attempting to send", message);

    if(DUMMY_TWEET && !disable_dummy_tweet) {
      var dummy = { text: message };
      logSentTweet(dummy, params, false);
      callback(dummy);
    } else {
      twit.updateStatus(message, params, function(reply) {
          if(reply.id) {
            logSentTweet(reply, params);
            callback(reply);
          } else {
            console.error("Error sending tweet", reply);
          }
      });
    }

}

// --- private
function start(callback) {
    updateTweets();
    streamWeatherTweets();
    streamUserReplies();
    callback();
}

function requireAuthentication(callback) {
    var express = require('express');
    var app = express();
    var server;

    app.get('/', twit.gatekeeper('/login'), function(req, res){
      res.send('Authentification successfull');
      console.log("Auth complete, closing authentication server.");
      server.close();
      callback();
    });
    app.get('/twauth', twit.login());

    console.log('Listening on 1200 for auth requests to Twitter');
    server = app.listen(1200);
}

function logSentTweet(tweet, params, real) {
    if(typeof params === 'undefined')
      params = {};

    if(typeof real === 'undefined')
      real = true;

    var insert = DB.prepare(sql['logSentTweet']);
    insert.run({ $text: tweet.text, $related_id: params.in_reply_to_status_id });
    console.log("Tweet sent successfully" + (real ? "" : " (Mock)"));
}

function selectTweets(params, callback) {
    defaultParams = {
        'q': QUERY,
        'lang': 'en',
        'result_type': 'recent', //mixed, recent, popular
        'f': 'realtime',
        'include_entities': 'false',
        'count': 100
    };

    params = merge(defaultParams, params);

    if(DUMMY_SEARCH) callback();
    else twit.get('/search/tweets.json', params, callback);
}

function updateTweets() {
    console.log("Querying twitter for updates using polling");
    DB.get(sql['selectNewestTweet'], function(err, result) {
        if(result) params = { 'since_id': (result.id + 1)  }
        else params = {}

        selectTweets(params, function(data) {
            if(!data || !data.statuses) {
                console.warn("No data received from Twitter from search poll");
            }
            else
            {
                var count = data.statuses.length;
                if(count < 1) {
                    console.warn("No tweets received from Twitter from search poll");
                }

                for(i=0;i<count;i++) {
                    var tweet = data.statuses[i];
                    seenTweet(tweet);
                }
            }

            updateComplete();
        });
    });
}

// Connect to a twitter stream
function streamTweets(type, params, dataCallback, reconnectSleepSeconds) {
    if(typeof reconnectSleepSeconds === 'undefined')
        reconnectSleepSeconds = DEFAULT_STREAM_SLEEP_SECONDS;

    twit.stream(type, params, function(stream) {
        console.info("Connected to twitter "+type+" stream");

        var resetStream = setTimeout(function() {
            console.info("Restarting "+type+" stream as it has been open for 30 minutes");
            stream.destroy();
        }, 30 * 60 * 1000);

        stream.on('data', function(data) {

            // reset done here as 'connect' fires even on rejection
            reconnectSleepSeconds = DEFAULT_STREAM_SLEEP_SECONDS;

            dataCallback(data);

        });

        stream.on('error', function(error) {
            if(error.errorSource) console.error("Twitter "+type+" stream error:", error);
            else console.error("Other Twitter "+type+" stream error");
        });

        stream.on('end', function(end) {
            console.info("Stream "+type+" ended, will attempt to reconnect in "+reconnectSleepSeconds+" seconds");
            clearTimeout(resetStream);
            setTimeout(function() {
              streamTweets(type, params, dataCallback, reconnectSleepSeconds * 2);
            }, reconnectSleepSeconds * 1000);
        });

    });

}

// Twitter side
var weather_keywords = ['weather', 'forecast', 'sunny', ' rain', 'sunshine', 'thunder', 'lightning',
                        'umbrella', ' snow', 'hail', ' warm', 'cold', 'brolly', 'boiling',
                        '#snow', '#rain', '#hail', '#sun', '#thunder'];

// Local
var ignored_users = ['galgateweather', 'mennewsdesk', 'metoffice', 'chadWeather', 'myweather_man',
                     'uk_storms', 'wx_manchester', 'widdop_weather', 'weather', TWITTER_ACCOUNT_NAME,
                     'LordChapman'];

var ignored_keywords = ['rt @', '[Manchester Weather] Your Weekend Forecast', 'weatherspoons',
                        'manchester, nh', 'train', '@MetOffice', '@coldplay', 'Cold cave'];

function isWeatherTweet(tweet) {
    return arrayInString(tweet.text, weather_keywords, true)
           && !arrayInString(tweet.user.screen_name, ignored_users)
           && !arrayInString(tweet.text, ignored_keywords)
}

function highlightMatches(string, array) {
    text = string;
    for(i=0;i<array.length;i++) {
      text = text.replace(new RegExp('(' + array[i] + ')','ig'), "$1".red);
    }
}

// Get tweets about the weather
function streamWeatherTweets() {

    // Bounding boxes do not act as filters for other filter parameters.
    // For example track=twitter&locations=-122.75,36.8,-121.75,37.8 would match
    // any tweets containing the term Twitter (even non-geo tweets) OR coming from the San Francisco area.
    track = []

    for(i=0;i<weather_keywords.length;i++) {
      track.push(weather_keywords[i] + ' manchester');
      track.push(weather_keywords[i] + ' #mcr');
    }

    streamTweets('filter',
        {
            track: track,
            locations: '-2.275,53.45,-2.2,53.5' // bottom right, top left - lon/lat
        },
        function(data) {

            // @todo should probably validate data, twitter might return a none tweet
            // the steam may contain "non weather" tweets we must filter first
            if(isWeatherTweet(data)) {

                textWithMatches = highlightMatches(data.text, weather_keywords);
                console.info("Matched streamed weather tweet @", data.user.screen_name, " ", textWithMatches);
                seenTweet(data, true);

            } else {
                fs.appendFile('ignoredTweets.txt', data.user.screen_name
                                                    + ":\t"
                                                    + data.text.replace(/(\r\n|\n|\r)/gm," \\ ")
                                                    + "\n");
            }

        }
    );
}

function streamUserReplies() {
    streamTweets('user',
        {
            replies: 'all', // By default @replies are only sent if the current user follows the sender and receiver
            with: 'users' // When set to "users", only messages targeted directly at a user will be delivered
        },
        function(data) {
            // Upon establishing a User Stream connection Twitter will send
            // a preamble before starting regular message delivery
            if(data.friends) {
                return;
            }

            else if(data.text && data.text.toLowerCase().indexOf(TWITTER_ACCOUNT_NAME) != -1) {
                console.log("Recieved mention in user stream @", data.user.screen_name, data.text);

                // Avoid loops, if we sent the message and it was a reply to US
                if(data.in_reply_to_screen_name
                    && data.in_reply_to_screen_name.toLowerCase() == TWITTER_ACCOUNT_NAME
                    && data.user.screen_name.toLowerCase() == TWITTER_ACCOUNT_NAME) {
                    console.warn('Ignored self reply message');
                    return;
                }

                seenTweet(data, true, true);
            }
        }
    );
}

function arrayInString(string, array, ignore_mentions) {
    if(typeof string !== 'string')
        throw new Error('The term to search must be a string');

    if(typeof ignore_mentions === 'undefined')
        ignore_mentions = false;

    for(i=0;i<array.length;i++) {
        if(string.toLowerCase().indexOf(array[i].toLowerCase()) != -1) {
            if(!ignore_mentions) return true;
            else {
              // note double escape and trim due to trailing spaces
              var re = new RegExp('@\\w*'+array[i].trim(),'i');
              if(!re.test(string))
                  return true;
            }
        }
    }

    return false;

}

function seenTweet(tweet, streamed, mention) {
    if(typeof streamed === 'undefined')
        streamed = false;

    if(typeof mention === 'undefined')
        mention = tweet.text.toLowerCase().indexOf(TWITTER_ACCOUNT_NAME) != -1;

    var select = DB.prepare(sql['selectExistingTweet']);
    select.get({$tweet_id: tweet.id}, function(err, row) {
        if(!row) { // If the result set is empty, the second parameter is undefined
            var insert = DB.prepare(sql['insertSeenTweet']);
            insert.run({
              $id: tweet.id,
              $text: tweet.text,
              $user_id: tweet.user.id,
              $username: tweet.user.screen_name,
              $streamed: streamed ? 1 : 0  // 0/1 as SQLlite has no boolean support
            });
            TWEET_CALLBACK(tweet, mention);
            console.info("Logged: ", tweet.text, tweet.created_at);
        } else {
            console.info("Ignored duplicate: ", tweet.text, tweet.created_at);
            // @todo if streamed == false, I might need to mark this as non-streamed
            // to prevent the non-stream falling behind if the stream is capturing everything
        }
    });
}

function updateComplete() {
    console.info("\n"+'Update complete, waiting '+WAIT_SECONDS+' seconds before the next update');
    countdown(WAIT_SECONDS, updateTweets, "seconds until next search...");
}

function countdown(seconds, callback, message) {
    var countdownInterval;
    var countdownSecondsLeft;
    var countdownCallback;

    countdownInterval = setInterval(function(){
        if(COUNTDOWN_OUTPUT) {
            process.stdout.clearLine();  // clear current text
            process.stdout.cursorTo(0);  // move cursor to beginning of line
            process.stdout.write(countdownSecondsLeft-- + " " + message);
        }

        if(countdownSecondsLeft <= 0) {
            clearInterval(countdownInterval);
            process.stdout.write("\n");
            if(countdownCallback) {
                temp = countdownCallback;
                countdownCallback = null;
                temp();
            }
        }
    }, 1000);

    countdownSecondsLeft = seconds;
    if(typeof callback !== 'undefined') {
        countdownCallback = callback;
    }
}<|endoftext|>


<commit_before>/**
 *  Copyright (c) 2015, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

// Model types
class Game extends Object {}
class HidingSpot extends Object {}

// Mock data
var game = new Game();
game.id = '1';

var hidingSpots = [];
(function () {
  var hidingSpot;
  var indexOfSpotWithTreasure = Math.floor(Math.random() * 9);
  for (var i = 0; i < 9; i++) {
    hidingSpot = new HidingSpot();
    hidingSpot.id = `${i}`;
    hidingSpot.hasTreasue = (i === indexOfSpotWithTreasure);
    hidingSpot.hasBeenChecked = false;
    hidingSpots.push(hidingSpot);
  }
})();

var turnsRemaining = 3;

export function checkHidingSpotForTreasure(id) {
  if (hidingSpots.some(hs => hs.hasTreasue && hs.hasBeenChecked)) {
    return;
  }
  turnsRemaining--;
  var hidingSpot = getHidingSpot(id);
  hidingSpot.hasBeenChecked = true;
}
export function getHidingSpot(id) {
  return hidingSpots.find(hs => hs.id === id);
}
export function getGame() { return game; }
export function getHidingSpots() { return hidingSpots; }
export function getTurnsRemaining() { return turnsRemaining; }
<commit_msg>fixes typo<commit_after>/**
 *  Copyright (c) 2015, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

// Model types
class Game extends Object {}
class HidingSpot extends Object {}

// Mock data
var game = new Game();
game.id = '1';

var hidingSpots = [];
(function () {
  var hidingSpot;
  var indexOfSpotWithTreasure = Math.floor(Math.random() * 9);
  for (var i = 0; i < 9; i++) {
    hidingSpot = new HidingSpot();
    hidingSpot.id = `${i}`;
    hidingSpot.hasTreasure = (i === indexOfSpotWithTreasure);
    hidingSpot.hasBeenChecked = false;
    hidingSpots.push(hidingSpot);
  }
})();

var turnsRemaining = 3;

export function checkHidingSpotForTreasure(id) {
  if (hidingSpots.some(hs => hs.hasTreasure && hs.hasBeenChecked)) {
    return;
  }
  turnsRemaining--;
  var hidingSpot = getHidingSpot(id);
  hidingSpot.hasBeenChecked = true;
}
export function getHidingSpot(id) {
  return hidingSpots.find(hs => hs.id === id);
}
export function getGame() { return game; }
export function getHidingSpots() { return hidingSpots; }
export function getTurnsRemaining() { return turnsRemaining; }
<|endoftext|>


<commit_before>import { Router } from 'chialab-router/src/router.js';
import { internal } from './helpers/internal.js';
import { BaseObject } from './base.js';
import { Controller } from './controller.js';
import { View } from './view.js';
import { PagesHelper } from './helpers/pages.js';
import { ViewHelper } from './helpers/view.js';
import { I18NHelper } from './helpers/i18n.js';
import { CssHelper } from './helpers/css.js';
import { debounce } from './helpers/debounce.js';
import * as EXCEPTIONS from './exceptions.js';
import '@dnajs/idom/observer.js';

export class App extends BaseObject {
    static get View() {
        return View;
    }

    static get ViewHelper() {
        return ViewHelper;
    }

    static get PagesHelper() {
        return PagesHelper;
    }

    static get I18NHelper() {
        return I18NHelper;
    }

    static addStyle(css) {
        CssHelper.add(css);
    }

    get defaultRouteOptions() {
        return {
            dispatch: true,
        };
    }

    get routeOptions() {
        return this.defaultRouteOptions;
    }

    get routeRules() {
        return {
            '/:controller/:action/*': 'route',
            '/:controller/:action': 'route',
            '/:controller': 'route',
            '*': 'notFound',
        };
    }

    get routeMap() {
        return {};
    }

    get i18nOptions() {
        return {
            languages: [],
        };
    }

    initialize(element) {
        super.initialize(element);
        this.router = new Router(this.routeOptions);
        this.registerRoutes();
        this.element = element;
        this.i18n = new this.constructor.I18NHelper(this.i18nOptions);
        internal(this).pagesDispatcher = new this.constructor.PagesHelper(this.element);
        this.element.addEventListener('click', (ev) => {
            let elem = ev.target;
            if (elem.tagName !== 'A') {
                elem = elem.closest('A');
            }
            if (elem && elem.tagName === 'A') {
                return this.handleLink(ev, elem, this);
            }
            return true;
        });
        this.ready()
            .then(() => {
                this.debounce(() => {
                    this.router.start();
                });
            })
            .catch((ex) => {
                // eslint-disable-next-line
                console.error(ex);
                // eslint-disable-next-line
                alert('Error occurred on application initialize.');
            });
    }

    registerRoutes(routeRules) {
        routeRules = routeRules || this.routeRules;
        for (let k in routeRules) {
            if (routeRules.hasOwnProperty(k)) {
                let ruleMatch = routeRules[k];
                if (typeof ruleMatch === 'string') {
                    if (typeof this[ruleMatch] === 'function') {
                        this.router.on(k, (...args) =>
                            this.beforeRoute(...args).then((args2) =>
                                this[ruleMatch].call(this, ...args2)
                                    .then(() => this.afterRoute())
                            )
                        );
                    }
                } else if (ruleMatch.prototype instanceof Controller) {
                    this.router.on(k, (...args) =>
                        this.beforeRoute(...args).then(() =>
                            this.dispatchController(ruleMatch)
                                .then((ctr) =>
                                    ctr.exec(...args).then((ctrRes) =>
                                        this.dispatchView(ctr, ctrRes)
                                    ).catch((err) =>
                                        this.throwException(err)
                                    )
                                )
                                .then(() => this.afterRoute())
                        )
                    );
                }
            }
        }
    }

    beforeRoute(...args) {
        return Promise.resolve(args || []);
    }

    afterRoute() {
        return Promise.resolve();
    }

    backState() {
        return this.router.back();
    }

    forwardState() {
        return this.router.forward();
    }

    handleLink() {
        return true;
    }

    route(controller, action, paths = '') {
        paths = paths.split('/');
        if (controller) {
            let RequestedController = this.routeMap[controller];
            if (typeof RequestedController !== 'undefined') {
                return this.dispatchController(RequestedController).then((ctr) => {
                    let promise;
                    if (action && typeof ctr[action] === 'function') {
                        promise = ctr[action].call(ctr, ...paths);
                    } else {
                        promise = ctr.exec(action, ...paths);
                    }
                    return promise
                        .then((ctrRes) =>
                            this.dispatchView(ctr, ctrRes)
                        )
                        .catch((err) =>
                            this.throwException(err)
                        );
                });
            }
        }
        this.notFoundException();
        return Promise.reject();
    }

    notFoundException() {
        this.throwException(new EXCEPTIONS.ContentNotFoundException());
    }

    dispatchController(RequestedController, ...args) {
        let ctr = new RequestedController(this, ...args);
        let destroyCtr = Promise.resolve();
        let previousCtr = internal(this).currentController;
        if (previousCtr) {
            previousCtr.off('update');
            destroyCtr = previousCtr.destroy();
        }
        return destroyCtr.then(() => {
            internal(this).currentController = ctr;
            return ctr.ready
                .then(() => Promise.resolve(ctr))
                .catch(() => Promise.reject(ctr));
        });
    }

    dispatchView(controller, controllerResponse) {
        const AppView = this.constructor.View;
        return new Promise((resolve) => {
            let view = new AppView(controller, controllerResponse);
            internal(this).currentView = view;
            internal(this).pagesDispatcher.add(view).then((page) => {
                let oldPage = this.currentPage;
                let destroyPromise = oldPage ? oldPage.destroy() : Promise.resolve();
                this.currentPage = page;
                this.debounce(() => {
                    destroyPromise.then(() => {
                        let shown = this.currentPage.show(!oldPage);
                        if (controller) {
                            if (controller.dispatchResolved) {
                                controller.dispatchResolved();
                            }
                            controller.on('update', (newCtrRes) =>
                                this.updateView(newCtrRes)
                            );
                        }
                        shown.then(() => resolve());
                    });
                });
            });
        });
    }

    updateView(controllerResponse) {
        if (internal(this).currentView) {
            return internal(this).currentView
                .update(controllerResponse);
        }
        return Promise.reject();
    }

    debounce(callback) {
        return debounce(callback);
    }

    navigate(...args) {
        return this.router.navigate(...args);
    }

    refresh() {
        return this.router.refresh();
    }

    throwException(err) {
        if (err && err instanceof EXCEPTIONS.AppException) {
            this.debounce(() => {
                if (!this.handleException(err)) {
                    throw err;
                }
            });
        }
    }

    handleException(err) {
        if (err instanceof EXCEPTIONS.ContentNotFoundException) {
            this.notFound();
            return true;
        } else if (err instanceof EXCEPTIONS.ContentErrorException) {
            this.error();
            return true;
        }
        return false;
    }

    notFound() {
        // NOT FOUND
        return Promise.resolve();
    }

    error() {
        // ERROR
        return Promise.resolve();
    }
}
<commit_msg>fix: remove all view helpers occurrences<commit_after>import { Router } from 'chialab-router/src/router.js';
import { internal } from './helpers/internal.js';
import { BaseObject } from './base.js';
import { Controller } from './controller.js';
import { View } from './view.js';
import { PagesHelper } from './helpers/pages.js';
import { I18NHelper } from './helpers/i18n.js';
import { CssHelper } from './helpers/css.js';
import { debounce } from './helpers/debounce.js';
import * as EXCEPTIONS from './exceptions.js';
import '@dnajs/idom/observer.js';

export class App extends BaseObject {
    static get View() {
        return View;
    }

    static get PagesHelper() {
        return PagesHelper;
    }

    static get I18NHelper() {
        return I18NHelper;
    }

    static addStyle(css) {
        CssHelper.add(css);
    }

    get defaultRouteOptions() {
        return {
            dispatch: true,
        };
    }

    get routeOptions() {
        return this.defaultRouteOptions;
    }

    get routeRules() {
        return {
            '/:controller/:action/*': 'route',
            '/:controller/:action': 'route',
            '/:controller': 'route',
            '*': 'notFound',
        };
    }

    get routeMap() {
        return {};
    }

    get i18nOptions() {
        return {
            languages: [],
        };
    }

    initialize(element) {
        super.initialize(element);
        this.router = new Router(this.routeOptions);
        this.registerRoutes();
        this.element = element;
        this.i18n = new this.constructor.I18NHelper(this.i18nOptions);
        internal(this).pagesDispatcher = new this.constructor.PagesHelper(this.element);
        this.element.addEventListener('click', (ev) => {
            let elem = ev.target;
            if (elem.tagName !== 'A') {
                elem = elem.closest('A');
            }
            if (elem && elem.tagName === 'A') {
                return this.handleLink(ev, elem, this);
            }
            return true;
        });
        this.ready()
            .then(() => {
                this.debounce(() => {
                    this.router.start();
                });
            })
            .catch((ex) => {
                // eslint-disable-next-line
                console.error(ex);
                // eslint-disable-next-line
                alert('Error occurred on application initialize.');
            });
    }

    registerRoutes(routeRules) {
        routeRules = routeRules || this.routeRules;
        for (let k in routeRules) {
            if (routeRules.hasOwnProperty(k)) {
                let ruleMatch = routeRules[k];
                if (typeof ruleMatch === 'string') {
                    if (typeof this[ruleMatch] === 'function') {
                        this.router.on(k, (...args) =>
                            this.beforeRoute(...args).then((args2) =>
                                this[ruleMatch].call(this, ...args2)
                                    .then(() => this.afterRoute())
                            )
                        );
                    }
                } else if (ruleMatch.prototype instanceof Controller) {
                    this.router.on(k, (...args) =>
                        this.beforeRoute(...args).then(() =>
                            this.dispatchController(ruleMatch)
                                .then((ctr) =>
                                    ctr.exec(...args).then((ctrRes) =>
                                        this.dispatchView(ctr, ctrRes)
                                    ).catch((err) =>
                                        this.throwException(err)
                                    )
                                )
                                .then(() => this.afterRoute())
                        )
                    );
                }
            }
        }
    }

    beforeRoute(...args) {
        return Promise.resolve(args || []);
    }

    afterRoute() {
        return Promise.resolve();
    }

    backState() {
        return this.router.back();
    }

    forwardState() {
        return this.router.forward();
    }

    handleLink() {
        return true;
    }

    route(controller, action, paths = '') {
        paths = paths.split('/');
        if (controller) {
            let RequestedController = this.routeMap[controller];
            if (typeof RequestedController !== 'undefined') {
                return this.dispatchController(RequestedController).then((ctr) => {
                    let promise;
                    if (action && typeof ctr[action] === 'function') {
                        promise = ctr[action].call(ctr, ...paths);
                    } else {
                        promise = ctr.exec(action, ...paths);
                    }
                    return promise
                        .then((ctrRes) =>
                            this.dispatchView(ctr, ctrRes)
                        )
                        .catch((err) =>
                            this.throwException(err)
                        );
                });
            }
        }
        this.notFoundException();
        return Promise.reject();
    }

    notFoundException() {
        this.throwException(new EXCEPTIONS.ContentNotFoundException());
    }

    dispatchController(RequestedController, ...args) {
        let ctr = new RequestedController(this, ...args);
        let destroyCtr = Promise.resolve();
        let previousCtr = internal(this).currentController;
        if (previousCtr) {
            previousCtr.off('update');
            destroyCtr = previousCtr.destroy();
        }
        return destroyCtr.then(() => {
            internal(this).currentController = ctr;
            return ctr.ready
                .then(() => Promise.resolve(ctr))
                .catch(() => Promise.reject(ctr));
        });
    }

    dispatchView(controller, controllerResponse) {
        const AppView = this.constructor.View;
        return new Promise((resolve) => {
            let view = new AppView(controller, controllerResponse);
            internal(this).currentView = view;
            internal(this).pagesDispatcher.add(view).then((page) => {
                let oldPage = this.currentPage;
                let destroyPromise = oldPage ? oldPage.destroy() : Promise.resolve();
                this.currentPage = page;
                this.debounce(() => {
                    destroyPromise.then(() => {
                        let shown = this.currentPage.show(!oldPage);
                        if (controller) {
                            if (controller.dispatchResolved) {
                                controller.dispatchResolved();
                            }
                            controller.on('update', (newCtrRes) =>
                                this.updateView(newCtrRes)
                            );
                        }
                        shown.then(() => resolve());
                    });
                });
            });
        });
    }

    updateView(controllerResponse) {
        if (internal(this).currentView) {
            return internal(this).currentView
                .update(controllerResponse);
        }
        return Promise.reject();
    }

    debounce(callback) {
        return debounce(callback);
    }

    navigate(...args) {
        return this.router.navigate(...args);
    }

    refresh() {
        return this.router.refresh();
    }

    throwException(err) {
        if (err && err instanceof EXCEPTIONS.AppException) {
            this.debounce(() => {
                if (!this.handleException(err)) {
                    throw err;
                }
            });
        }
    }

    handleException(err) {
        if (err instanceof EXCEPTIONS.ContentNotFoundException) {
            this.notFound();
            return true;
        } else if (err instanceof EXCEPTIONS.ContentErrorException) {
            this.error();
            return true;
        }
        return false;
    }

    notFound() {
        // NOT FOUND
        return Promise.resolve();
    }

    error() {
        // ERROR
        return Promise.resolve();
    }
}
<|endoftext|>


<commit_before>import {NicSvg} from './model/nic-svg';

global.app = function (element, config_path) {
    var nicaragua = new NicSvg(element, config_path);
    nicaragua.svgToDom();
};<commit_msg>return json parse<commit_after>import {NicSvg} from './model/nic-svg';

global.app = function (element, config_path) {
    var nicaragua = new NicSvg(element, config_path);
    nicaragua.build();
};<|endoftext|>


<commit_before>/*
 * Copyrights phwang
 * Written by Paul Hwang
 * File name: list_mgr.js
 */

module.exports = {
    malloc_mgr: function (host_object_val, global_id_val) {
        return new ListMgrClass(host_object_val, global_id_val);
    },

    malloc_joint: function (entry_id_val, entry_name_val) {
        return new ListjointClass(entry_id_val, entry_name_val);
    },

    head: function (list_mgr_val) {
        if (list_mgr_val === null) {
            return null;
        }
        return list_mgr_val.head();
    },

    next: function (host_object_val) {
        if (host_object_val === null) {
            return null;
        }
        if (host_object_val.jointObject().next() === null) {
            return null;
        }
        return host_object_val.jointObject().next().hostObject();
    },
};

function ListMgrClass(host_object_val, global_id_val) {
    "use strict";

    this.init__ = function (host_object_val, global_id_val) {
        this.theHostObject = host_object_val;
        this.theGlobalId = global_id_val;
        this.theHead = null;
        this.theTail = null;
        this.theSize = 0;
        this.debug(true, "init__", "");
    };

    this.objectName = function () {
        return "ListMgrClass";
    };

    this.hostObject = function () {
        return this.theHostObject;
    };

    this.head = function () {
        return this.theHead;
    }

    this.setHead = function (val) {
        this.theHead = val;
    }

    this.tail = function () {
        return this.theTail;
    }

    this.setTail = function (val) {
        this.theTail = val;
    }

    this.size = function () {
        return this.theSize;
    }

    this.incrementSize = function () {
        this.theSize += 1;
    }

    this.decrementSize = function () {
        this.theSize -= 1;
    }

    this.globalId = function () {
        return this.theGlobalId;
    };

    this.incrementGlobalId = function () {
        this.theGlobalId += 1;
    };

    this.allocId = function () {
        this.incrementGlobalId();
        return this.globalId();
    }

    this.insertEntry = function (link_val) {
        if (!link_val) {
            this.abend("insertEntry", "null link_val");
            return;
        }

        this.abendIt();

        this.incrementSize();
        if (!this.head()) {
            link_val.jointObject().setPrev(null);
            link_val.jointObject().setNext(null);
            this.setHead(link_val);
            this.setTail(link_val);
        } else {
            this.tail().jointObject().setNext(link_val);
            link_val.jointObject().setPrev(this.tail());
            link_val.jointObject().setNext(null);
            this.setTail(link_val);
        }
        this.abendIt();
    };

    this.deleteEntry = function (link_val) {
        if (this.size() <= 0) {
            this.abend("deleteEntry", "size=" + this.size());
            return;
        }
        if (!this.linkExistInTheList(link_val)) {
            this.abend("deleteEntry", "linkExistInTheList is false");
            return;
        }

        this.abendIt();
        if (link_val.jointObject().prev()) {
            link_val.jointObject().prev().jointObject().setNext(link_val.next());
        } else {
            this.setHead(link_val.jointObject().next());
        }
        if (link_val.jointObject().next()) {
            link_val.jointObject().next().setPrev(link_val.jointObject().prev());
        } else {
            this.setTail(link_val.jointObject().prev());
        }
        this.decrementSize();
        this.abendIt();
    };

    this.searchId = function (id_val) {
        this.debug(false, "searchId", "id=" + id_val);
        var entry = this.head();
        while (entry) {
            if (entry.jointObject().entryId() === id_val) {
                return entry;
            }
            entry = entry.jointObject().next();
        }
        return null;
    };

    this.searchIdName = function (id_val, name_val) {
        this.debug(false, "searchIdName", "id=" + id_val + " name=" + name_val);
        var entry = this.head();
        while (entry) {
            this.debug(false, "searchIdName", "entryId=" + entry.jointObject().entryId() + " entryName=" + entry.jointObject().entryName());
            if ((entry.jointObject().entryId() === id_val) && (entry.jointObject().entryName() === name_val)) {
                return entry;
            }
            entry = entry.jointObject().next();
        }
        return null;
    };

    this.searchLinkByName = function (my_name_val) {
        this.debug(false, "searchLinkByName", my_name_val);
        var link = this.head();
        while (link) {
            if (link.myName() === my_name_val) {
                return link;
            }
            link = link.next();
        }
        return null;
    };

    this.linkExistInTheList = function (link_val) {
        var link = this.head();
        while (link) {
            if (link === link_val) {
                return true;
            }
            link = link.next();
        }
        return false;
    };

    this.setNameListChanged = function () {
        var link = this.head();
        while (link) {
            link.setNameListChanged();
            link = link.next();
        }
    };

    this.getNameList = function () {
        var name_array = [];
        var i = 0;
        var link = this.head();
        while (link) {
            name_array[i] = link.myName();
            i += 1;
            link = link.next();
        }
        return name_array;
    };

    this.abendIt = function () {
        var i = 0;
        var link = this.head();
        while (link) {
            link = link.jointObject().next();
            i += 1;
        }
        if (i !== this.size()) {
            this.abend("abendIt", "head: size=" + this.size() + " i=" + i);
        }

        i = 0;
        link = this.tail();
        while (link) {
            link = link.jointObject().prev();
            i += 1;
        }
        if (i !== this.size()) {
            this.abend("abendIt", "tail: size=" + this.size() + " i=" + i);
        }
    };

    this.debug = function (debug_val, str1_val, str2_val) {
        if (debug_val) {
            this.logit(str1_val, str2_val);
        }
    };

    this.logit = function (str1_val, str2_val) {
        this.LOG_IT(this.objectName() + "." + str1_val, str2_val);
    };

    this.abend = function (str1_val, str2_val) {
        this.ABEND(this.objectName() + "." + str1_val, str2_val);
    };

    this.LOG_IT = function(str1_val, str2_val) {
        require("../util_modules/logit.js").LOG_IT(str1_val, str2_val);
    };

    this.ABEND = function(str1_val, str2_val) {
        require("../util_modules/logit.js").ABEND(str1_val, str2_val);
    };

    this.init__(host_object_val, global_id_val);
}

function ListjointClass(entry_id_val, entry_name_val) {
    "use strict";

    this.init__ = function (entry_id_val, entry_name_val) {
        this.theEntryId = entry_id_val;
        this.theEntryName = entry_name_val;
        this.thePrev = null;
        this.theNext = null;
    };

    this.entryId = function () {
        return this.theEntryId;
    };

    this.entryName = function () {
        return this.theEntryName;
    };

    this.prev = function () {
        return this.thePrev;
    };

    this.setPrev = function (val) {
        this.thePrev = val;
    };

    this.next = function () {
        return this.theNext;
    };

    this.setNext = function (val) {
        this.theNext = val;
    };

    this.init__(entry_id_val, entry_name_val);
};
<commit_msg>matrix<commit_after>/*
 * Copyrights phwang
 * Written by Paul Hwang
 * File name: list_mgr.js
 */

module.exports = {
    malloc_mgr: function (host_object_val, global_id_val) {
        return new ListMgrClass(host_object_val, global_id_val);
    },

    malloc_joint: function (entry_id_val, entry_name_val) {
        return new ListjointClass(entry_id_val, entry_name_val);
    },

    head: function (list_mgr_val) {
        if (list_mgr_val === null) {
            return null;
        }
        return list_mgr_val.head();
    },

    next: function (host_object_val) {
        if (host_object_val === null) {
            return null;
        }
        if (host_object_val.jointObject().next() === null) {
            return null;
        }
        return host_object_val.jointObject().next().hostObject();
    },
};

function ListMgrClass(host_object_val, global_id_val) {
    "use strict";

    this.init__ = function (host_object_val, global_id_val) {
        this.theHostObject = host_object_val;
        this.theGlobalId = global_id_val;
        this.theHead = null;
        this.theTail = null;
        this.theSize = 0;
        this.debug(true, "init__", host_object_val.objectName());
    };

    this.objectName = function () {
        return "ListMgrClass";
    };

    this.hostObject = function () {
        return this.theHostObject;
    };

    this.head = function () {
        return this.theHead;
    }

    this.setHead = function (val) {
        this.theHead = val;
    }

    this.tail = function () {
        return this.theTail;
    }

    this.setTail = function (val) {
        this.theTail = val;
    }

    this.size = function () {
        return this.theSize;
    }

    this.incrementSize = function () {
        this.theSize += 1;
    }

    this.decrementSize = function () {
        this.theSize -= 1;
    }

    this.globalId = function () {
        return this.theGlobalId;
    };

    this.incrementGlobalId = function () {
        this.theGlobalId += 1;
    };

    this.allocId = function () {
        this.incrementGlobalId();
        return this.globalId();
    }

    this.insertEntry = function (link_val) {
        if (!link_val) {
            this.abend("insertEntry", "null link_val");
            return;
        }

        this.abendIt();

        this.incrementSize();
        if (!this.head()) {
            link_val.jointObject().setPrev(null);
            link_val.jointObject().setNext(null);
            this.setHead(link_val);
            this.setTail(link_val);
        } else {
            this.tail().jointObject().setNext(link_val);
            link_val.jointObject().setPrev(this.tail());
            link_val.jointObject().setNext(null);
            this.setTail(link_val);
        }
        this.abendIt();
    };

    this.deleteEntry = function (link_val) {
        if (this.size() <= 0) {
            this.abend("deleteEntry", "size=" + this.size());
            return;
        }
        if (!this.linkExistInTheList(link_val)) {
            this.abend("deleteEntry", "linkExistInTheList is false");
            return;
        }

        this.abendIt();
        if (link_val.jointObject().prev()) {
            link_val.jointObject().prev().jointObject().setNext(link_val.next());
        } else {
            this.setHead(link_val.jointObject().next());
        }
        if (link_val.jointObject().next()) {
            link_val.jointObject().next().setPrev(link_val.jointObject().prev());
        } else {
            this.setTail(link_val.jointObject().prev());
        }
        this.decrementSize();
        this.abendIt();
    };

    this.searchId = function (id_val) {
        this.debug(false, "searchId", "id=" + id_val);
        var entry = this.head();
        while (entry) {
            if (entry.jointObject().entryId() === id_val) {
                return entry;
            }
            entry = entry.jointObject().next();
        }
        return null;
    };

    this.searchIdName = function (id_val, name_val) {
        this.debug(false, "searchIdName", "id=" + id_val + " name=" + name_val);
        var entry = this.head();
        while (entry) {
            this.debug(false, "searchIdName", "entryId=" + entry.jointObject().entryId() + " entryName=" + entry.jointObject().entryName());
            if ((entry.jointObject().entryId() === id_val) && (entry.jointObject().entryName() === name_val)) {
                return entry;
            }
            entry = entry.jointObject().next();
        }
        return null;
    };

    this.searchLinkByName = function (my_name_val) {
        this.debug(false, "searchLinkByName", my_name_val);
        var link = this.head();
        while (link) {
            if (link.myName() === my_name_val) {
                return link;
            }
            link = link.next();
        }
        return null;
    };

    this.linkExistInTheList = function (link_val) {
        var link = this.head();
        while (link) {
            if (link === link_val) {
                return true;
            }
            link = link.next();
        }
        return false;
    };

    this.setNameListChanged = function () {
        var link = this.head();
        while (link) {
            link.setNameListChanged();
            link = link.next();
        }
    };

    this.getNameList = function () {
        var name_array = [];
        var i = 0;
        var link = this.head();
        while (link) {
            name_array[i] = link.myName();
            i += 1;
            link = link.next();
        }
        return name_array;
    };

    this.abendIt = function () {
        var i = 0;
        var link = this.head();
        while (link) {
            link = link.jointObject().next();
            i += 1;
        }
        if (i !== this.size()) {
            this.abend("abendIt", "head: size=" + this.size() + " i=" + i);
        }

        i = 0;
        link = this.tail();
        while (link) {
            link = link.jointObject().prev();
            i += 1;
        }
        if (i !== this.size()) {
            this.abend("abendIt", "tail: size=" + this.size() + " i=" + i);
        }
    };

    this.debug = function (debug_val, str1_val, str2_val) {
        if (debug_val) {
            this.logit(str1_val, str2_val);
        }
    };

    this.logit = function (str1_val, str2_val) {
        this.LOG_IT(this.objectName() + "." + str1_val, str2_val);
    };

    this.abend = function (str1_val, str2_val) {
        this.ABEND(this.objectName() + "." + str1_val, str2_val);
    };

    this.LOG_IT = function(str1_val, str2_val) {
        require("../util_modules/logit.js").LOG_IT(str1_val, str2_val);
    };

    this.ABEND = function(str1_val, str2_val) {
        require("../util_modules/logit.js").ABEND(str1_val, str2_val);
    };

    this.init__(host_object_val, global_id_val);
}

function ListjointClass(entry_id_val, entry_name_val) {
    "use strict";

    this.init__ = function (entry_id_val, entry_name_val) {
        this.theEntryId = entry_id_val;
        this.theEntryName = entry_name_val;
        this.thePrev = null;
        this.theNext = null;
    };

    this.entryId = function () {
        return this.theEntryId;
    };

    this.entryName = function () {
        return this.theEntryName;
    };

    this.prev = function () {
        return this.thePrev;
    };

    this.setPrev = function (val) {
        this.thePrev = val;
    };

    this.next = function () {
        return this.theNext;
    };

    this.setNext = function (val) {
        this.theNext = val;
    };

    this.init__(entry_id_val, entry_name_val);
};
<|endoftext|>


<commit_before>const path = require('path')
const Koa = require('koa')
const views = require('koa-views')
const serve = require('koa-static')
const onerror = require('koa-onerror')
const log4js = require('log4js')
const util = require('util')

const middleware = require('./middleware')
const logger = log4js.getLogger('app.js')
const database = require('./config/database')
const config = require('./config')
const router = require('./routes')
const app = new Koa()

app.keys = ['secret', 'lisa']

// 初始化数据库
database()

// 静态资源目录
app.use(serve(path.join(__dirname, '../views/static')))

// Views
app.use(views(path.join(__dirname, '../views'), {
  extensio: 'swig',
  map: {
    html: 'swig'
  }
}))

// global error
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (e) {
    logger.error('global error', e, ctx)
    ctx.body = {
      code: e.status || 90001,
      data: '',
      message: e.message || 'Server internal error.'
    }
  }
})

app.on('error', function (err, ctx) {
  logger.error('server error', err, ctx)
})

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

app.use(middleware())

onerror(app, {
  engine: 'swig',
  template: path.join(__dirname, '../views/500.html')
})

app.use(router.routes()).use(router.allowedMethods())

// 404
app.use(async (ctx, next) => {
  logger.info('请求404')
  util.log(`request to ${JSON.stringify(ctx.request)}`)
  ctx.status = 404
  await ctx.render('404')
})

// TODO log4js使用示例
// logger.trace('Entering cheese testing')
// logger.debug('Some debug messages.')
// logger.warn('Cheese is quite smelly.')
// logger.error('Cheese is too ripe!')
// logger.fatal('Cheese was breeding ground for listeria.')

logger.info('Server Start listening on port: ', config.app.port || '3333')
app.listen(config.app.port || '3333')

module.exports = app
<commit_msg>update<commit_after>const path = require('path')
const Koa = require('koa')
const views = require('koa-views')
const serve = require('koa-static')
const onerror = require('koa-onerror')
const log4js = require('log4js')
const util = require('util')

const middleware = require('./middleware')
const logger = log4js.getLogger('app.js')
const database = require('./config/database')
const config = require('./config')
const router = require('./routes')
const app = new Koa()

app.keys = ['secret', 'lisa']

// 初始化数据库
database()

// 静态资源目录
app.use(serve(path.join(__dirname, '../views/static')))

// Views
app.use(views(path.join(__dirname, '../views'), {
  extensio: 'swig',
  map: {
    html: 'swig'
  }
}))

// global error
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (e) {
    logger.error('global error', e, ctx)
    ctx.body = {
      code: e.status || 90001,
      data: '',
      message: e.message || 'Server internal error.'
    }
  }
})

app.on('error', function (err, ctx) {
  logger.error('server error', err, ctx)
})

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

app.use(middleware())

onerror(app, {
  engine: 'swig',
  template: path.join(__dirname, '../views/500.html')
})

app.use(router.routes()).use(router.allowedMethods())

// 404
app.use(async (ctx, next) => {
  logger.info('请求404')
  util.log(`request to ${JSON.stringify(ctx.request.url)}`)
  ctx.status = 404
  await ctx.render('404')
})

// TODO log4js使用示例
// logger.trace('Entering cheese testing')
// logger.debug('Some debug messages.')
// logger.warn('Cheese is quite smelly.')
// logger.error('Cheese is too ripe!')
// logger.fatal('Cheese was breeding ground for listeria.')

logger.info('Server Start listening on port: ', config.app.port || '3333')
app.listen(config.app.port || '3333')

module.exports = app
<|endoftext|>


<commit_before>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;

const apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
const sessionIds = new Map();

const generic_message = {
    attachment: {
        type: "template",
        payload: {
            template_type: "generic",
            elements: [{
                title: "Fresh Pizza",
                subtitle: "Be carefull and don't eat your fingers!!!",
                item_url: "https://en.wikipedia.org/wiki/Pizza",
                //image_url: "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg",
                buttons: [{
                    type: "web_url",
                    url: "https://en.wikipedia.org/wiki/Pizza",
                    title: "Open Web URL"
                }/*, {
                 type: "postback",
                 title: "Call Postback",
                 payload: "Payload for first bubble"
                 }*/]
            }]
        }
    }
};

function processEvent(event) {
    var sender = event.sender.id.toString();

    if ((event.message && event.message.text) || (event.postback && event.postback.payload)) {
        var text = event.message ? event.message.text : event.postback.payload;
        // Handle a text message from this sender

        if (!sessionIds.has(sender)) {
            sessionIds.set(sender, uuid.v1());
        }

        console.log("Text", text);

        let apiaiRequest = apiAiService.textRequest(text,
            {
                sessionId: sessionIds.get(sender)
            });

        apiaiRequest.on('response', (response) => {
            if (isDefined(response.result)) {
                //asd
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let action = response.result.action;
                /*console.log(responseText);
                console.log(responseData);
                console.log(action);*/
                if (isDefined(responseData) && isDefined(responseData.facebook)) {
                    if (!Array.isArray(responseData.facebook)) {
                        try {
                            console.log('Response as formatted message');
                            sendFBMessage(sender, responseData.facebook);
                        } catch (err) {
                            sendFBMessage(sender, {text: err.message});
                        }
                    } else {
                        responseData.facebook.forEach((facebookMessage) => {
                            try {
                                if (facebookMessage.sender_action) {
                                    console.log('Response as sender action');
                                    sendFBSenderAction(sender, facebookMessage.sender_action);
                                }
                                else {
                                    console.log('Response as formatted message');
                                    sendFBMessage(sender, facebookMessage);
                                }
                            } catch (err) {
                                sendFBMessage(sender, {text: err.message});
                            }
                        });
                    }
                //} else if (isDefined(responseText)) {
                } else if (isDefined(action) && isDefined(responseText)) {
                    console.log('Response as text message');
                    // facebook API limit for text length is 320,
                    // so we must split message if needed
                    var splittedText = splitResponse(responseText);
                    /*switch (action){
                        case 'show_prod':
                            console.log('generic');
                            async.eachSeries(splittedText, (textPart, callback) => {
                                sendFBMessage(sender, {text: textPart}, callback);
                            });
                            sendFBMessage(sender, generic_message, null);
                            break;
                        default:
                            console.log('default');
                            async.eachSeries(splittedText, (textPart, callback) => {
                                sendFBMessage(sender, {text: textPart}, callback);
                            });
                    }*/
                    async.eachSeries(splittedText, (textPart, callback) => {
                        sendFBMessage(sender, {text: textPart}, callback);
                    });
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }
}

function splitResponse(str) {
    if (str.length <= 320) {
        return [str];
    }

    return chunkString(str, 300);
}

function chunkString(s, len) {
    var curr = len, prev = 0;

    var output = [];

    while (s[curr]) {
        if (s[curr++] == ' ') {
            output.push(s.substring(prev, curr));
            prev = curr;
            curr += len;
        }
        else {
            var currReverse = curr;
            do {
                if (s.substring(currReverse - 1, currReverse) == ' ') {
                    output.push(s.substring(prev, currReverse));
                    prev = currReverse;
                    curr = currReverse + len;
                    break;
                }
                currReverse--;
            } while (currReverse > prev)
        }
    }
    output.push(s.substr(prev));
    //console.log(output);
    //output = ['Length of param message[text] must be less than or equal to 320'];
    return output;
}

/*const generic_message = {
    attachment: {
        type: "template",
        payload: {
            template_type: "generic",
            elements: [{
                title: "rift",
                subtitle: "Next-generation virtual reality",
                item_url: "https://www.oculus.com/en-us/rift/",
                image_url: "http://messengerdemo.parseapp.com/img/rift.png",
                buttons: [{
                    type: "web_url",
                    url: "https://www.oculus.com/en-us/rift/",
                    title: "Open Web URL"
                }, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for first bubble"
                }]
            }, {
                title: "touch",
                subtitle: "Your Hands, Now in VR",
                item_url: "https://www.oculus.com/en-us/touch/",
                image_url: "http://messengerdemo.parseapp.com/img/touch.png",
                buttons: [{
                    type: "web_url",
                    url: "https://www.oculus.com/en-us/touch/",
                    title: "Open Web URL"
                }, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for second bubble"
                }]
            }]
        }
    }
};*/

function sendFBMessage(sender, messageData, callback) {
    //console.log(sender);
    request({
        url: 'https://graph.facebook.com/v2.6/me/messages',
        qs: {access_token: FB_PAGE_ACCESS_TOKEN},
        method: 'POST',
        json: {
            recipient: {id: sender},
            message: messageData
            //message: generic_message
        }
    }, (error, response, body) => {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }

        if (callback) {
            callback();
        }
    });
}

function sendFBSenderAction(sender, action, callback) {
    setTimeout(() => {
        request({
            url: 'https://graph.facebook.com/v2.6/me/messages',
            qs: {access_token: FB_PAGE_ACCESS_TOKEN},
            method: 'POST',
            json: {
                recipient: {id: sender},
                sender_action: action
            }
        }, (error, response, body) => {
            if (error) {
                console.log('Error sending action: ', error);
            } else if (response.body.error) {
                console.log('Error: ', response.body.error);
            }
            if (callback) {
                callback();
            }
        });
    }, 1000);
}

function doSubscribeRequest() {
    request({
            method: 'POST',
            uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
        },
        (error, response, body) => {
            if (error) {
                console.error('Error while subscription: ', error);
            } else {
                console.log('Subscription result: ', response.body);
            }
        });
}

function isDefined(obj) {
    if (typeof obj == 'undefined') {
        return false;
    }

    if (!obj) {
        return false;
    }

    return obj != null;
}

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        var data = JSONbig.parse(req.body);
        var asd = data;
        //console.log(asd);
        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        //console.log(event);
                        if (event.message && !event.message.is_echo ||
                            event.postback && event.postback.payload) {
                            processEvent(event);
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.post('/webhook_apiai/', (req, res) => {
    try {
        var data = JSONbig.parse(req.body);
        //console.log(data);
        switch(data.result.parameters.pizza_type){
            case 'Margherita':
                generic_message.attachment.payload.elements.title = data.result.parameters.pizza_type;
                generic_message.attachment.payload.elements.image_url = "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg";
        }
        console.log(generic_message.payload.elements);
        return res.status(200).json({
            speech: data.result.resolvedQuery,
            displayText: 'displayText',
            data: {
                facebook: generic_message,
            }
        })
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }
});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

doSubscribeRequest();
<commit_msg>'webhook_test'<commit_after>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;

const apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
const sessionIds = new Map();

const generic_message = {
    attachment: {
        type: "template",
        payload: {
            template_type: "generic",
            elements: [{
                title: "Fresh Pizza",
                subtitle: "Be carefull and don't eat your fingers!!!",
                item_url: "https://en.wikipedia.org/wiki/Pizza",
                //image_url: "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg",
                buttons: [{
                    type: "web_url",
                    url: "https://en.wikipedia.org/wiki/Pizza",
                    title: "Open Web URL"
                }/*, {
                 type: "postback",
                 title: "Call Postback",
                 payload: "Payload for first bubble"
                 }*/]
            }]
        }
    }
};

function processEvent(event) {
    var sender = event.sender.id.toString();

    if ((event.message && event.message.text) || (event.postback && event.postback.payload)) {
        var text = event.message ? event.message.text : event.postback.payload;
        // Handle a text message from this sender

        if (!sessionIds.has(sender)) {
            sessionIds.set(sender, uuid.v1());
        }

        console.log("Text", text);

        let apiaiRequest = apiAiService.textRequest(text,
            {
                sessionId: sessionIds.get(sender)
            });

        apiaiRequest.on('response', (response) => {
            if (isDefined(response.result)) {
                //asd
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let action = response.result.action;
                /*console.log(responseText);
                console.log(responseData);
                console.log(action);*/
                if (isDefined(responseData) && isDefined(responseData.facebook)) {
                    if (!Array.isArray(responseData.facebook)) {
                        try {
                            console.log('Response as formatted message');
                            sendFBMessage(sender, responseData.facebook);
                        } catch (err) {
                            sendFBMessage(sender, {text: err.message});
                        }
                    } else {
                        responseData.facebook.forEach((facebookMessage) => {
                            try {
                                if (facebookMessage.sender_action) {
                                    console.log('Response as sender action');
                                    sendFBSenderAction(sender, facebookMessage.sender_action);
                                }
                                else {
                                    console.log('Response as formatted message');
                                    sendFBMessage(sender, facebookMessage);
                                }
                            } catch (err) {
                                sendFBMessage(sender, {text: err.message});
                            }
                        });
                    }
                //} else if (isDefined(responseText)) {
                } else if (isDefined(action) && isDefined(responseText)) {
                    console.log('Response as text message');
                    // facebook API limit for text length is 320,
                    // so we must split message if needed
                    var splittedText = splitResponse(responseText);
                    /*switch (action){
                        case 'show_prod':
                            console.log('generic');
                            async.eachSeries(splittedText, (textPart, callback) => {
                                sendFBMessage(sender, {text: textPart}, callback);
                            });
                            sendFBMessage(sender, generic_message, null);
                            break;
                        default:
                            console.log('default');
                            async.eachSeries(splittedText, (textPart, callback) => {
                                sendFBMessage(sender, {text: textPart}, callback);
                            });
                    }*/
                    async.eachSeries(splittedText, (textPart, callback) => {
                        sendFBMessage(sender, {text: textPart}, callback);
                    });
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }
}

function splitResponse(str) {
    if (str.length <= 320) {
        return [str];
    }

    return chunkString(str, 300);
}

function chunkString(s, len) {
    var curr = len, prev = 0;

    var output = [];

    while (s[curr]) {
        if (s[curr++] == ' ') {
            output.push(s.substring(prev, curr));
            prev = curr;
            curr += len;
        }
        else {
            var currReverse = curr;
            do {
                if (s.substring(currReverse - 1, currReverse) == ' ') {
                    output.push(s.substring(prev, currReverse));
                    prev = currReverse;
                    curr = currReverse + len;
                    break;
                }
                currReverse--;
            } while (currReverse > prev)
        }
    }
    output.push(s.substr(prev));
    //console.log(output);
    //output = ['Length of param message[text] must be less than or equal to 320'];
    return output;
}

/*const generic_message = {
    attachment: {
        type: "template",
        payload: {
            template_type: "generic",
            elements: [{
                title: "rift",
                subtitle: "Next-generation virtual reality",
                item_url: "https://www.oculus.com/en-us/rift/",
                image_url: "http://messengerdemo.parseapp.com/img/rift.png",
                buttons: [{
                    type: "web_url",
                    url: "https://www.oculus.com/en-us/rift/",
                    title: "Open Web URL"
                }, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for first bubble"
                }]
            }, {
                title: "touch",
                subtitle: "Your Hands, Now in VR",
                item_url: "https://www.oculus.com/en-us/touch/",
                image_url: "http://messengerdemo.parseapp.com/img/touch.png",
                buttons: [{
                    type: "web_url",
                    url: "https://www.oculus.com/en-us/touch/",
                    title: "Open Web URL"
                }, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for second bubble"
                }]
            }]
        }
    }
};*/

function sendFBMessage(sender, messageData, callback) {
    //console.log(sender);
    request({
        url: 'https://graph.facebook.com/v2.6/me/messages',
        qs: {access_token: FB_PAGE_ACCESS_TOKEN},
        method: 'POST',
        json: {
            recipient: {id: sender},
            message: messageData
            //message: generic_message
        }
    }, (error, response, body) => {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }

        if (callback) {
            callback();
        }
    });
}

function sendFBSenderAction(sender, action, callback) {
    setTimeout(() => {
        request({
            url: 'https://graph.facebook.com/v2.6/me/messages',
            qs: {access_token: FB_PAGE_ACCESS_TOKEN},
            method: 'POST',
            json: {
                recipient: {id: sender},
                sender_action: action
            }
        }, (error, response, body) => {
            if (error) {
                console.log('Error sending action: ', error);
            } else if (response.body.error) {
                console.log('Error: ', response.body.error);
            }
            if (callback) {
                callback();
            }
        });
    }, 1000);
}

function doSubscribeRequest() {
    request({
            method: 'POST',
            uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
        },
        (error, response, body) => {
            if (error) {
                console.error('Error while subscription: ', error);
            } else {
                console.log('Subscription result: ', response.body);
            }
        });
}

function isDefined(obj) {
    if (typeof obj == 'undefined') {
        return false;
    }

    if (!obj) {
        return false;
    }

    return obj != null;
}

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        var data = JSONbig.parse(req.body);
        var asd = data;
        //console.log(asd);
        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        //console.log(event);
                        if (event.message && !event.message.is_echo ||
                            event.postback && event.postback.payload) {
                            processEvent(event);
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.post('/webhook_apiai/', (req, res) => {
    try {
        var data = JSONbig.parse(req.body);
        //console.log(data);
        switch(data.result.parameters.pizza_type){
            case 'Margherita':
                generic_message.attachment.payload.elements.title = data.result.parameters.pizza_type;
                generic_message.attachment.payload.elements.image_url = "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg";
        }
        console.log(generic_message);
        console.log(JSON.stringify(generic_message));
        return res.status(200).json({
            speech: data.result.resolvedQuery,
            displayText: 'displayText',
            data: {
                facebook: generic_message,
            }
        })
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }
});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

doSubscribeRequest();
<|endoftext|>


<commit_before>var http = require('http');
var path = require('path');
var Api = require('./lib/api');
var Cards = require('./lib/cards');
var express = require('express');
var logger = require('morgan');
var app = express();

// view engine setup
console.log('starting');
app.set('views', path.join(__dirname, 'views'));
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.set('view engine', 'jade');

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

app.get('/', (req, res) => res.render('index'));
app.get('/print', (req, res) => res.render('print', {
  whiteCards: Cards.answers,
  blackCards: Cards.questions
}));

// catch 404 and forward to error handler
app.use((req, res, next) => {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use((err, req, res, next) => {
    res.status(err.status || 500);
    console.log(app.get('env'));
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use((err, req, res, next) => {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

var server = app.listen(process.env.PORT || 3000, () => {

  var host = server.address().address;
  var port = server.address().port;

  console.log('Caracas Against Humanity listening at http://%s:%s', host, port);

});
<commit_msg>Cleanup<commit_after>var http = require('http');
var path = require('path');
var Api = require('./lib/api');
var Cards = require('./lib/cards');
var express = require('express');
var logger = require('morgan');
var app = express();

app.set('views', path.join(__dirname, 'views'));
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.set('view engine', 'jade');

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

app.get('/', (req, res) => res.render('index'));
app.get('/print', (req, res) => res.render('print', {
  whiteCards: Cards.answers,
  blackCards: Cards.questions
}));

// catch 404 and forward to error handler
app.use((req, res, next) => {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// ERROR HANDLERS
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use((err, req, res, next) => {
    res.status(err.status || 500);
    console.log(app.get('env'));
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use((err, req, res, next) => {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

var server = app.listen(process.env.PORT || 3000, () => {

  var host = server.address().address;
  var port = server.address().port;

  console.log('Caracas Against Humanity listening at http://%s:%s', host, port);

});
<|endoftext|>