From bf865d4535efc73468b7c092039ced565ba187e7 Mon Sep 17 00:00:00 2001 From: Brandon Rozek Date: Mon, 11 Jul 2016 23:24:15 -0400 Subject: [PATCH] initial commit --- .gitignore | 1 + Rozbot.js | 55 ++++++++++++ User.js | 57 +++++++++++++ example.js | 36 ++++++++ lib/commands/Command.js | 8 ++ lib/commands/Hangman.js | 112 +++++++++++++++++++++++++ lib/commands/calculate.js | 23 +++++ lib/commands/displayTweet.js | 26 ++++++ lib/commands/getWeather.js | 48 +++++++++++ lib/commands/grabDefinition.js | 33 ++++++++ lib/commands/grabPictures.js | 42 ++++++++++ lib/commands/grabTweets.js | 24 ++++++ lib/commands/grabUpdates.js | 32 +++++++ lib/commands/grabWikipedia.js | 22 +++++ lib/commands/grabWikipedia~ | 0 lib/commands/naturalSpeech.js | 125 ++++++++++++++++++++++++++++ lib/commands/onTheRadio.js | 15 ++++ lib/commands/queryDuckDuckGo.js | 25 ++++++ lib/commands/search.js | 19 +++++ lib/commands/timer.js | 40 +++++++++ lib/helpers/additionalPrototypes.js | 39 +++++++++ lib/helpers/grabFeedURL.js | 27 ++++++ lib/helpers/grabTitles.js | 22 +++++ lib/helpers/grabTokens.js | 10 +++ lib/helpers/grabURL.js | 19 +++++ lib/helpers/lastModified.js | 7 ++ lib/helpers/parseDuckDuckGo.js | 22 +++++ lib/helpers/parsePictures.js | 28 +++++++ lib/helpers/parseTwitter.js | 22 +++++ lib/helpers/parseWikipedia.js | 17 ++++ lib/helpers/parseWordnik.js | 18 ++++ lib/helpers/stopWords.js | 1 + lib/promise/appendFile.js | 13 +++ lib/promise/cheerio.js | 7 ++ lib/promise/feed.js | 14 ++++ lib/promise/fetch.js | 13 +++ lib/promise/fs-stat.js | 8 ++ lib/promise/math-eval.js | 8 ++ package.json | 18 ++++ 39 files changed, 1056 insertions(+) create mode 100644 .gitignore create mode 100644 Rozbot.js create mode 100644 User.js create mode 100644 example.js create mode 100644 lib/commands/Command.js create mode 100644 lib/commands/Hangman.js create mode 100644 lib/commands/calculate.js create mode 100644 lib/commands/displayTweet.js create mode 100644 lib/commands/getWeather.js create mode 100644 lib/commands/grabDefinition.js create mode 100644 lib/commands/grabPictures.js create mode 100644 lib/commands/grabTweets.js create mode 100644 lib/commands/grabUpdates.js create mode 100644 lib/commands/grabWikipedia.js create mode 100644 lib/commands/grabWikipedia~ create mode 100644 lib/commands/naturalSpeech.js create mode 100644 lib/commands/onTheRadio.js create mode 100644 lib/commands/queryDuckDuckGo.js create mode 100644 lib/commands/search.js create mode 100644 lib/commands/timer.js create mode 100644 lib/helpers/additionalPrototypes.js create mode 100644 lib/helpers/grabFeedURL.js create mode 100644 lib/helpers/grabTitles.js create mode 100644 lib/helpers/grabTokens.js create mode 100644 lib/helpers/grabURL.js create mode 100644 lib/helpers/lastModified.js create mode 100644 lib/helpers/parseDuckDuckGo.js create mode 100644 lib/helpers/parsePictures.js create mode 100644 lib/helpers/parseTwitter.js create mode 100644 lib/helpers/parseWikipedia.js create mode 100644 lib/helpers/parseWordnik.js create mode 100644 lib/helpers/stopWords.js create mode 100644 lib/promise/appendFile.js create mode 100644 lib/promise/cheerio.js create mode 100644 lib/promise/feed.js create mode 100644 lib/promise/fetch.js create mode 100644 lib/promise/fs-stat.js create mode 100644 lib/promise/math-eval.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/Rozbot.js b/Rozbot.js new file mode 100644 index 0000000..b70c24f --- /dev/null +++ b/Rozbot.js @@ -0,0 +1,55 @@ +var promise = require('promise-polyfill'); +var prototypes = require('./lib/helpers/additionalPrototypes.js'); +var User = require('./User.js'); + +module.exports = function() { + //Store possible commands + this.commandList = []; + //Store users + this.userList = []; + + //Add commands to the commandList + this.extend = function(command) { + if (typeof(command) === 'object') { + this.commandList.push(command); + } else { + throw new Error("Extend must take a Command Object"); + } + }; + + //Add user to userlist + this.addUser = function(username, sendMethod) { + var user = new User(username, sendMethod); + this.userList.push(user); + return user; + } + //Get's user from userlist by username + this.getUser = function(username) { + for (var i = 0; i < this.userList.length; i++) { + if (this.userList[i].username === username) { + return this.userList[i]; + } + } + //User not found + return null; + } + + //Gives Rozbot's response + this.respond = function(message, user) { + //Store whether or not an app wants to listen to the next message + var inAppScope = user.inAppScope + //Used to allow apps to get the next message + user.listener.emit('message', message); + if (!inAppScope) { + var args = [message, user.send]; + //Find the right command to run + var command = this.commandList.find(function(cmd) { + return cmd.condition(message) === true; + }); + //Run the command using user's contextual data (app by app basis) + if (command !== undefined) { + command.respond.apply(command, args.concat(user.getData(command.name))); + } + } + } +} diff --git a/User.js b/User.js new file mode 100644 index 0000000..1f0a4f3 --- /dev/null +++ b/User.js @@ -0,0 +1,57 @@ +var EventEmitter = require('events'); +var promise = require('promise-polyfill'); +module.exports = function(username, sendMethod) { + this.username = username; + this.data = {}; + this.inAppScope = false; + this.listener = new EventEmitter(); + this.send = sendMethod; + this.getData = function(commandName) { + var self = this; + //If it doesn't exist, create it + if (this.data[commandName] === undefined) { + this.data[commandName] = { + properties: [], + setProperty: function(key, value) { + //First make sure it doesn't already exist + for (var i = 0; i < this.properties.length; i++) { + //If it does then update it + if (this.properties[i].key === key) { + this.properties[i].value = value; + return; + } + } + //If it doesnt exist then add it to the properties array + this.properties.push({key: key, value: value}); + }, + getProperty: function(key) { + for (var i = 0; i < this.properties.length; i++) { + if (this.properties[i].key === key) { + return this.properties[i].value; + } + } + //Key does not exist + return null; + }, + isset: function(key) { + return this.getProperty(key) !== null; + }, + prompt: function(question) { + question = question || "is reading your next message"; + //Inform user that the next input goes to the app + self.send(commandName + ": " + question); + //Make it so that the input doesnt get processed by any other app + self.inAppScope = true; + return new promise(function(resolve, reject) { + self.listener.once('message', function(text) { + resolve(text); + self.inAppScope = false; + }); + }); + } + } + } + + return this.data[commandName]; + } +} diff --git a/example.js b/example.js new file mode 100644 index 0000000..0520799 --- /dev/null +++ b/example.js @@ -0,0 +1,36 @@ +var Rozbot = require('./Rozbot.js'); +var User = require('./User.js'); + +var rozbot = new Rozbot(); +/* + Order matters!! + -Rozbot checks through all the commands until it hits one that matches +*/ + +rozbot.extend(require('./lib/commands/onTheRadio.js')); +getWeather = require('./lib/commands/getWeather.js'); +getWeather.setId('188d435fd17dcf22261679cf64e98cd5'); +rozbot.extend(getWeather); +rozbot.extend(require('./lib/commands/grabUpdates.js')); +rozbot.extend(require('./lib/commands/grabTweets.js')); +rozbot.extend(require('./lib/commands/displayTweet.js')); +rozbot.extend(require('./lib/commands/Hangman.js')); +rozbot.extend(require('./lib/commands/grabPictures.js')); +rozbot.extend(require('./lib/commands/grabWikipedia.js')); +rozbot.extend(require('./lib/commands/grabDefinition.js')); +rozbot.extend(require('./lib/commands/calculate.js')); +rozbot.extend(require('./lib/commands/timer.js')); +rozbot.extend(require('./lib/commands/search.js')); +//rozbot.extend(require('./lib/commands/naturalSpeech.js')); +rozbot.extend(require('./lib/commands/queryDuckDuckGo.js')); + +//rozbot.respond(process.argv[2], rozbot.getUser("Command Line") || rozbot.addUser("Command Line")); +rozbot.respond("Let's play hangman!", rozbot.getUser("Command Line") || rozbot.addUser("Command Line", function(msg) { + console.log(msg); +})); +var letters = ["a", "e", "i", "o", "u", "b"] +rozbot.respond(letters[0], rozbot.getUser("Command Line")); +rozbot.respond(letters[1], rozbot.getUser("Command Line")); +rozbot.respond(letters[2], rozbot.getUser("Command Line")); +rozbot.respond(letters[3], rozbot.getUser("Command Line")); +rozbot.respond(letters[4], rozbot.getUser("Command Line")); diff --git a/lib/commands/Command.js b/lib/commands/Command.js new file mode 100644 index 0000000..595ad11 --- /dev/null +++ b/lib/commands/Command.js @@ -0,0 +1,8 @@ +module.exports = function(commandName, condition, commandFunction) { + //Name for identification purproses + this.name = commandName; + //Must be a function which returns a boolean if it believes that the it's the appropriate command for the message + this.condition = condition; + //The command's core functionality + this.respond = commandFunction; +} diff --git a/lib/commands/Hangman.js b/lib/commands/Hangman.js new file mode 100644 index 0000000..01f1010 --- /dev/null +++ b/lib/commands/Hangman.js @@ -0,0 +1,112 @@ +var Command = require('./Command.js'); +var fetch = require('../promise/fetch.js'); +var cheeriop = require('../promise/cheerio.js'); +var promise = require('promise-polyfill'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return tokens.contain('play') && tokens.contain('hangman'); +} +module.exports = new Command("Hangman", condition, function(text, send, userData) { + var GUESSES_ALLOWED = 7; + + //Lets get a random word! + send("I'm thinking of a word..."); + + //Grabs headline of random page + var grabHeadline = function(html) { + return new promise(function(resolve, reject) { + cheeriop(html).then(function($) { + var response = $('#headword').text().trim() + if (response) { + resolve(response); + } else { + reject(""); + } + }).catch(function(error) { + console.log(error); + reject(""); + }) + }); + } + + //Recursive guessing letters until person loses or wins + var guessLetter = function() { + var correctLetters = userData.getProperty("correctLetters"); + var guessedLetters = userData.getProperty("guessedLetters"); + var word = userData.getProperty("word"); + var numOfUniqueLetters = userData.getProperty("numOfUniqueLetters"); + + if (correctLetters.length === numOfUniqueLetters) { + send("You win!!"); + return; + } + if (guessedLetters.length - correctLetters.length > GUESSES_ALLOWED) { + send("You lose..."); + send("The word was " + word); + return; + } + + return new promise(function(resolve, reject) { + userData.prompt("Guess a letter").then(function(letter) { + if (letter.length != 1) { + send("You can only guess one letter at a time"); + } else { + if (guessedLetters.indexOf(letter) !== -1) { + send("You already guessed this letter"); + } else { + if (word.indexOf(letter) !== -1) { + send("The letter is in the word!"); + correctLetters.push(letter); + } else { + send("The letter is not in the word.."); + } + + //Push letter to lettersGuessed array and send the part of the word guessed so far. + guessedLetters.push(letter); + send(revealKnownCharacters(word, correctLetters)); + } + } + return; + }).then(function() { resolve(guessLetter()) }).catch(function(e) { console.log(e); reject(e); }); + }); + } + + //Shows player word with __ and known letters + var revealKnownCharacters = function(word, lettersArray) { + return word.split('').map(function(item) { + return (lettersArray.indexOf(item) !== -1)? item: "_"; + }).join(''); + } + + //Counts the number of unique letters in a word + var countUniqueLetters = function(word) { + var letters = []; + for (var i = 0; i < word.length; i++) { + if (letters.indexOf(word[i]) === -1) { + letters.push(word[i]); + } + } + return letters.length; + } + + //Grab a random word from wordnik and start the game + fetch("https://wordnik.com/randoml", {rejectUnauthorized: false}).then(function(res) { + origin = res.meta.finalUrl; + return grabHeadline(res.body.toString()); + }).then(function(word) { + send("The word is " + word.length + " letters long"); + + //Setup game variables + userData.setProperty("word", word); + userData.setProperty("numOfUniqueLetters", countUniqueLetters(word)); + userData.setProperty("correctLetters", []); + userData.setProperty("guessedLetters", []); + + //start + guessLetter(); + }).catch(function(error) { + send(error); + }); +}); diff --git a/lib/commands/calculate.js b/lib/commands/calculate.js new file mode 100644 index 0000000..d419294 --- /dev/null +++ b/lib/commands/calculate.js @@ -0,0 +1,23 @@ +var Command = require('./Command.js'); +var mathEval = require('../promise/math-eval.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return tokens.contain('calculate') || tokens.contain('calc'); +} +module.exports = new Command("Mathjax", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var query; + if (tokens.contain('calculate')) { + query = text.from('calculate'); + } else if (tokens.contain('calc')) { + query = text.from('calc'); + } + query = query.removeAll('?'); + mathEval(query.toString()).then(function(answer) { + send(answer); + }).catch(function(error) { + send("I'm not smart enough to calcuate that yet >.<"); + }); +}); diff --git a/lib/commands/displayTweet.js b/lib/commands/displayTweet.js new file mode 100644 index 0000000..68e791a --- /dev/null +++ b/lib/commands/displayTweet.js @@ -0,0 +1,26 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); +var parseTwitter = require('../helpers/parseTwitter.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return tokens.filter(function(item) { + return item.contains("twitter.com") && item.contains("/status/") + }).length > 0; +} +module.exports = new Command("DisplayTweet", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var status = tokens.filter(function(item) { + return item.contains("twitter.com") && item.contains("/status/") + }); + status.forEach(function(item) { + grabURL(item + '?').then(function(response) { + return parseTwitter(response.body.toString()); + }).then(function(tweet) { + send(tweet); + }).catch(function(error) { + console.log(error); + }); + }); +}) diff --git a/lib/commands/getWeather.js b/lib/commands/getWeather.js new file mode 100644 index 0000000..ad3759c --- /dev/null +++ b/lib/commands/getWeather.js @@ -0,0 +1,48 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return command.id != '' && tokens.contain('weather'); +} +var command = new Command("Weather", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var location = tokens.slice(tokens.indexOf('weather') + 1, tokens.length); + //Function to get the weather + var getWeather = function(loc) { + grabURL("http://api.openweathermap.org/data/2.5/weather?q=" + loc.join('+') + "&units=imperial&appid=" + command.id) + .then(function(response) { return response.body.toString(); }) + .then(function(text) { return JSON.parse(text); }) + .then(function(json) { + if (json.cod == 401) { + send("Invalid API key set"); + } else { + send(json.weather[0].description + " and the temperature is " + json.main.temp + " degrees Fahrenheit in " + json.name + "\nWeather provided by OpenWeatherMap"); + } + }).catch(function(error) { send(error); }); + } + //Did the person not specify the location and have no location set? + if (location.length === 0 && !userData.isset("location")) { + userData.prompt("Where do you live?").then(function(loc) { + userData.setProperty("location", loc.split(" ")); + getWeather(loc.split(" ")); + }); + } + //The person specified the location + else if (location.length !== 0) { + getWeather(location); + } + //The person didn't specify but he has a location saved + else { + getWeather(userData.getProperty("location")); + } +}); + +command.id = ''; +//Need to call this with a proper id for this command to work +command.setId = function(id) { + this.id = id; +} + +module.exports = command; diff --git a/lib/commands/grabDefinition.js b/lib/commands/grabDefinition.js new file mode 100644 index 0000000..e565afe --- /dev/null +++ b/lib/commands/grabDefinition.js @@ -0,0 +1,33 @@ +var Command = require('./Command.js'); +var fetch = require('../promise/fetch.js'); +var parseWordnik = require('../helpers/parseWordnik.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + var query; + if (tokens.contain("define") && tokens[tokens.indexOf('define') + 1]) { + query = tokens.splice(tokens.indexOf('define') + 1).join("%20"); + } else if (tokens.contain("definition") && tokens[tokens.indexOf('definition') + 1]) { + query = tokens.splice(tokens.indexOf('definition') + 1).join("%20"); + } + return (tokens.contain("define") || tokens.contain("definition")) && query != ""; +} +module.exports = new Command("Dictionary", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var query = ""; + if (tokens.contain("define") && tokens[tokens.indexOf('define') + 1]) { + query = tokens.splice(tokens.indexOf('define') + 1).join("%20"); + } else if (tokens.contain("definition") && tokens[tokens.indexOf('definition') + 1]) { + query = tokens.splice(tokens.indexOf('definition') + 1).join("%20"); + } + var origin; + fetch("https://www.wordnik.com/words/" + query, {rejectUnauthorized: false}).then(function(res) { + origin = res.meta.finalUrl; + return parseWordnik(res.body.toString()); + }).then(function(def) { + send(def + "\nFor more go to " + origin); + }).catch(function(error) { + send(error); + }); +}) diff --git a/lib/commands/grabPictures.js b/lib/commands/grabPictures.js new file mode 100644 index 0000000..2566bc6 --- /dev/null +++ b/lib/commands/grabPictures.js @@ -0,0 +1,42 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); +var parsePictures = require('../helpers/parsePictures.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return tokens.contain('photo') || tokens.contain('photos') || tokens.superContain('pic') || tokens.superContain('pics'); +} +module.exports = new Command("Pixabay", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var of = ""; + var amt = 0; + if (tokens.contain("photo")) { + of = tokens.slice(tokens.indexOf('photo') + 1).join(' '); + amt = 1; + } else if (tokens.contain("photos")) { + of = tokens.slice(tokens.indexOf('photos') + 1).join(' '); + amt = 5; + } else if (tokens.superContain("pic")) { + //Is the last letter a 's'? + if (tokens[tokens.superContainAt('pic')][tokens[tokens.superContainAt('pic')].length - 1] == 's') { + amt = 5; + } else { + amt = 1; + } + of = tokens.slice(tokens.superContainAt('pic') + 1).join(' '); + } + var origin; + grabURL("https://pixabay.com/en/photos/?q=" + of).then(function(res) { + origin = res.meta.finalUrl; + return parsePictures(res.body.toString(), amt); + }).then(function(pictures) { + if (amt == 1) { + send("Here is a picture: " + pictures); + } else { + send("Here are some pictures of " + of + ": \n" + pictures.join("\n") + " For more, check out " + origin); + } + }).catch(function(error) { + send(error); + }); +}) diff --git a/lib/commands/grabTweets.js b/lib/commands/grabTweets.js new file mode 100644 index 0000000..520bd1f --- /dev/null +++ b/lib/commands/grabTweets.js @@ -0,0 +1,24 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); +var parseTwitter = require('../helpers/parseTwitter.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + //Text must contain the word tweets and have a handler + return tokens.contain('tweets') && tokens.filter(function(f) { return f.indexOf("#") == 0 || f.indexOf("@") == 0}).length > 0; +} +module.exports = new Command("GetTweets", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var handles = tokens.filter(function(f) { return f.indexOf("#") == 0 || f.indexOf("@") == 0}); + handles.forEach(function(handle) { + var handleUrl = (handle[0] == "@")? "https://twitter.com/" + handle.substring(1): "https://twitter.com/hashtag/" + handle.substring(1); + return grabURL(handleUrl).then(function(res) { + return parseTwitter(res.body.toString()); + }).then(function(tweets) { + send("The latest tweets from " + handle + ":\n" + tweets.join("\n")); + }).catch(function(error) { + send(error); + }); + }); +}) diff --git a/lib/commands/grabUpdates.js b/lib/commands/grabUpdates.js new file mode 100644 index 0000000..1f1b9f6 --- /dev/null +++ b/lib/commands/grabUpdates.js @@ -0,0 +1,32 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); +var grabFeedURL = require('../helpers/grabFeedURL.js'); +var grabTitles = require('../helpers/grabTitles.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + //needs to have the word update and a URL + return tokens.superContain("update") && tokens.slice(tokens.superContainAt("update") + 1).filter(function(item) { + return item.contains('.'); + }).length > 0 +} +module.exports = new Command("Feed", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var urls = tokens.slice(tokens.superContainAt("update") + 1).filter(function(item) { + return item.contains('.'); + }); + send("One second and I'll go check!"); + urls.forEach(function(element) { + console.log(element); + return grabURL(element).then(function(res) { + return grabFeedURL(res.meta.finalUrl, res.body.toString()); + }).then(function(feedURL) { + return grabTitles(feedURL); + }).then(function(titles) { + send("The latest 5 articles from " + element + " :\n" + titles.join("\n")); + }).catch(function(error) { + send(error); + }) + }); +}) diff --git a/lib/commands/grabWikipedia.js b/lib/commands/grabWikipedia.js new file mode 100644 index 0000000..774a41c --- /dev/null +++ b/lib/commands/grabWikipedia.js @@ -0,0 +1,22 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); +var parseWikipedia = require('../helpers/parseWikipedia.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return tokens.superContain('what') && tokens.slice(tokens.superContainAt('what') + 1).join('+').length > 0; +} +module.exports = new Command("Wikipedia", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var query = tokens.slice(tokens.superContainAt('what') + 1).join('+'); + var origin; + grabURL("https://en.wikipedia.org/w/index.php?search=" + query).then(function(res) { + origin = res.meta.finalUrl; + return parseWikipedia(res.body.toString()); + }).then(function(definition) { + send(definition + "\nMore information at " + origin); + }).catch(function(error) { + send(error); + }); +}) diff --git a/lib/commands/grabWikipedia~ b/lib/commands/grabWikipedia~ new file mode 100644 index 0000000..e69de29 diff --git a/lib/commands/naturalSpeech.js b/lib/commands/naturalSpeech.js new file mode 100644 index 0000000..a95b92b --- /dev/null +++ b/lib/commands/naturalSpeech.js @@ -0,0 +1,125 @@ +/*** + Keep disabled until major rewrite has gone under way + + + +***/ + +var Command = require('./Command.js'); +var grabTokens = require('../helpers/grabTokens.js'); +var greetings = "hello|hi|hey|yo|morning|afternoon|evening"; +var wantSomething = /(\w+) (want|wants) ([^.&^\n]+)/i; +var happy = "yay|woo|yess|:D|:\\)" +var url = "link|url"; + +var condition = function(text) { + +} +module.exports = new Command("NaturalSpeech", condition, function(text, send, extra) { + var from = extra.from || ""; + var privateMessage = extra.privateMessage || false; + var tokens = grabTokens(text); + /* + Link to codeshare + */ + if (new RegExp(url).test(text) && tokens.contain("codeshare")) { + send("https://hidden-ocean-8102.herokuapp.com/"); + } + /* + :( -> It's okay. + */ + else if (tokens.contain(":(")) { + send("It's okay"); + } + /* + thank you -> You're welcome :) + */ + else if (tokens.superContain("thank") && (tokens.superContain("rozbot") || privateMessage)) { + send("You're welcome :)"); + } + /* + rozbot? -> Yes? + */ + else if (text.toLowerCase() === "rozbot?") { + send("Yes?"); + } + /* + Hugs rozbot -> Rozbot hugs [user] + */ + else if ((tokens.superContain("rozbot") || privateMessage) && tokens.superContain("hug")) { + send("Hugs " + from); + } + /* + Woo -> Yeah! + */ + else if (new RegExp(happy, 'i').test(text)) { + send("Yeah!"); + } + /* + [user] wants [item] -> Gives [user] [item] + */ + else if (wantSomething.test(text)) { + var responseTokens = wantSomething.exec(text); + var person = (responseTokens[1].toLowerCase() === "i")? from: responseTokens[1]; + send("Gives " + person + " " + responseTokens[3]); + } + /* + Who is Brandon? -> Brandon is the most awesome person in the world. + */ + else if ((tokens.contain("who") || tokens.contain("what")) && tokens.contain("brandon") && tokens.contain('rozek')) { + send("Brandon is the most awesome person in the world."); + } + /* + Who is Rozbot? -> A friendly neighborhood IRC bot + */ + else if (tokens.contain('who') || tokens.contain('what') && tokens.superContain("rozbot")&& !tokens.contain('radio')) { + send("A friendly neighborhood IRC bot"); + } + /* + What is Brandon up to? -> [Lists projects] + */ + else if ((tokens.contain("how") || tokens.contain("doing") || tokens.contain("up")) && tokens.contain("brandon")) { + send("I'm not sure. He may be doing a variety of things. For example:\n\ + working on his site (https://brandonrozek.com)\n\ + working on a writer's portfolio (https://toridayton.com\n\ + taking pictures for sentenceworthy.com\n\ + managing Math I/O\n\ + working on his apps codeshare or babbler.\n\ + running his radio (https://radio.zeropointshift.com)\n\ + Hopefully, he's not working on me. Cuz' I'm perfect."); + } + /* + How are you Rozbot? -> I'm awesome + */ + else if ((tokens.superContain("rozbot") || privateMessage) && tokens.contain("how")) { + send("I'm awesome."); + } + /* + Good morning Rozbot! -> Hello [user]! + */ + else if ((tokens.superContain('rozbot') || privateMessage) && new RegExp(greetings, 'i').test(text)) { + send("Hello " + from + "!"); + } + /* + What are you up to Rozbot? -> Currently working on my job + */ + else if ((tokens.superContain("rozbot") || privateMessage) && tokens.superContain("what") && (tokens.contain("up") || tokens.contain("doing"))) { + send("Currently working on my job"); + } + /* + What is your job Rozbot? -> Chilling on IRC doing whatever Brandon programs me to do + */ + else if (tokens.superContain("rozbot") && tokens.superContain("what") && tokens.contain("job")) { + send("Chilling on IRC doing whatever Brandon programs me to do."); + } + /* + Start Greenteam meeting -> [instructions for mumble] + */ + else if (tokens.contain("start") && tokens.contain("greenteam") && tokens.contain("meeting")) { + send("Everyone please connect to mumble\n\ + Host: zeropointshift.com\n\ + Leave port at default\n\ + Give yourself a sensible username\n\ + Label it whatever you want"); + } +}) diff --git a/lib/commands/onTheRadio.js b/lib/commands/onTheRadio.js new file mode 100644 index 0000000..c7c580e --- /dev/null +++ b/lib/commands/onTheRadio.js @@ -0,0 +1,15 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return tokens.superContain('what') && tokens.contain('radio'); +} +module.exports = new Command("OnTheRadio", condition, function(text, send, userData) { + grabURL('https://meldicradio.com/json') + .then(function(response) { return response.body.toString(); }) + .then(function(text) { return JSON.parse(text); }) + .then(function(json) { send("\"" + json.artist + " - " + json.title + "\" is currently playing on meldicradio.com"); }) + .catch(function(error) { send(error); }); +}); diff --git a/lib/commands/queryDuckDuckGo.js b/lib/commands/queryDuckDuckGo.js new file mode 100644 index 0000000..0ed04b4 --- /dev/null +++ b/lib/commands/queryDuckDuckGo.js @@ -0,0 +1,25 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); + +var condition = function(text) { + //This is a catch all + return true; +} +module.exports = new Command("DuckDuckGoQuery", condition, function(text, send, userData) { + grabURL("https://duckduckgo.com/?q=" + text.split(' ').join('+') + "&format=json&no_redirect=1&t=rozbot") + .then(function(response) { return response.body.toString(); }) + .then(function(text) { return JSON.parse(text); }) + .then(function(json) { + if (json.Answer != "") { + send(json.Answer + "\nResults from DuckDuckGo"); + return true; + } + if (json.AbstractText != "") { + send(json.AbstractText + "\nMore at " + json.AbstractURL); + return true; + } + if (json.Redirect != "") { + send(json.Redirect); + } + }).catch(function(error) { console.log(error); }); +}) diff --git a/lib/commands/search.js b/lib/commands/search.js new file mode 100644 index 0000000..b7f626f --- /dev/null +++ b/lib/commands/search.js @@ -0,0 +1,19 @@ +var Command = require('./Command.js'); +var grabURL = require('../helpers/grabURL.js'); +var parseDuckDuckGo = require('../helpers/parseDuckDuckGo.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return tokens.contain('search'); +} + +module.exports = new Command("Search", condition, function(text, send, userData) { + var tokens = grabTokens(text); + var search = text.substring(text.indexOf('search') + 6); + grabURL("https://duckduckgo.com/html/?q=" + search.split(' ').join('+') + '&t=rozbot') + .then(function(response) { return response.body.toString(); }) + .then(function(text) { return parseDuckDuckGo(text) }) + .then(function(results) { send("Results for \"" + search + "\":\n" + results.join("\n") + "\nSearch Provided by DuckDuckGo"); }) + .catch(function(error) { send(error) }); +}); diff --git a/lib/commands/timer.js b/lib/commands/timer.js new file mode 100644 index 0000000..c3086dd --- /dev/null +++ b/lib/commands/timer.js @@ -0,0 +1,40 @@ +var Command = require('./Command.js'); +var grabTokens = require('../helpers/grabTokens.js'); + +var condition = function(text) { + var tokens = grabTokens(text); + return tokens.superContain('timer') && (tokens.superContain('hour') || tokens.superContain('min') || tokens.superContain('sec')) +} + +module.exports = new Command('Timer', condition, function(text, send, userData) { + var tokens = grabTokens(text); + var seconds = 0; + if (tokens.superContain('hour')) { + var numHours = Number(tokens[tokens.superContainAt('hour') - 1]); + if (numHours || numHours == 0) { + seconds += numHours * 3600; + } else { + seconds += 3600; + } + } + if (tokens.superContain('min')) { + var numMins = Number(tokens[tokens.superContainAt('min') - 1]); + if (numMins|| numMins == 0) { + seconds += numMins * 60; + } else { + seconds += 60; + } + } + if (tokens.superContain('sec')) { + var numSeconds = Number(tokens[tokens.superContainAt('sec') - 1]); + if (numSeconds || numSeconds == 0) { + seconds += numSeconds; + } else { + seconds += 1; + } + } + send("Timer set for " + seconds + " seconds"); + setTimeout(function() { + send("Times up!"); + }, seconds * 1000); +}); diff --git a/lib/helpers/additionalPrototypes.js b/lib/helpers/additionalPrototypes.js new file mode 100644 index 0000000..884636a --- /dev/null +++ b/lib/helpers/additionalPrototypes.js @@ -0,0 +1,39 @@ +String.prototype.contains = function(str) { + return this.indexOf(str) != -1; +} +Array.prototype.contain = function(item) { + return this.indexOf(item) != -1; +} +Array.prototype.superContain = function(item) { + for (var i = 0; i < this.length; i++) { + if (this[i].indexOf(item) != -1) { + return true; + } + } + return false; +} +Array.prototype.superContainAt = function(item) { + for (var i = 0; i < this.length; i++) { + if (this[i].indexOf(item) != -1) { + return i; + } + } + return false; +} +String.prototype.from = function(str) { + return this.substring(this.indexOf(str) + str.length); +} +String.prototype.remove = function(str) { + var index = this.indexOf(str); + return this.substring(0, index) + this.substring(index + str.length); +} +String.prototype.removeAll = function(str) { + var newString = this.substring(0); + for (var i = 0; i < arguments.length; i++) { + while (newString.contains(arguments[i])) { + newString = newString.remove(arguments[i]); + } + } + return newString; +} + diff --git a/lib/helpers/grabFeedURL.js b/lib/helpers/grabFeedURL.js new file mode 100644 index 0000000..b9ee2b0 --- /dev/null +++ b/lib/helpers/grabFeedURL.js @@ -0,0 +1,27 @@ +var promise = require('promise-polyfill'); +var cheerio = require('../promise/cheerio.js'); +var url = require('url'); + +module.exports = function(origin, html) { + return new promise(function(resolve, reject) { + cheerio(html).then(function($) { + var link = $('link[rel=alternate]').attr('href'); + if (link) { + var feedURL = url.parse(link, true, true); + if (feedURL.hostname === null) { + feedURL = url.parse(url.resolve(origin, feedURL.href), true, true) + } + if (feedURL.protocol === null) { + feedURL = url.parse(url.resolve('http:', feedURL.href), true, true); + } + resolve(feedURL.href); + } + else { + reject("I couldn't find the link >.<"); + } + }).catch(function(error) { + console.log(error); + reject("I wasn't able to find the link :/"); + }) + }) +} diff --git a/lib/helpers/grabTitles.js b/lib/helpers/grabTitles.js new file mode 100644 index 0000000..9856c15 --- /dev/null +++ b/lib/helpers/grabTitles.js @@ -0,0 +1,22 @@ +var promise = require('promise-polyfill'); +var feed = require('../promise/feed.js'); + +module.exports = function(feedURL) { + return new promise(function(resolve, reject) { + feed(feedURL).then(function(articles) { + var titles = []; + var length = Math.min(5, articles.length); + for (var i = 0; i < length; i++) { + titles.push(articles[i].title + " " + articles[i].link); + } + if (titles.length > 0) { + resolve(titles); + } else { + reject("There are no updates"); + } + }).catch(function(error) { + console.log(error); + reject("I couldn't parse the feed :("); + }) + }); +} diff --git a/lib/helpers/grabTokens.js b/lib/helpers/grabTokens.js new file mode 100644 index 0000000..6a8bc04 --- /dev/null +++ b/lib/helpers/grabTokens.js @@ -0,0 +1,10 @@ +var stopWords = require('./stopWords.js'); + +module.exports = function(text) { + text = text || ""; + return text.split(' ').filter(function(item) { + return stopWords.indexOf(item) === -1; + }).map(function(item) { + return (item.contains("http")) ? item: item.removeAll("?", ".", ",", "!").toLowerCase(); + }); +} diff --git a/lib/helpers/grabURL.js b/lib/helpers/grabURL.js new file mode 100644 index 0000000..e9c9058 --- /dev/null +++ b/lib/helpers/grabURL.js @@ -0,0 +1,19 @@ +var promise = require('promise-polyfill'); +var url = require('url'); +var fetch = require('../promise/fetch.js'); + +module.exports = function(websiteURL) { + return new promise(function(resolve, reject) { + var weburl = url.parse(websiteURL, true, true); + if (weburl.protocol === null) { + weburl = url.parse('http://' + websiteURL); + } + websiteURL = weburl.href; + fetch(websiteURL).then(function(res) { + resolve({meta: res.meta, body: res.body}); + }).catch(function(error) { + console.log(error); + reject("Sorry, I wasn't able to grab the page."); + }); + }); +} diff --git a/lib/helpers/lastModified.js b/lib/helpers/lastModified.js new file mode 100644 index 0000000..14e9dee --- /dev/null +++ b/lib/helpers/lastModified.js @@ -0,0 +1,7 @@ +var fsStat = require('../promise/fs-stat.js'); + +module.exports = function(file) { + return fsStat(file).then(function(stats) { + return stats.mtime; + }) +} diff --git a/lib/helpers/parseDuckDuckGo.js b/lib/helpers/parseDuckDuckGo.js new file mode 100644 index 0000000..f97ca08 --- /dev/null +++ b/lib/helpers/parseDuckDuckGo.js @@ -0,0 +1,22 @@ +var cheerio = require('../promise/cheerio.js'); +var promise = require('promise-polyfill'); + +module.exports = function(html) { + return new promise(function(resolve, reject) { + cheerio(html).then(function($) { + var results = [] + var resultsContainer = $('.result__url'); + var resultsToShow = Math.min(5, resultsContainer.length - 1); + for (var i = 0; i < resultsToShow; i++) { + results.push(resultsContainer.eq(i).text()) + } + if (results.length > 0) { + resolve(results); + } else { + reject("No results were found. [DuckDuckGo]") + } + }).catch(function(error) { + reject(error); + }); + }); +} diff --git a/lib/helpers/parsePictures.js b/lib/helpers/parsePictures.js new file mode 100644 index 0000000..146e487 --- /dev/null +++ b/lib/helpers/parsePictures.js @@ -0,0 +1,28 @@ +var cheeriop = require('../promise/cheerio.js'); +var promise = require('promise-polyfill'); + +module.exports = function(html, amt) { + return new promise(function(resolve, reject) { + cheeriop(html).then(function($) { + var photos = []; + var pics = $('#photo_grid .item'); + var picsToShow = Math.min(amt, pics.length); + if (picsToShow == 1) { + resolve("https://pixabay.com" + $('#photo_grid .item').children().find('img').eq(0).attr('src')); + return; + } + + for (var i = 0; i < picsToShow; i++) { + photos.push("https://pixabay.com" + $('#photo_grid .item').children().find('img').eq(i).attr('src')); + } + if (photos.length > 0) { + resolve(photos); + } else { + reject("I looked around everywhere and couldn't find any"); + } + }).catch(function(error) { + console.log(error); + reject(error); + }) + }); +} diff --git a/lib/helpers/parseTwitter.js b/lib/helpers/parseTwitter.js new file mode 100644 index 0000000..b0d9697 --- /dev/null +++ b/lib/helpers/parseTwitter.js @@ -0,0 +1,22 @@ +var cheerio = require('../promise/cheerio.js'); +var promise = require('promise-polyfill'); + +module.exports = function(html) { + return new promise(function(resolve, reject) { + cheerio(html).then(function($) { + var tweets = []; + var tweetsContainer = $('.tweet'); + var tweetsToShow = Math.min(5, tweetsContainer.length - 1); + for (var i = 0; i < tweetsToShow; i++) { + tweets.push($('.tweet').children().find('.fullname').eq(i).text() + " (" + $('.tweet').children().find('.username').eq(i).text() + ")" + ": " + $('.tweet').children().find('.tweet-text').eq(i).text()) + } + if (tweets.length > 0) { + resolve(tweets); + } else { + reject("No tweets were found.") + } + }).catch(function(error) { + reject(error); + }); + }); +} diff --git a/lib/helpers/parseWikipedia.js b/lib/helpers/parseWikipedia.js new file mode 100644 index 0000000..3dc2408 --- /dev/null +++ b/lib/helpers/parseWikipedia.js @@ -0,0 +1,17 @@ +var cheeriop = require('../promise/cheerio.js'); +var promise = require('promise-polyfill'); + +module.exports = function(html) { + return new promise(function(resolve, reject) { + cheeriop(html).then(function($) { + if ($('.mw-search-nonefound').length > 0) { + reject(""); + } else { + resolve($('#mw-content-text').find('p').first().text()); + } + }).catch(function(error) { + console.log(error); + reject(""); + }) + }); +} diff --git a/lib/helpers/parseWordnik.js b/lib/helpers/parseWordnik.js new file mode 100644 index 0000000..0098e20 --- /dev/null +++ b/lib/helpers/parseWordnik.js @@ -0,0 +1,18 @@ +var cheeriop = require('../promise/cheerio.js'); +var promise = require('promise-polyfill'); + +module.exports = function(html) { + return new promise(function(resolve, reject) { + cheeriop(html).then(function($) { + var response = $('#define').children().find('ul').children().first().text() + if (response) { + resolve(response); + } else { + reject(""); + } + }).catch(function(error) { + console.log(error); + reject(""); + }) + }); +} diff --git a/lib/helpers/stopWords.js b/lib/helpers/stopWords.js new file mode 100644 index 0000000..914d18d --- /dev/null +++ b/lib/helpers/stopWords.js @@ -0,0 +1 @@ +module.exports = ["a", "about", "above", "across", "after", "afterwards", "again", "against", "all", "almost", "alone", "along", "already", "also", "although", "always", "am", "among", "amongst", "amoungst", "amount", "an", "and", "another", "any", "anyhow", "anyone", "anything", "anyway", "anywhere", "around", "are", "as", "at", "back", "be", "became", "because", "become", "becomes", "becoming", "been", "before", "beforehand", "behind", "being", "below", "beside", "besides", "between", "beyond", "bill", "both", "bottom", "but", "by", "call", "can", "cannot", "cant", "co", "con", "could", "couldnt", "cry", "de", "describe", "detail", "do", "done", "down", "due", "during", "each", "eg", "eight", "either", "eleven", "else", "elsewhere", "empty", "enough", "etc", "even", "ever", "every", "everyone", "everything", "everywhere", "except", "few", "fifteen", "fify", "fill", "find", "fire", "first", "five", "for", "former", "formerly", "forty", "found", "four", "from", "front", "full", "further", "get", "give", "go", "had", "has", "hasnt", "have", "he", "hence", "her", "here", "hereafter", "hereby", "herein", "hereupon", "hers", "herself", "him", "himself", "his", "however", "hundred", "ie", "if", "in", "inc", "indeed", "interest", "into", "is", "it", "its", "itself", "keep", "last", "latter", "latterly", "least", "less", "ltd", "made", "many", "may", "me", "meanwhile", "might", "mill", "mine", "more", "moreover", "most", "mostly", "move", "much", "must", "my", "myself", "name", "namely", "neither", "never", "nevertheless", "next", "nine", "no", "nobody", "none", "noone", "nor", "not", "nothing", "now", "nowhere", "of", "wff", "often", "on", "once", "one", "only", "onto", "or", "other", "others", "otherwise", "our", "ours", "ourselves", "out", "over", "own", "part", "per", "perhaps", "please", "put", "rather", "re", "same", "see", "seem", "seemed", "seeming", "seems", "serious", "several", "she", "should", "show", "side", "since", "sincere", "six", "sixty", "so", "some", "somehow", "someone", "something", "sometime", "sometimes", "somewhere", "still", "such", "system", "take", "ten", "than", "that", "the", "their", "them", "themselves", "then", "thence", "there", "thereafter", "thereby", "therefore", "therein", "thereupon", "these", "they", "thickv", "thin", "third", "this", "those", "though", "three", "through", "throughout", "thru", "thus", "to", "together", "too", "top", "toward", "towards", "twelve", "twenty", "two", "un", "under", "until", "up", "upon", "us", "very", "via", "was", "we", "well", "were", "whatever", "whence", "whenever", "whereafter", "whereas", "whereby", "wherein", "whereupon", "wherever", "whether", "which", "while", "whither", "whoever", "whole", "whom", "whose", "will", "with", "within", "without", "would", "yet", "your", "yours", "yourself", "yourselves"]; diff --git a/lib/promise/appendFile.js b/lib/promise/appendFile.js new file mode 100644 index 0000000..4319d43 --- /dev/null +++ b/lib/promise/appendFile.js @@ -0,0 +1,13 @@ +var fs = require('fs'); +var promise = require('promise-polyfill'); + +module.exports = function(file, data) { + return new promise(function(resolve, reject) { + fs.appendFile(file, data + "\n", function(error) { + if (error) { + console.log(error); + //reject(error); + } + }); + }); +} diff --git a/lib/promise/cheerio.js b/lib/promise/cheerio.js new file mode 100644 index 0000000..788df1a --- /dev/null +++ b/lib/promise/cheerio.js @@ -0,0 +1,7 @@ +var cheerio = require('cheerio').load; +var promise = require('promise-polyfill'); +module.exports = function(html) { + return new promise(function(resolve, reject) { + resolve(cheerio(html)); + }); +} diff --git a/lib/promise/feed.js b/lib/promise/feed.js new file mode 100644 index 0000000..7fb5fcd --- /dev/null +++ b/lib/promise/feed.js @@ -0,0 +1,14 @@ +var feed = require('feed-read'); +var promise = require('promise-polyfill'); + +module.exports = function(link) { + return new promise(function(resolve, reject) { + feed(link, function(error, articles) { + if (error) { + reject(error); + } else { + resolve(articles); + } + }); + }); +} diff --git a/lib/promise/fetch.js b/lib/promise/fetch.js new file mode 100644 index 0000000..c1141b7 --- /dev/null +++ b/lib/promise/fetch.js @@ -0,0 +1,13 @@ +var fetch = require('fetch').fetchUrl; +var promise = require('promise-polyfill'); +module.exports = function(link, options) { + return new promise(function(resolve, reject) { + fetch(link, options, function(error, meta, body) { + if (error) { + reject(error); + } else { + resolve({meta: meta, body: body}); + } + }); + }); +} diff --git a/lib/promise/fs-stat.js b/lib/promise/fs-stat.js new file mode 100644 index 0000000..2363455 --- /dev/null +++ b/lib/promise/fs-stat.js @@ -0,0 +1,8 @@ +var fs = require('fs'); +var promise = require('promise-polyfill'); + +module.exports = function(path) { + return new promise(function(resolve, reject) { + resolve(fs.stat(path)); + }); +} diff --git a/lib/promise/math-eval.js b/lib/promise/math-eval.js new file mode 100644 index 0000000..d2856ce --- /dev/null +++ b/lib/promise/math-eval.js @@ -0,0 +1,8 @@ +var algebrite = require('algebrite'); +var promise = require('promise-polyfill'); + +module.exports = function(expression) { + return new promise(function(resolve, reject) { + resolve(algebrite.eval(expression).toString()); + }); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..da74c0f --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "rozbot", + "version": "0.1.0", + "description": "An IRC bot created by Brandon Rozek", + "main": "bot.js", + "dependencies": { + "cheerio": "^0.19.0", + "feed-read": "^0.0.1", + "fetch": "^1.0.0", + "algebrite": "^0.2.20", + "promise-polyfill": "^2.1.4" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Brandon Rozek" +}