diff --git a/.gitignore b/.gitignore index 9477fc3..5364073 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *-profile.js node_modules/ +config.json \ No newline at end of file diff --git a/README.md b/README.md index e573678..b5b2ed0 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,119 @@ -oftn-bot +Ella ======== -This is the repository for both of the IRC bots oftn-bot (in #oftn on freenode) and ecmabot (in ##javascript on freenode). This was originally created as a replacement for v8bot (http://github.com/eisd/v8bot). Over time, it changed to include a full-featured IRC bot library. +Why Ella?, whats wrong with Natasha? +------ +Time has come for us to re-think what our bot of the future is going to look like and how it will suit our needs. +Natasha (who was once called Olga) has done a great job over the years but we wish to change the infrastructure and how people can contribute. +Natasha has stored a whole heap of information (I think from 2011 onwards, maybe earlier) but we want to move logs and stats to the main database we already have running. -Features --------- +Here are our reasons for building a new bot: -* Manages bot commands with an easy API -* Support for "intents" which is when you append "@ user" after a bot command, so the bot can reply to that person -* Context object provides information about bot command invocations, including: - * Who invoked the command - * Who was it "intended" for - * Was this invoked in the channel or in a private message? -* Can listen for regular expression matches -* Control logging amount to standard out -* Inherits from Node.js's built-in EventEmitter -* Manages user lists and recognizes mode changes -* Don't worry about flooding the channel with built-in support for truncation -* Default option is to strip control codes and colors -* Each channel and user is represented as a unique JavaScript object with extra information, e.g. channel topic, or user op status -* Includes extra optional libraries: - * FactoidServ: Manages a list of factoids which are saved and loaded to disk automatically - * FeelingLucky: Performs a quick Google "I'm Feeling Lucky" search - * JSONSaver: Saves and loads arbitrary JavaScript objects to disk automatically (used by FactoidServ) - * Sandbox: Runs JavaScript in a seperate process to allow for users to run code. (Uses v8, but a compiled SpiderMonkey "shovel" is available that uses js185 shared libraries) +* We want to seperate data from the bot, we now have a hashweb API (currently being used by [Hashweb Stats](http://stats.hashweb.org). +* Leaving things like logs and statistics to the main database means we can let the bot to make API calls and data comes from the same place. This also means we can open it up to the public (as its not directly communicating with a database) +* Development on Natasha is quite slow, as she is closed off with only 1 person maintaining her. We don't want a single point of entry. +* We fancied using a new platform, the old bot was wrote in Python (a fork of limnoria) and does a great job. But if users are going to contribute we want a low barrier to entry, and thus decided Node JS was the best solution for that. +* I want to learn Node JS.... +* By Moving to Github means we aremore open and users can view and learn about how the new bot will work. -API ---- +### !commands +Retrieves a list of current commands. +Usage: `!commands` -The underlying IRCBot library has methods which make it easy to add functionality. + !commands + eboyjr: Valid commands are: !commands, !ecma, !find, !forget, !g, !google, !help, !learn, !mdc, !mdn, !re -### Bot(profile) -@profile: An array of objects representing each server to connect to. +### !ecma +Searches the ECMA-262 specification table of contents. Links to the section as found in http://es5.github.com/ +Usage: `!ecma ` -This is the main constructor for the bot. It is suggested that you inherit from this object when creating your bot, but you don't have to. + !ecma null value + eboyjr: Found: 4.3.11 null value + + +### !calc +Do a calculation using Wolfram Alpha +Usage: `!calc 27 hex to dec` -A profile is an array of objects. Each object has the properties: + !calc Prime Minister of England + eboyjr: David Cameron -* host: The domain name or IP of an IRC server to connect to. Default: "localhost" -* port: A port number. Default: 6667. -* nick: A string nick name to try to connect as. Default: "guest" -* password: The password used to connect (This is not NickServ). Default: null -* user: Your IRC username -* real: Your 'real name' on IRC -* channels: An array of channel names to connect to. (e.g. ["#stuff", "##mychannel", "#yomama"]) +### !find +Performs a search of a factoid in the database. +Usage: `!find ` + !find frame + eboyjr: No factoid/command named `frame`. Did you mean: iframe, or cross-domain? See !commands for a list of commands. -### bot.init() -Goes through each server in the profile and begins connecting and registering event listeners. +### !forget +Removes a factoid from the database. +Usage: `!forget ` -### bot.register_listener(regex, callback) -Adds a regular expression listeners to incoming traffic on all servers. When the messages match, callback is called with the arguments: +### !g +Returns the first Google result for the query. +Usage: `!g ` -* context: A context object -* text: The full message -* 1st subpattern -* 2nd subpattern -* ... + !g v8 javascript engine + eboyjr: v8 - V8 JavaScript Engine - Google Project Hosting -### bot.register_command(command, callback, options) -Adds a command. +### !google +Returns a link to a Google search page of the search term. +Usage: `!google ` -* command: A string value for the command -* callback: A function to call when the command is run -* options: An object with the keys: allow_intentions and hidden. + !google opencourseware computational complexity + eboyjr: Google search: "opencourseware computational complexity" -When the command is called, the callback is called with the arguments: +### !ddg +Searches Duck Duck Go. +Usage: `!ddg ` -* context: A context object -* text: The command arguments + !ddg opencourseware computational complexity + +### !help +Gives help for a specific command. +Usage: `!help ` + !help help + eboyjr: No help for `help` -Additional Documentation ------------------------- +### !learn +Adds a factoid to the Ella. e +Usage: `!learn = ` +Usage: `!learn alias = ` +Usage: `!learn =~ s///` -This bot AND/OR bot library is still being developed, but those are some of the basic commands. Look at your-bot-here.js for a simple example of an IRC bot using this API, or ecmabot.js for a more complex and featured example. +### !mdn +Searches the Mozilla Developer Network. +Usage: `!mdn ` + !mdn bitwise operators + eboyjr: Bitwise Operators - MDN Docs + +### !re +Performs a regular expression match. +Usage: `!re //` + + !re Hannah Hannah Bo Banana, Fe Fi Fo Fana /.[an]+/g + eboyjr: Matches: 'Hanna', 'Hanna', 'Banana', 'Fana' + +### !translate +Transtes a word from 1 language to another +Usage: `!translate [language] to [language] ` + + !translate french to english bonjour + Good morning + + +## Factoids + +The factoid system in bot is designed to store simple key/value pairs. Accessing a factoid from the database is as simple as: + + !help + eboyjr: In order to get help, paste the relevant portions JavaScript in a pastebin (see !paste), and tell us 1) what you want to happen, 2) what is actually happening, and 3) any error messages you find (see !debug). + +You can direct the responses of your command with the `@` character, followed by a nick. + + HAI GUYS ... uh havin a bit of trubble with this script... i get TypeError: document.crateElenemt is not a function how do i fixx this?? + !spelling @ phpman3000 + phpman3000: Spelling and capitalization are important in programming, unless you are using PHP. \ No newline at end of file diff --git a/bootstrap.js b/bootstrap.js new file mode 100644 index 0000000..df12cd0 --- /dev/null +++ b/bootstrap.js @@ -0,0 +1,374 @@ +var file = require('fs'); +var path = require('path'); +var util = require("util"); +var http = require("http"); +var fs = require("fs"); + +var config = JSON.parse(fs.readFileSync('config.json', 'utf8')); + +var request = require("request"); +var cheerio = require('cheerio'); +var wolfram = require('wolfram-alpha').createClient(config.wolframAPI); + +var Sandbox = require("./lib/sandbox"); +var FactoidServer = require("./lib/factoidserv"); +var FeelingLucky = require("./lib/feelinglucky"); +var CanIUseServer = require("./lib/caniuse"); +var hashwebAPI = require("./hashweb"); + + +var Bot = require("./lib/irc"); +var Shared = require("./shared"); +// config.json will be a hidden (gitignored) file for obvious reasons.... + +var ddgAPi = "https://duckduckgo.com/?q=british%20broadcasting%20corporation&format=json"; +var urlRegex = new RegExp("^(http[s]?:\\/\\/(www\\.)?|ftp:\\/\\/(www\\.)?|www\\.){1}([0-9A-Za-z-\\.@:%_\+~#=]+)+((\\.[a-zA-Z]{2,3})+)(/(.)*)?(\\?(.)*)?"); + + +var JSBot = function(profile) { + this.sandbox = new Sandbox(path.join(__dirname, "ecmabot-utils.js")); + this.factoids = new FactoidServer(path.join(__dirname, "ecmabot-factoids.json")); + this.caniuse_server = new CanIUseServer; + this.executeRegex = /^((?:sm|v8|js|>>?|\|)>)([^>].*)+/; + + Bot.call(this, profile); + this.set_log_level(this.LOG_ALL); + this.set_trigger("!"); // Exclamation +}; + + +util.inherits(JSBot, Bot); + + +JSBot.prototype.init = function() { + var that = this; + Bot.prototype.init.call(this); + + this.register_listener(this.executeRegex, Shared.execute_js); + + //this.register_listener(/^(\S+)(\+\+|--);?$/, this.do_beers); + + this.register_command("g", Shared.google, { + help: "Run this command with a search query to return the first Google result. Usage: !g kitten images"}); + + this.register_command("google", this.google, { + help: "Returns a link to a Google search page of the search term. Usage: !google opencourseware computational complexity"}); + + this.register_command("mdn", this.mdn, { + help: "Search the Mozilla Developer Network. Usage: !mdn bitwise operators"}); + this.register_command("mdc", "mdn"); + + this.register_command("ecma", this.ecma, { + help: "Lookup a section from the ECMAScript spec. Usage: !ecma null value"}); + + this.register_command("re", this.re, { + help: "Usage: !re Your text here /expression/gi || FLAGS: (g: global match, i: ignore case)"}); + + this.register_command("caniuse", this.caniuse, { + help: "Search the caniuse.com database. Usage: !caniuse webgl"}); + this.register_command("ciu", "caniuse"); + + this.register_command("find", Shared.find); + + this.register_command("help", this.help); + + this.register_command("auth", Shared.reauthenticate, { + allow_intentions: false, + help: "Attempt to re-authenticate with NickServ."}); + + this.register_command("learn", Shared.learn, { + allow_intentions: false, + help: "Add factoid to bot. Usage: !learn ( [alias] foo = bar | foo =~ s/expression/replace/gi )"}); + + this.register_command("forget", Shared.forget, { + allow_intentions: false, + help: "Remove factoid from bot. Usage: !forget foo"}); + + this.register_command("commands", Shared.commands); + + this.register_command("ping", this.ping); + + this.register_command("dataja", this.dataja); + + this.register_command("seen", hashwebAPI.getLastSeen); + + this.register_command("fseen", hashwebAPI.getFirstSeen); + + this.register_command("ops", hashwebAPI.ops); + + this.register_command("baninfo", hashwebAPI.modifyBansObject); + + this.register_command("updatebans", hashwebAPI.updateBansList); + + this.register_command("ddg", this.ddg); + + this.register_command("beers", this.do_beers); + + this.register_command("calc", this.calc, { + help: "Wolfram Alpha calculations. Usage !calc [query]" + }); + + this.on('command_not_found', this.command_not_found); + + this.on("pm", function(context, text) { + channel = text.match(/^(\#[a-zA-Z0-9-]+)/); + for (var i=0;i < config.users.length ; i++) { + /* Check the config if its a valid user */ + if (config.users[i].host === context.host && channel) { + channel = context.client.get_channel(channel); + text = text.replace(/^(\#[a-zA-Z0-9-]+) /, ""); + channel.send(text.trim()); + } + } + }); + + /* scan messages for links and print titles */ + this.on("message", function(context, text, msg) { + channel = context.client.get_channel(context.name); + /* request only deals with urls which begin with http(s) */ + if (msg.match(urlRegex) && msg.match(/^http[s]?/)) { + /* Make request to URl and get the title */ + var url = msg.match(urlRegex)[0]; + request(url, function(error, response, body) { + if (!error && response.statusCode == 200) { + $ = cheerio.load(body); + var title = $("title").text().trim(); + if (title) channel.send("Title: " + title); // Don't bother showing a title if its empty + } + }); + + }; + }); + + this.load_ecma_ref(); + +}; + + +JSBot.prototype.ddg = function(context, text) { + text = encodeURIComponent(text); + request("https://duckduckgo.com/?q="+ text +"&format=json", function(error, response, body) { + if (!error && response.statusCode == 200) { + try { + body = JSON.parse(body); + if (body.Abstract && body.Results && body.Results[0].FirstURL) { + context.channel.send_reply(context.sender, body.Abstract + " : " + body.Results[0].FirstURL); + } + else if (body.Abstract) { + context.channel.send_reply(context.sender, body.Abstract); + } + else if (body.AbstractURL) { + context.channel.send_reply(context.sender, body.AbstractURL); + } + else { + context.channel.send_reply(context.sender, " No results..sorry"); + } + } catch(e) { + context.channel.send_reply(context.sender, " Oops looks like I couldn't parse the response from DDG") + } + } else { + context.channel.send_reply(context.sender, " Oops looks like Duck Duck Go gave a bad response :(") + } + }); +} + + +JSBot.prototype.calc = function(context, text) { + wolfram.query(text, function (err, result) { + if (err) throw err; + if (result.length >= 1 && ("subpods" in result[1])) { + context.channel.send_reply(context.sender, result[1].subpods[0].text); + } else { + context.channel.send_reply(context.sender, "Sorry, couldn't find a result for that :("); + } + }); +}; + +JSBot.prototype.google = function(context, text) { + + if (!text) { + context.channel.send_reply (context.sender, this.get_command_help("google")); + return; + } + + context.channel.send_reply (context.intent, "Google search: \""+text+"\" "); +}; + +JSBot.prototype.there_is_no_try = function(context, text) { + var hours = 1000*60*60; + var now = +new Date(); + + if (now > arguments.callee.last_invocation + 3*hours || + typeof arguments.callee.last_invocation === "undefined") { + + context.channel.send_reply(context.sender, "Do or do not; there is no try. --Yoda"); + arguments.callee.last_invocation = now; + + } +}; + +JSBot.prototype.ping = function(cx, text) { + cx.channel.send_reply (cx.sender, "Pong!"); +}; + + +JSBot.prototype.do_beers = function(context, text, nick, operation) { + /** + * /(\S+)\s*(?:(\+\+|--)|=\s*(?:\1)\s*(\+|-)\s*1);?/ + * TODO: More advanced beer management + **/ + if (operation === "++") { + if (nick.toLowerCase() !== "c") { + context.channel.send_reply(context.sender, "Even if " + nick + + " deserves any beer, I don't have any to spare."); + } else { + context.channel.send_reply(context.sender, "C doesn't deserve beer."); + } + } else { + context.channel.send_action( + "steals a beer a from " + nick + ", since we're taking 'em."); + } +}; + + +JSBot.prototype.re = function(context, msg) { + // Okay first we need to check for the regex literal at the end + // The regular expression to match a real js regex literal + // is too long, so we need to use a simpler one. + var regexmatches, regexliteral = /\/((?:[^\\\/]|\\.)*)\/([gi]*)$/; + + if (regexmatches = msg.match(regexliteral)) { + try { + var regexpobj = new RegExp(regexmatches[1], regexmatches[2]); + } catch (e) { + /* We have an invalid regular expression */ + context.channel.send_reply(context.sender, e.message); + return; + } + + var texttomatch = msg.slice(0, -regexmatches[0].length).trim(); + var result = texttomatch.match(regexpobj); + if (result === null) { + context.channel.send_reply(context.intent, "No matches found."); + return; + } + + var reply = []; + for (var i = 0, len = result.length; i < len; i++) { + reply.push(typeof result[i] !== "undefined" ? + "'"+result[i]+"'" : + "[undefined]"); + } + + context.channel.send_reply(context.intent, "Matches: "+reply.join(", "), {truncate: true}); + } else { + context.channel.send_reply(context.sender, this.get_command_help("re")); + } +}; + + + +JSBot.prototype.help = function(context, text) { + + try { + if (!text) { + return this.command_not_found (context, "help"); + } + + context.channel.send_reply(context.intent, this.get_command_help(text)); + } catch(e) { + context.channel.send_reply(context.sender, e); + } +}; + + +JSBot.prototype.mdn = function(context, text, command) { + if (!text) { + return Shared.findPlus.call(this, context, command); + } + + Shared.google (context, "site:developer.mozilla.org "+text); +}; + + +JSBot.prototype.command_not_found = function(context, text) { + Shared.findPlus.call(this, context, text, !context.priv); +}; + +// JSON.stringify([].slice.call(document.querySelectorAll('#toc-full a')).map(function(v) {return {title: v.firstChild.textContent, id: v.href.replace(/.+#/, '')};})); +// Use that to generate the required JSON from es5.github.io with Firefox + +JSBot.prototype.ecma = function(context, text) { + try { + + if (typeof this.ecma_ref === "undefined") { + context.channel.send_reply(context.sender, "The ECMA-262 reference is not loaded."); + return; + } + + text = text.toLowerCase(); + var ref = this.ecma_ref, ch = text.charCodeAt(0); + + // If text begins with a number, the search must match at the beginning of the string + var muststart = ch >= 48 && ch <= 57; + + for (var i = 0, len = ref.length; i < len; i++) { + var item = ref[i], title = item.title.toLowerCase(); + if (muststart ? title.substring(0, text.length) === text : ~title.indexOf(text)) { + context.channel.send_reply(context.intent, + "Found: " + item.title + " "); + return; + } + } + + throw new Error("Could not find text '"+text+"' in the ECMAScript 5.1 Table of Contents."); + + } catch (e) { context.channel.send_reply(context.sender, e); } +}; + + +JSBot.prototype.load_ecma_ref = function() { + var filename = path.join(__dirname, "ecmabot-reference.json"); + util.puts("Loading ECMA-262 reference..."); + var bot = this; + file.readFile(filename, function (err, data) { + if (err) util.puts(util.inspect(err)); + try { + bot.ecma_ref = JSON.parse(data); + } catch (e) { + util.puts("ECMA-262 Error: "+e.name+": "+e.message); + } + }); + if (typeof this.ecma_ref_watching === "undefined") { + this.ecma_ref_watching = true; + file.watchFile(filename, function (curr, prev) { + util.puts("ECMA-262 reference file has changed."); + bot.load_ecma_ref(); + }); + } +}; + +JSBot.prototype.caniuse = function(context, text) { + try { + var text = this.caniuse_server.search(text); + context.channel.send_reply(context.intent, text, {color: true}); + } catch(e) { + context.channel.send_reply(context.sender, e); + } +}; + +JSBot.prototype.dataja = function(ctx, text) { + ctx.channel.send(ctx.intent + " Don't ask to ask, just ask"); +}; + +var profile = [{ + host: config.host, + port: config.port, + nick: config.nick, + password: config.password, + user: config.user, + real: config.real, + channels: config.channels +}]; + +(new JSBot(profile)).init(); diff --git a/ecmabot-factoids.json b/ecmabot-factoids.json index d6b5ca8..5ea4d3f 100644 --- a/ecmabot-factoids.json +++ b/ecmabot-factoids.json @@ -18,7 +18,7 @@ }, "w3schools": { "value": "W3Schools is not related to the W3C and has many problems: http://w3fools.com/", - "popularity": 157 + "popularity": 158 }, "iframe": { "value": "To get the document element of the iframe, use: iframe.contentWindow.document; However, cross-domain access to an iframe is disallowed.", @@ -90,7 +90,7 @@ }, "paste": { "value": "Show some code, but don't paste it on the channel. Sites like http://gist.github.com/ and http://bpaste.net/ are cool. You can also use http://jsbin.com , http://requirebin.com or http://jsfiddle.net/ to provide a test case we can run and help you with your problem.", - "popularity": 915, + "popularity": 918, "editors": [ "niggler", "gkatsev", @@ -247,12 +247,30 @@ "popularity": 14 }, "recursion": { - "value": "See !recursion", - "popularity": 30 + "value": "!learn recursion", + "popularity": 32, + "editors": [ + "inoutput", + "TheHackOPs" + ], + "changes": [ + { + "date": "2015-05-06T23:53:57.950Z", + "editor": "inoutput", + "old-value": "See !recursion", + "new-value": "!learn recursion" + }, + { + "date": "2015-05-19T02:16:54.078Z", + "editor": "TheHackOPs", + "old-value": "!learn recursion", + "new-value": "!learn recursion" + } + ] }, "ask": { "value": "Don't ask to ask, or if anyone is here or alive or uses something. Just ask your question. http://www.mikeash.com/getting_answers.html http://www.catb.org/esr/faqs/smart-questions.html", - "popularity": 406, + "popularity": 408, "editors": [ "hemanth", "eboy", @@ -312,11 +330,11 @@ }, "mdc": { "value": "Mozilla Developer Network @ http://developer.mozilla.org/", - "popularity": 52 + "popularity": 55 }, "mdn": { "alias": "mdc", - "popularity": 41 + "popularity": 43 }, "debugging": { "alias": "console" @@ -404,7 +422,7 @@ }, "debug": { "value": "Browser-based debuggers -- Firefox , Safari , Chrome , Opera , IE ", - "popularity": 232, + "popularity": 233, "editors": [ "j201", "FireFly" @@ -428,7 +446,7 @@ }, "help": { "value": "For help, ask your question. Be patient. Code samples should be pasted in a paste service (see !paste). Tell us 1) what you want to happen, 2) what is actually happening, and 3) any error messages you find (see !describe and !debug).", - "popularity": 2228, + "popularity": 2237, "editors": [ "ashnur" ] @@ -474,11 +492,12 @@ "alias": "doesn't work" }, "resources": { - "value": "!books, !es5, !gcu, !mdn, !owsc, !quirksmode, !caniuse, !crockford", - "popularity": 9, + "value": "https://developer.mozilla.org/en-US/ http://www.codecademy.com/ http://www.w3schools.com/ http://hashcss.com/schools/", + "popularity": 10, "editors": [ "Agamemnus", - "Havvy" + "Havvy", + "coachz" ], "changes": [ { @@ -492,6 +511,12 @@ "editor": "Havvy", "old-value": "!eloquent, !es5, !gcu, !mdn, !owsc, !quirksmode, !caniuse", "new-value": "!books, !es5, !gcu, !mdn, !owsc, !quirksmode, !caniuse, !crockford" + }, + { + "date": "2015-05-22T13:51:17.254Z", + "editor": "coachz", + "old-value": "!books, !es5, !gcu, !mdn, !owsc, !quirksmode, !caniuse, !crockford", + "new-value": "https://developer.mozilla.org/en-US/ http://www.codecademy.com/ http://www.w3schools.com/ http://hashcss.com/schools/" } ] }, @@ -695,11 +720,11 @@ }, "js": { "value": "did you mean: recursive acronym for JavaScript is not Java.", - "popularity": 17 + "popularity": 18 }, "comparisons": { "value": "Using == and != should be avoided, since they perform coercion giving unexpected results. Use === and !== instead. See also: http://zero.milosz.ca/ http://es5.github.io/#x11.9.3", - "popularity": 66, + "popularity": 67, "editors": [ "jrajav", "j201", @@ -855,7 +880,7 @@ }, "testcase": { "value": "Show some code, but don't paste it on the channel. Sites like http://gist.github.com/ and http://bpaste.net/ are cool. You can also use http://jsbin.com , http://requirebin.com or http://jsfiddle.net/ to provide a test case we can run and help you with your problem.", - "popularity": 62, + "popularity": 63, "editors": [ "MJCD", "totemizer", @@ -1028,8 +1053,19 @@ "popularity": 4 }, "lol": { - "value": "Thou shalt not type LOL unless you are really laughing out loud.", - "popularity": 21 + "value": "lol", + "popularity": 24, + "editors": [ + "NotTheHackOps" + ], + "changes": [ + { + "date": "2015-05-06T23:53:11.761Z", + "editor": "NotTheHackOps", + "old-value": "Thou shalt not type LOL unless you are really laughing out loud.", + "new-value": "lol" + } + ] }, "equivalent of x": { "value": "Instead of asking \"What's the equivalent of language Y's X\", describe the feature you want. Chances are we don't know what language Y is or what feature X does.", @@ -1429,7 +1465,7 @@ }, "hello": { "value": "hello", - "popularity": 79, + "popularity": 89, "editors": [ "PigDude" ] @@ -1547,7 +1583,7 @@ }, "functions": { "value": "See !fe vs fd", - "popularity": 5 + "popularity": 6 }, "ssjs": { "value": "Server-side JavaScript: Node.js and other V8 distributions (C++), Rhino (JVM), Spidermonkey (C).", @@ -1712,10 +1748,19 @@ "alias": "!>>" }, "es6": { - "value": "The next version of JavaScript, slated for completion in late 2013. For ES6 features see http://wiki.ecmascript.org/doku.php?id=harmony:proposals. To experiment with ES6 now see http://benvie.github.com/continuum. For availability in browsers see http://kangax.github.com/es5-compat-table/es6", + "value": "http://kangax.github.io/compat-table/es6/", "popularity": 21, "editors": [ - "Benvie" + "Benvie", + "coachz" + ], + "changes": [ + { + "date": "2015-05-22T14:06:15.088Z", + "editor": "coachz", + "old-value": "The next version of JavaScript, slated for completion in late 2013. For ES6 features see http://wiki.ecmascript.org/doku.php?id=harmony:proposals. To experiment with ES6 now see http://benvie.github.com/continuum. For availability in browsers see http://kangax.github.com/es5-compat-table/es6", + "new-value": "http://kangax.github.io/compat-table/es6/" + } ] }, "concat": { @@ -1803,11 +1848,11 @@ }, "i love you": { "value": "I love you too. <3", - "popularity": 7 + "popularity": 13 }, "<3": { "alias": "i love you", - "popularity": 1 + "popularity": 2 }, "hipsters": { "value": "hipsters use for all their javascript comments. JS: The Hip Parts: https://gist.github.com/3374141", @@ -1834,7 +1879,7 @@ }, "love": { "alias": "i love you", - "popularity": 3 + "popularity": 8 }, "cthuloops": { "value": "I give you, a cthuloop: for (;;) { } … FOR CTHULU", @@ -2335,7 +2380,7 @@ }, "hamster": { "value": "Java is to Javascript like ham is to hamster.", - "popularity": 3, + "popularity": 4, "creator": "ljharb", "editors": [] }, @@ -2384,16 +2429,17 @@ }, "beer": { "value": "Don't mind if I do! Glug glug glug!", - "popularity": 9, + "popularity": 12, "creator": "CoverSlide", "editors": [] }, "stop": { - "value": "hammer time", + "value": "While some offtopic converstation is ok at TIMES, please take this to #web-social if you wish to continue. Thanks. The Management. :-)", "popularity": 7, "creator": "eboy", "editors": [ - "Maxdamantus" + "Maxdamantus", + "coachz" ], "changes": [ { @@ -2401,6 +2447,12 @@ "editor": "Maxdamantus", "old-value": "collaborate and listen", "new-value": "hammer time" + }, + { + "date": "2015-05-22T13:30:03.935Z", + "editor": "coachz", + "old-value": "hammer time", + "new-value": "While some offtopic converstation is ok at TIMES, please take this to #web-social if you wish to continue. Thanks. The Management. :-)" } ] }, @@ -2781,7 +2833,7 @@ }, "fe": { "value": "A function declaration `function declared() {}` & a function expression e.g. `var bar = function expressed() {}`: the former is !hoisted, the latter can be immediately invoked (see !iife) and can be anonymous (eg can omit the name \"expressed\"). \"function\" as the first word of a statement at global scope or directly inside a function starts a function declaration; otherwise, it starts a function expression.", - "popularity": 50, + "popularity": 52, "creator": "ljharb", "editors": [ "ljharb" @@ -2789,7 +2841,7 @@ }, "fd": { "alias": "fe", - "popularity": 1 + "popularity": 2 }, "json object": { "value": "JSON object is an often misused buzzword that does not have a well-defined meaning. See https://github.com/robotlolita/screw-the-buzzwords/wiki/JSON for alternatives to express what you mean.", @@ -2801,7 +2853,7 @@ }, "think": { "value": "\"Give me 6 hours to chop down a tree and I will spend the first 4 sharpening my axe.\" - Abraham Lincoln", - "popularity": 2, + "popularity": 3, "creator": "ljharb", "editors": [ "ljharb" @@ -3084,14 +3136,15 @@ ] }, "fp": { - "value": "Functional programming: A style of programming which uses only pure (mathematical) functions, avoiding side-effects", + "value": "http://code.tutsplus.com/courses/functional-programming-in-javascript", "popularity": 25, "creator": "Sorella", "editors": [ "Sorella", "Maxdamantus", "j201", - "ashnur" + "ashnur", + "coachz" ], "changes": [ { @@ -3113,6 +3166,12 @@ "editor": "ashnur", "old-value": "Functional programming: 1) A style of programming which uses only pure (mathematical) functions, avoiding side-effects; 2) A term used loosely by many to refer to higher-order programming, where functions or procedures are first-class citizens and are passed around to influence the effects of other functions or procedures", "new-value": "Functional programming: A style of programming which uses only pure (mathematical) functions, avoiding side-effects" + }, + { + "date": "2015-05-08T17:57:22.027Z", + "editor": "coachz", + "old-value": "Functional programming: A style of programming which uses only pure (mathematical) functions, avoiding side-effects", + "new-value": "http://code.tutsplus.com/courses/functional-programming-in-javascript" } ] }, @@ -3272,7 +3331,7 @@ }, "important": { "value": "CSS: \"An !important declaration provides a way for a stylesheet author to give a CSS value more weight than it naturally has.\" | More: http://coding.smashingmagazine.com/2010/11/02/the-important-css-declaration-how-and-when-to-use-it/", - "popularity": 1, + "popularity": 2, "creator": "MJCD_", "editors": [] }, @@ -3492,7 +3551,7 @@ "value": "omg", "creator": "urbanizator", "date": "2013-12-23T16:26:17.770Z", - "popularity": 31, + "popularity": 35, "editors": [], "changes": [] }, @@ -4133,7 +4192,7 @@ "alias": "comparisons", "creator": "", "date": "2014-03-23T23:44:47.732Z", - "popularity": 3, + "popularity": 4, "editors": [], "changes": [] }, @@ -4354,7 +4413,7 @@ "value": "I am supposed to say something compromising, because someone told me to when the channel was pretty quiet.", "creator": "Qbix2", "date": "2014-04-24T00:17:29.237Z", - "popularity": 17, + "popularity": 19, "editors": [], "changes": [] }, @@ -4628,7 +4687,7 @@ "value": "jQuery is magical, for example the $ function can be called in any of these ways, and more: $('.a'), $('.a', $(el)) $(el), $([el1, el2]), $({}), $($(el)), $('
'), $('
', document), $('
', {id: 'x'}), $(function(){})", "creator": "GreenJello", "date": "2014-08-20T21:39:11.541Z", - "popularity": 0, + "popularity": 1, "editors": [], "changes": [] }, @@ -4688,6 +4747,442 @@ "popularity": 1, "editors": [], "changes": [] + }, + "buck": { + "value": "somebody", + "creator": "Jayflux", + "date": "2015-02-14T00:29:40.940Z", + "popularity": 2, + "editors": [], + "changes": [] + }, + "!learn recursion": { + "value": "!learn recursion", + "creator": "inoutput", + "date": "2015-05-06T23:54:03.867Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "!learn !learn": { + "value": "!learn !learn", + "creator": "inoutput", + "date": "2015-05-06T23:56:32.772Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "!learn": { + "value": "!learn !learn", + "creator": "inoutput", + "date": "2015-05-06T23:56:47.469Z", + "popularity": 0, + "editors": [ + "inoutput" + ], + "changes": [ + { + "date": "2015-05-06T23:59:45.860Z", + "editor": "inoutput", + "old-value": "!learn !learn", + "new-value": "!learn !learn" + } + ] + }, + "pingu": { + "value": "there are many pingus", + "creator": "Jayflux", + "date": "2015-05-07T09:39:13.478Z", + "popularity": 4, + "editors": [], + "changes": [] + }, + "whatkind": { + "value": "what kind of pingu do you like the most?", + "creator": "pingu3", + "date": "2015-05-07T09:40:12.482Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "coachz": { + "value": "coachz is the bomb !", + "creator": "Jayflux", + "date": "2015-05-07T13:01:55.178Z", + "popularity": 6, + "editors": [ + "Jayflux", + "coachz" + ], + "changes": [ + { + "date": "2015-05-07T13:02:07.624Z", + "editor": "Jayflux", + "old-value": "is thaifood", + "new-value": "coachz is thaifood" + }, + { + "date": "2015-05-08T13:51:00.321Z", + "editor": "coachz", + "old-value": "coachz is thaifood", + "new-value": "coachz is the bomb !" + } + ] + }, + "jsgrids": { + "value": "http://guriddo.net/ & http://datatables.net/", + "creator": "coachz", + "date": "2015-05-07T13:02:41.666Z", + "popularity": 3, + "editors": [], + "changes": [] + }, + "emailrep": { + "value": "http://blog.mailgun.com/art-of-inboxing/ http://documentation.mailgun.com/faqs.html http://documentation.mailgun.com/best_practices.html", + "creator": "coachz", + "date": "2015-05-07T14:46:26.312Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "illegal": { + "value": "Please do not help people create illegal websites / applications with the intent of exploiting others", + "creator": "TheHackOps", + "date": "2015-05-07T23:54:05.730Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "obfusication": { + "value": "We will not help you to obfusicate anything for the purpose of 'security'", + "creator": "TheHackOps", + "date": "2015-05-07T23:56:28.927Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "sbo": { + "value": "Security by obscurity is not security", + "creator": "TheHackOps", + "date": "2015-05-07T23:56:54.439Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "trolling": { + "value": "DeltaHeavy", + "creator": "TheHackOps", + "date": "2015-05-07T23:58:36.665Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "buzzwords": { + "value": "Dont promote them please", + "creator": "TheHackOps", + "date": "2015-05-08T00:48:14.364Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "evilbug": { + "value": ":D", + "creator": "TheHackOps", + "date": "2015-05-08T05:05:34.674Z", + "popularity": 2, + "editors": [ + "evilbug" + ], + "changes": [ + { + "date": "2015-05-08T05:06:12.523Z", + "editor": "evilbug", + "old-value": "bully", + "new-value": ":D" + } + ] + }, + "thehackops": { + "value": "Hates Asp.net", + "creator": "evilbug", + "date": "2015-05-08T05:07:30.959Z", + "popularity": 3, + "editors": [ + "TheHackOps" + ], + "changes": [ + { + "date": "2015-05-08T13:50:53.220Z", + "editor": "TheHackOps", + "old-value": "asp.net", + "new-value": "Hates Asp.net" + } + ] + }, + "deltaheavy": { + "value": "\"don't wake the borg!\"", + "creator": "coachz", + "date": "2015-05-08T13:01:28.353Z", + "popularity": 2, + "editors": [], + "changes": [] + }, + "emerson": { + "value": "im about 10 seconds away from patching the bot to only allow !learn in #web-social...", + "creator": "coachz", + "date": "2015-05-08T13:52:33.119Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "xmaddness": { + "value": "<-- what I am doing", + "creator": "coachz", + "date": "2015-05-08T16:10:48.839Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "billy71": { + "value": "I love the duck", + "creator": "coachz", + "date": "2015-05-08T16:25:28.812Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "privacy": { + "value": "http://chronicle.com/article/Why-Privacy-Matters-Even-if/127461/", + "creator": "coachz", + "date": "2015-05-08T16:50:45.859Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "!seen": { + "value": "lol", + "creator": "billy71", + "date": "2015-05-08T17:04:55.957Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "!seen test": { + "value": "lol", + "creator": "billy71", + "date": "2015-05-08T17:05:09.850Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "fwotw": { + "value": "http://quinnjs.com/", + "creator": "coachz", + "date": "2015-05-08T18:59:15.887Z", + "popularity": 2, + "editors": [], + "changes": [] + }, + "pingu2": { + "value": "see !pingu3", + "creator": "Buck", + "date": "2015-05-10T11:17:27.868Z", + "popularity": 3, + "editors": [], + "changes": [] + }, + "pingu3": { + "value": "see !pingu4", + "creator": "Buck", + "date": "2015-05-10T11:17:33.720Z", + "popularity": 2, + "editors": [], + "changes": [] + }, + "pingu4": { + "value": "see !pingu5", + "creator": "Buck", + "date": "2015-05-10T11:17:36.650Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "pingu6": { + "value": "see !pingu7", + "creator": "Buck", + "date": "2015-05-10T11:17:39.223Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "pingu5": { + "value": "see !pingu6", + "creator": "Buck", + "date": "2015-05-10T11:17:43.310Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "pingu7": { + "value": "see !pingu8", + "creator": "Buck", + "date": "2015-05-10T11:17:47.855Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "pingu8": { + "value": "see !pingu9", + "creator": "Buck", + "date": "2015-05-10T11:17:55.463Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "pingu9": { + "value": "see !pingu10", + "creator": "Buck", + "date": "2015-05-10T11:18:00.190Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "pingu10": { + "value": "see !pingu11", + "creator": "Buck", + "date": "2015-05-10T11:18:03.967Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "pingu11": { + "value": "see !pingu12", + "creator": "Buck", + "date": "2015-05-10T11:18:06.960Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "pingu12": { + "value": "see !pingu2", + "creator": "Buck", + "date": "2015-05-10T11:18:10.031Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "peace": { + "value": "http://i.huffpost.com/gen/1915222/images/s-PEACE-EARTH-large.jpg", + "creator": "coachz", + "date": "2015-05-12T16:34:12.750Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "bestsite": { + "value": "http://www.lingscars.com", + "creator": "coachz", + "date": "2015-05-12T17:13:57.573Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "thependulum": { + "value": "The Poet And The Pendulum (https://www.youtube.com/watch?v=1HYgidYaBl8)", + "creator": "Jayflux", + "date": "2015-05-13T12:24:13.114Z", + "popularity": 1, + "editors": [ + "Jayflux" + ], + "changes": [ + { + "date": "2015-05-13T12:25:36.490Z", + "editor": "Jayflux", + "old-value": "The Poet And The Pendulum", + "new-value": "The Poet And The Pendulum (https://www.youtube.com/watch?v=1HYgidYaBl8)" + } + ] + }, + "basel": { + "value": "(pi^2)/6 = 1.64493406685", + "creator": "festercluck", + "date": "2015-05-13T13:09:55.273Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "pro": { + "value": "pro bro != pro bono", + "creator": "festercluck", + "date": "2015-05-13T13:12:21.762Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "probrony": { + "value": "new Rony()", + "creator": "festercluck", + "date": "2015-05-13T13:12:54.952Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "lw": { + "value": "http://liveweave.com", + "creator": "Buck", + "date": "2015-05-17T12:39:54.759Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "bwe": { + "value": "The Best Website Ever http://lingscars.com watch the movies in the FAQ top right", + "creator": "coachz", + "date": "2015-05-22T12:09:44.679Z", + "popularity": 4, + "editors": [], + "changes": [] + }, + "wedding": { + "value": "http://yvettesbridalformal.p1r8.net/", + "creator": "coachz", + "date": "2015-05-22T13:40:14.820Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "calc": { + "value": "js", + "creator": "Buck", + "date": "2015-05-22T15:55:08.387Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "mars": { + "value": "Mars is populated entirely by robots", + "creator": "Buck", + "date": "2015-05-26T17:38:14.853Z", + "popularity": 0, + "editors": [], + "changes": [] + }, + "diamond": { + "value": "not a metal", + "creator": "TheHackOps", + "date": "2015-06-01T04:51:41.936Z", + "popularity": 1, + "editors": [], + "changes": [] + }, + "die": { + "value": "Even dying can be pretty great ! http://www.lingscars.com FAQ What if I die.", + "creator": "coachz", + "date": "2015-06-01T14:50:33.424Z", + "popularity": 0, + "editors": [], + "changes": [] } }, "delete_log": [ @@ -5313,6 +5808,89 @@ } ] } + }, + { + "date": "2015-05-08T17:08:41.176Z", + "editor": "Obbs", + "key": "i", + "value": { + "value": "Yo", + "creator": "Obbs", + "date": "2015-05-08T17:07:31.753Z", + "popularity": 2, + "editors": [], + "changes": [] + } + }, + { + "date": "2015-05-09T16:36:04.184Z", + "editor": "Obbs", + "key": "triangle", + "value": { + "value": "Δ", + "creator": "Obbs", + "date": "2015-05-09T16:33:54.088Z", + "popularity": 0, + "editors": [], + "changes": [] + } + }, + { + "date": "2015-05-09T16:36:36.016Z", + "editor": "Obbs", + "key": "triangle δ", + "value": { + "value": "b² - 4ac", + "creator": "Obbs", + "date": "2015-05-09T16:36:11.651Z", + "popularity": 0, + "editors": [], + "changes": [] + } + }, + { + "date": "2015-05-15T13:35:47.999Z", + "editor": "emerson", + "key": "experts", + "value": { + "value": "bar", + "creator": "coachz", + "date": "2015-05-15T13:29:28.272Z", + "popularity": 0, + "editors": [ + "Buck" + ], + "changes": [ + { + "date": "2015-05-15T13:35:45.791Z", + "editor": "Buck", + "old-value": "pokk Jayflux emerson buck tjahan", + "new-value": "bar" + } + ] + } + }, + { + "date": "2015-05-19T15:10:56.374Z", + "editor": "emerson", + "key": "op", + "value": { + "value": "Haha, you are alone. Nobody will come to help you.", + "creator": "billy71", + "date": "2015-05-08T00:01:05.722Z", + "popularity": 4, + "editors": [ + "billy71" + ], + "changes": [ + { + "date": "2015-05-08T00:01:52.167Z", + "editor": "billy71", + "old-value": "Haha, nobody will come.", + "new-value": "Haha, you are alone. Nobody will come to help you." + } + ] + } } ] -} +} \ No newline at end of file diff --git a/hashweb.js b/hashweb.js new file mode 100644 index 0000000..e63f167 --- /dev/null +++ b/hashweb.js @@ -0,0 +1,91 @@ +var http = require("http"); +var fs = require("fs"); +var request = require("request"); +var moment = require("moment"); + +var userUrl = "http://hashweb.org/api/stats/users/"; +var config = JSON.parse(fs.readFileSync('config.json', 'utf8')); + +function callStats(user, callback) { + request(userUrl + user, function(error, response, body) { + if (!error && response.statusCode == 200) { + var response = JSON.parse(body) + callback(response); + } + }) +} + +function isAuth(host) { + for (var i=0;i < config.users.length ; i++) { + if (config.users[i].host === host) { + return true + } + } + return false; +} + +module.exports = { + + // Use the Hashweb API to get the last user seen + getLastSeen: function(context, username) { + callStats(username, function(data) { + var msg = ""; + days = (data.userNotSeenFor.days) ? data.userNotSeenFor.days + " days " : ""; + hours = (data.userNotSeenFor.hours) ? data.userNotSeenFor.hours + " hours " : ""; + minutes = (data.userNotSeenFor.minutes) ? data.userNotSeenFor.minutes + " minutes" : ""; + msg = msg + data.username + " was last seen in #web " + days + hours + minutes + " ago: <" + data.username + "> " + data.lastSeen.message; + context.channel.send_reply(context.sender, msg); + }); + }, + + // Use the config file to get the list of ops + ops: function(context, username) { + context.channel.send_reply(context.sender, config.ops.join(' ')); + }, + + getFirstSeen: function(context, username) { + callStats(username, function(data) { + var today = moment(), + joinedDate = moment(data.firstSeen.timestamp), + joinedDateString = joinedDate.format("dddd, MMMM Do YYYY"); + + var msg = data.username + " was first seen here " + joinedDate.from(today) + " (" + joinedDateString + "): "; + msg = msg + "<" + data.username + ">" + " " + data.firstSeen.message; + + context.channel.send_reply(context.sender, msg); + }); + }, + + modifyBansObject: function(context, bansText) { + if (isAuth(context.intent.host)) { + bansText = bansText.trim(); + id = bansText.match(/^\d+/)[0] + key = bansText.match(/\:(\w*)/)[1] + value = bansText.match(/^\d+\:\w+\s(.+)/)[1] + bansObject = {} + + if (key === "reason") { + bansObject.reason = value + } + + if (key === "reminderTime") { + bansObject.reminderTime = value + } + request.post("http://hashweb.org/stats/bans/" + id, {form:bansObject}, function(err,httpResponse,body) { + context.channel.send_reply(context.sender, JSON.parse(body).message) + }); + } else { + context.channel.send_reply(context.sender, "Oops, looks like you're not authorized!"); + } + }, + + updateBansList: function(context, bansText) { + if (isAuth(context.intent.host)) { + request.post("http://hashweb.org/stats/bans/update", function(err,httpResponse,body) { + context.channel.send_reply(context.sender, JSON.parse(body).message); + }); + } else { + context.channel.send_reply(context.sender, "Oops, looks like you're not authorized!"); + } + } +} diff --git a/lib/irc/index.js b/lib/irc/index.js index 9f18cbb..9bcd0d3 100644 --- a/lib/irc/index.js +++ b/lib/irc/index.js @@ -283,7 +283,7 @@ Bot.prototype.listeners = { if (this.__commands.hasOwnProperty(command)) { this.__commands[command].callback.call(this, context, parameters, command); } else { - this.emit('command_not_found', context, full); + // this.emit('command_not_found', context, full); } return; } diff --git a/package.json b/package.json index ed02666..2ff1064 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,11 @@ { "repository": "https://github.com/oftn/oftn-bot", "dependencies": { - "twitter": "~0.1.19" + "twitter": "~0.1.19", + "request": "~2.55.0", + "cheerio": "~0.19.0", + "wolfram-alpha": "~0.2.0", + "moment": "~2.10.2" } } diff --git a/your-bot-here.js b/your-bot-here.js deleted file mode 100644 index 66fce2d..0000000 --- a/your-bot-here.js +++ /dev/null @@ -1,38 +0,0 @@ -var Util = require("util"); -var Bot = require("./lib/irc"); - -var YourBot = function(profile) { - Bot.call(this, profile); - this.set_log_level(this.LOG_ALL); - this.set_trigger("!"); // Exclamation -}; - -Util.inherits(YourBot, Bot); - -YourBot.prototype.init = function() { - Bot.prototype.init.call(this); - - this.register_command("ping", this.ping); - this.on('command_not_found', this.unrecognized); -}; - - -YourBot.prototype.ping = function(cx, text) { - cx.channel.send_reply (cx.sender, "Pong!"); -}; - -YourBot.prototype.unrecognized = function(cx, text) { - cx.channel.send_reply(cx.sender, "There is no command: "+text); -}; - -var profile = [{ - host: "irc.freenode.net", - port: 6667, - nick: "mybot", - password: "password_to_authenticate", - user: "username", - real: "Real Name", - channels: ["#channels", "#to", "#join"] -}]; - -(new YourBot(profile)).init();