├── .gitignore ├── LICENSE.txt ├── README.md ├── bin └── jenkins.sh ├── config.js.sample ├── data └── README ├── lib ├── admins.js ├── entities.js ├── entities.js.LICENSE ├── listdb.js └── objdb.js ├── main.js ├── package.json ├── regexFactory.js ├── scripts.js └── scripts ├── admin.js ├── autojoin.js ├── bitly.js ├── botsnack.js ├── britishenglishpolice.js.verbose ├── coin.js ├── colortest.js ├── counter.js ├── dice.js ├── doyou.js ├── emoticons.js ├── eval.child.js ├── eval.js ├── eval_coffee.child.js ├── eval_coffee_compile.child.js ├── excuse.js ├── fire.js ├── googl.js ├── greet.js ├── help.js ├── mdn.js ├── nickserv.js ├── numbergame.js ├── ping.js ├── rockpaperscissors.js ├── sayitagain.js ├── smack.js ├── tell.js ├── time.js ├── title.js └── twitter.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | config.js 3 | data/ 4 | node_modules/ 5 | scripts/*.local.js 6 | *.log 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Richard Carter 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nodebot - a [node.js](http://www.nodejs.org/) IRC bot 2 | ===================================================== 3 | 4 | Prerequisites 5 | ------------- 6 | 7 | - node.js 8 | - npm 9 | - Run `npm install` to install the dependencies listed in `package.json` 10 | 11 | Configuration 12 | ------------- 13 | 14 | Copy `config.js.sample` to `config.js` and then look in the file for all 15 | configuration options. You can disable scripts by renaming them to a different 16 | extension or deleting/moving them out of the scripts folder. 17 | 18 | Running 19 | ------- 20 | 21 | Just run `node main.js`. It will load the scripts and connect to the IRC server, 22 | at which time the scripts should react to messages on the server (e.g. 23 | autojoin.js will autojoin any channels, nickserv.js will identify with NickServ, 24 | pong.js will send a PONG reply to every PING). 25 | 26 | It also starts a REPL loop, so that you can run commands manually. So for 27 | example, you could `nodebot.join('#room')` to cause the bot to join a room, 28 | `nodebot.privmsg('#room', 'hi')` to make the bot talk in the room, or 29 | `nodebot.loadScripts()` to reload the scripts. Refer to main.js for the 30 | available functions in the `global.nodebot` object, or run 31 | `require('util').inspect(nodebot)`. 32 | 33 | Scripts 34 | ------- 35 | 36 | Scripts are still under development, and the framework is such that one is not 37 | able to programmatically create a list of the available commands. Thus, the 38 | `help.js` script is rather useless and certainly not what one might expect. So 39 | please refer to the top and contents of each script to find out what it does. 40 | Each script has a comment at the top clearly explaining what the script does and 41 | each command it makes available. 42 | 43 | To trigger a script action, you can say "{botname}, {action}" or 44 | "{prefix}{action}" where prefix is set in the config file. For example, 45 | "nodebot, help" or "~help". Some scripts don't react this way; the "say" script, 46 | for example, doesn't require a prefix, so you can simply write "say something" 47 | and the bot will reply "something". 48 | 49 | For developing your own scripts, use the existing scripts as examples to make 50 | your own. 51 | 52 | License 53 | ------- 54 | 55 | (c) 2012 Richard Carter 56 | 57 | This project is licensed under the MIT license; see LICENSE.txt for details. 58 | -------------------------------------------------------------------------------- /bin/jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git ls-files | grep -r \.js$ | xargs uglifyjs -lint > /dev/null 4 | 5 | -------------------------------------------------------------------------------- /config.js.sample: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | global.nodebot_prefs = { 5 | realname: 'Nodebot v0.8', 6 | nickname: 'nodebot1337', 7 | username: 'nodebot1337', 8 | serverpass: null, 9 | 10 | /* NickServ details, case-insensitive. 11 | * Note: regex characters must be escaped, as these values are placed 12 | * into a regex (not the password). 13 | */ 14 | nickserv_nickname: 'NickServ', 15 | nickserv_hostname: 'NickServ@services\\.', 16 | nickserv_password: 'password', 17 | 18 | command_prefix: '~', 19 | server: 'irc.freenode.net', 20 | port: 6667, 21 | secret: 'secret', /* password to become admin */ 22 | 23 | ping_interval: 58000, /* ms to pass before sending a PING to stay connected */ 24 | 25 | default_location: '90210', 26 | 27 | /* bitly.js */ 28 | bitly_username: '', 29 | bitly_apikey: '', 30 | 31 | /* Google API - https://code.google.com/apis/console/?noredirect 32 | * Currently used for the goo.gl library. Visit the above URL and register 33 | * an application. Enable the URL Shortener API. Copy and paste the API key 34 | * from the API Access page into here. 35 | */ 36 | google_api_key: '', 37 | 38 | /* twitter */ 39 | twitter_consumerkey: '', 40 | twitter_secret: '' 41 | }; 42 | -------------------------------------------------------------------------------- /data/README: -------------------------------------------------------------------------------- 1 | Scripts should write data into this directory. 2 | 3 | -------------------------------------------------------------------------------- /lib/admins.js: -------------------------------------------------------------------------------- 1 | // (c) 2015 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // The admins library keeps track of administrative users. It should be used 5 | // sparingly in scripts; prefer the "listen_admin" sandbox function for 6 | // defining commands that can only be used by admins. 7 | // TODO: Track the user better. Nickname is not a reliable indicator of a user, 8 | // easy to impersonate (even if only for a short time thanks to NickServ guard) 9 | 10 | var db = require('./listdb').getDB('admins'); 11 | 12 | exports.is = function (nickname) { 13 | return db.hasValue(nickname, true); 14 | } 15 | 16 | exports.add = function (nickname) { 17 | db.add(nickname); 18 | } 19 | 20 | exports.remove = function (nickname) { 21 | db.remove(nickname, true); 22 | } 23 | 24 | // Returns list of admins as a comma-separated string 25 | exports.list = function () { 26 | return db.getAll().join(","); 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /lib/entities.js: -------------------------------------------------------------------------------- 1 | var entities = { 2 | ' ': '\u00a0', 3 | '¡': '\u00a1', 4 | '¢': '\u00a2', 5 | '£': '\u00a3', 6 | '€': '\u20ac', 7 | '¥': '\u00a5', 8 | '¦': '\u0160', 9 | '§': '\u00a7', 10 | '¨': '\u0161', 11 | '©': '\u00a9', 12 | 'ª': '\u00aa', 13 | '«': '\u00ab', 14 | '¬': '\u00ac', 15 | '­': '\u00ad', 16 | '®': '\u00ae', 17 | '¯': '\u00af', 18 | '°': '\u00b0', 19 | '±': '\u00b1', 20 | '²': '\u00b2', 21 | '³': '\u00b3', 22 | '´': '\u017d', 23 | 'µ': '\u00b5', 24 | '¶': '\u00b6', 25 | '·': '\u00b7', 26 | '¸': '\u017e', 27 | '¹': '\u00b9', 28 | 'º': '\u00ba', 29 | '»': '\u00bb', 30 | '¼': '\u0152', 31 | '½': '\u0153', 32 | '¾': '\u0178', 33 | '¿': '\u00bf', 34 | 'À': '\u00c0', 35 | 'Á': '\u00c1', 36 | 'Â': '\u00c2', 37 | 'Ã': '\u00c3', 38 | 'Ä': '\u00c4', 39 | 'Å': '\u00c5', 40 | 'Æ': '\u00c6', 41 | 'Ç': '\u00c7', 42 | 'È': '\u00c8', 43 | 'É': '\u00c9', 44 | 'Ê': '\u00ca', 45 | 'Ë': '\u00cb', 46 | 'Ì': '\u00cc', 47 | 'Í': '\u00cd', 48 | 'Î': '\u00ce', 49 | 'Ï': '\u00cf', 50 | 'Ð': '\u00d0', 51 | 'Ñ': '\u00d1', 52 | 'Ò': '\u00d2', 53 | 'Ó': '\u00d3', 54 | 'Ô': '\u00d4', 55 | 'Õ': '\u00d5', 56 | 'Ö': '\u00d6', 57 | '×': '\u00d7', 58 | 'Ø': '\u00d8', 59 | 'Ù': '\u00d9', 60 | 'Ú': '\u00da', 61 | 'Û': '\u00db', 62 | 'Ü': '\u00dc', 63 | 'Ý': '\u00dd', 64 | 'Þ': '\u00de', 65 | 'ß': '\u00df', 66 | 'à': '\u00e0', 67 | 'á': '\u00e1', 68 | 'â': '\u00e2', 69 | 'ã': '\u00e3', 70 | 'ä': '\u00e4', 71 | 'å': '\u00e5', 72 | 'æ': '\u00e6', 73 | 'ç': '\u00e7', 74 | 'è': '\u00e8', 75 | 'é': '\u00e9', 76 | 'ê': '\u00ea', 77 | 'ë': '\u00eb', 78 | 'ì': '\u00ec', 79 | 'í': '\u00ed', 80 | 'î': '\u00ee', 81 | 'ï': '\u00ef', 82 | 'ð': '\u00f0', 83 | 'ñ': '\u00f1', 84 | 'ò': '\u00f2', 85 | 'ó': '\u00f3', 86 | 'ô': '\u00f4', 87 | 'õ': '\u00f5', 88 | 'ö': '\u00f6', 89 | '÷': '\u00f7', 90 | 'ø': '\u00f8', 91 | 'ù': '\u00f9', 92 | 'ú': '\u00fa', 93 | 'û': '\u00fb', 94 | 'ü': '\u00fc', 95 | 'ý': '\u00fd', 96 | 'þ': '\u00fe', 97 | 'ÿ': '\u00ff', 98 | '"': '\u0022', 99 | '<': '\u003c', 100 | '>': '\u003e', 101 | ''': '\u0027', 102 | '−': '\u2212', 103 | 'ˆ': '\u02c6', 104 | '˜': '\u02dc', 105 | 'Š': '\u0160', 106 | '‹': '\u2039', 107 | 'Œ': '\u0152', 108 | '‘': '\u2018', 109 | '’': '\u2019', 110 | '“': '\u201c', 111 | '”': '\u201d', 112 | '•': '\u2022', 113 | '–': '\u2013', 114 | '—': '\u2014', 115 | '™': '\u2122', 116 | 'š': '\u0161', 117 | '›': '\u203a', 118 | 'œ': '\u0153', 119 | 'Ÿ': '\u0178', 120 | 'ƒ': '\u0192', 121 | 'Α': '\u0391', 122 | 'Β': '\u0392', 123 | 'Γ': '\u0393', 124 | 'Δ': '\u0394', 125 | 'Ε': '\u0395', 126 | 'Ζ': '\u0396', 127 | 'Η': '\u0397', 128 | 'Θ': '\u0398', 129 | 'Ι': '\u0399', 130 | 'Κ': '\u039a', 131 | 'Λ': '\u039b', 132 | 'Μ': '\u039c', 133 | 'Ν': '\u039d', 134 | 'Ξ': '\u039e', 135 | 'Ο': '\u039f', 136 | 'Π': '\u03a0', 137 | 'Ρ': '\u03a1', 138 | 'Σ': '\u03a3', 139 | 'Τ': '\u03a4', 140 | 'Υ': '\u03a5', 141 | 'Φ': '\u03a6', 142 | 'Χ': '\u03a7', 143 | 'Ψ': '\u03a8', 144 | 'Ω': '\u03a9', 145 | 'α': '\u03b1', 146 | 'β': '\u03b2', 147 | 'γ': '\u03b3', 148 | 'δ': '\u03b4', 149 | 'ε': '\u03b5', 150 | 'ζ': '\u03b6', 151 | 'η': '\u03b7', 152 | 'θ': '\u03b8', 153 | 'ι': '\u03b9', 154 | 'κ': '\u03ba', 155 | 'λ': '\u03bb', 156 | 'μ': '\u03bc', 157 | 'ν': '\u03bd', 158 | 'ξ': '\u03be', 159 | 'ο': '\u03bf', 160 | 'π': '\u03c0', 161 | 'ρ': '\u03c1', 162 | 'ς': '\u03c2', 163 | 'σ': '\u03c3', 164 | 'τ': '\u03c4', 165 | 'υ': '\u03c5', 166 | 'φ': '\u03c6', 167 | 'χ': '\u03c7', 168 | 'ψ': '\u03c8', 169 | 'ω': '\u03c9', 170 | 'ϑ': '\u03d1', 171 | 'ϒ': '\u03d2', 172 | 'ϖ': '\u03d6', 173 | ' ': '\u2002', 174 | ' ': '\u2003', 175 | ' ': '\u2009', 176 | '‌': '\u200c', 177 | '‍': '\u200d', 178 | '‎': '\u200e', 179 | '‏': '\u200f', 180 | '‚': '\u201a', 181 | '„': '\u201e', 182 | '†': '\u2020', 183 | '‡': '\u2021', 184 | '…': '\u2026', 185 | '‰': '\u2030', 186 | '′': '\u2032', 187 | '″': '\u2033', 188 | '‾': '\u203e', 189 | '⁄': '\u2044', 190 | '€': '\u20ac', 191 | 'ℑ': '\u2111', 192 | '℘': '\u2118', 193 | 'ℜ': '\u211c', 194 | 'ℵ': '\u2135', 195 | '←': '\u2190', 196 | '↑': '\u2191', 197 | '→': '\u2192', 198 | '↓': '\u2193', 199 | '↔': '\u2194', 200 | '↵': '\u21b5', 201 | '⇐': '\u21d0', 202 | '⇑': '\u21d1', 203 | '⇒': '\u21d2', 204 | '⇓': '\u21d3', 205 | '⇔': '\u21d4', 206 | '∀': '\u2200', 207 | '∂': '\u2202', 208 | '∃': '\u2203', 209 | '∅': '\u2205', 210 | '∇': '\u2207', 211 | '∈': '\u2208', 212 | '∉': '\u2209', 213 | '∋': '\u220b', 214 | '∏': '\u220f', 215 | '∑': '\u2211', 216 | '∗': '\u2217', 217 | '√': '\u221a', 218 | '∝': '\u221d', 219 | '∞': '\u221e', 220 | '∠': '\u2220', 221 | '∧': '\u2227', 222 | '∨': '\u2228', 223 | '∩': '\u2229', 224 | '∪': '\u222a', 225 | '∫': '\u222b', 226 | '∴': '\u2234', 227 | '∼': '\u223c', 228 | '≅': '\u2245', 229 | '≈': '\u2248', 230 | '≠': '\u2260', 231 | '≡': '\u2261', 232 | '≤': '\u2264', 233 | '≥': '\u2265', 234 | '⊂': '\u2282', 235 | '⊃': '\u2283', 236 | '⊄': '\u2284', 237 | '⊆': '\u2286', 238 | '⊇': '\u2287', 239 | '⊕': '\u2295', 240 | '⊗': '\u2297', 241 | '⊥': '\u22a5', 242 | '⋅': '\u22c5', 243 | '⌈': '\u2308', 244 | '⌉': '\u2309', 245 | '⌊': '\u230a', 246 | '⌋': '\u230b', 247 | '⟨': '\u2329', 248 | '⟩': '\u232a', 249 | '◊': '\u25ca', 250 | '♠': '\u2660', 251 | '♣': '\u2663', 252 | '♥': '\u2665', 253 | '♦': '\u2666' 254 | }; 255 | 256 | exports.decode = function (str) { 257 | if (!~str.indexOf('&')) return str; 258 | 259 | //Decode literal entities 260 | for (var i in entities) { 261 | str = str.replace(new RegExp(i, 'g'), entities[i]); 262 | } 263 | 264 | //Decode hex entities 265 | str = str.replace(/&#x(0*[0-9a-f]{2,5});?/gi, function (m, code) { 266 | return String.fromCharCode(parseInt(code, 16)); 267 | }); 268 | 269 | //Decode numeric entities 270 | str = str.replace(/&#([0-9]{2,4});?/gi, function (m, code) { 271 | return String.fromCharCode(+code); 272 | }); 273 | 274 | str = str.replace(/&/g, '&'); 275 | 276 | return str; 277 | } 278 | 279 | exports.encode = function (str) { 280 | str = str.replace(/&/g, '&'); 281 | 282 | //IE doesn't accept ' 283 | str = str.replace(/'/g, '''); 284 | 285 | //Encode literal entities 286 | for (var i in entities) { 287 | str = str.replace(new RegExp(entities[i], 'g'), i); 288 | } 289 | 290 | return str; 291 | } 292 | -------------------------------------------------------------------------------- /lib/entities.js.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Chris O'Hara 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/listdb.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // A list db is a file-backed list of strings; the strings should not have 5 | // the newline character in them, but otherwise can be arbitrary length. 6 | // The db file is stored in "data/DBNAME.txt". 7 | 8 | var fs = require('fs'); 9 | 10 | // Store dbs on the global object so that it persists through require reloads 11 | global._listdb = global._listdb || {}; 12 | global._listdb.dbs = global._listdb.dbs || {}; 13 | var dbs = global._listdb.dbs; 14 | 15 | function notEmpty(str) { 16 | return str && str.length > 0; 17 | } 18 | 19 | exports.getDB = function (dbName) { 20 | if (dbs.hasOwnProperty(dbName)) { 21 | // DB has already been set up 22 | return dbs[dbName]; 23 | } 24 | 25 | var fileName = 'data/'+dbName+'.txt'; 26 | var values = []; 27 | 28 | function readFile() { 29 | try { 30 | var fileContents = fs.readFileSync(fileName, 'ascii'); 31 | if(fileContents && fileContents.length > 0) { 32 | values = fileContents.split('\n').filter(notEmpty); 33 | } else { 34 | values = []; 35 | } 36 | } catch(err) { 37 | console.warn('listdb: Caught error reading file "' + fileName + 38 | '", error:', err); 39 | values = []; 40 | 41 | // in case file doesn't exist, touch it 42 | try { 43 | fs.closeSync(fs.openSync(fileName, 'a')); 44 | } catch (err) { 45 | console.error('listdb: error touching file ' + fileName, err); 46 | } 47 | } 48 | } 49 | 50 | function writeFile() { 51 | fs.writeFileSync(fileName, values.join('\n'), 'ascii'); 52 | } 53 | 54 | readFile(); 55 | 56 | fs.watch(fileName, function () { 57 | console.log('listdb: Detected change, reloading: ' + fileName); 58 | readFile(); 59 | }); 60 | 61 | console.log('listdb: Set up listdb ' + dbName + ' (' + values.length + ' entries)'); 62 | 63 | dbs[dbName] = { 64 | getName: function () { 65 | return dbName; 66 | }, 67 | getAll: function() { 68 | return values; 69 | }, 70 | hasValue: function (value, ignoreCase) { 71 | ignoreCase = ignoreCase || false; 72 | 73 | var i; 74 | for(i = 0; i < values.length; i++) { 75 | if(ignoreCase) { 76 | if(values[i].toUpperCase() == value.toUpperCase()) { 77 | return true; 78 | } 79 | } else { 80 | if(values[i] == value) { 81 | return true; 82 | } 83 | } 84 | } 85 | 86 | return false; 87 | }, 88 | add: function (value) { 89 | console.log('listdb: to ' + dbName + ' add ' + value); 90 | values.push(value); 91 | 92 | writeFile(); 93 | }, 94 | remove: function (value, ignoreCase) { 95 | ignoreCase = ignoreCase || false; 96 | 97 | var i; 98 | for(i = 0; i < values.length; i++) { 99 | if(ignoreCase) { 100 | if(values[i].toUpperCase() == value.toUpperCase()) { 101 | console.log('listdb: to ' + dbName + ' removei ' + values[i]); 102 | values.splice(i,1); 103 | i--; 104 | } 105 | } else { 106 | if(values[i] == value) { 107 | console.log('listdb: to ' + dbName + ' remove ' + values[i]); 108 | values.splice(i,1); 109 | i--; 110 | } 111 | } 112 | } 113 | 114 | writeFile(); 115 | } 116 | }; 117 | 118 | return dbs[dbName]; 119 | } 120 | 121 | -------------------------------------------------------------------------------- /lib/objdb.js: -------------------------------------------------------------------------------- 1 | // (c) 2015 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // An object db is a file-backed JSON object. 5 | // The db file is stored in "data/DBNAME.objdb.json". 6 | 7 | var fs = require('fs'); 8 | 9 | exports.getDB = function (dbName) { 10 | var fileName = 'data/'+dbName+'.objdb.json'; 11 | var obj; 12 | 13 | try { 14 | var fileContents = fs.readFileSync(fileName, 'ascii'); 15 | if(fileContents) { 16 | obj = JSON.parse(fileContents); 17 | } else { 18 | obj = {}; 19 | } 20 | } catch(err) {obj = {};} 21 | 22 | function writeFile() { 23 | fs.writeFileSync(fileName, JSON.stringify(obj), 'ascii'); 24 | } 25 | 26 | return { 27 | get: function (key) { 28 | if (key) { 29 | return obj[key]; 30 | } else { 31 | return obj; 32 | } 33 | }, 34 | write: writeFile, 35 | set: function (key, value) { 36 | obj[key] = value; 37 | writeFile(); 38 | }, 39 | remove: function (key) { 40 | delete obj[key]; 41 | writeFile(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | // https://github.com/Ricket/nodebot 4 | require('./config.js'); 5 | var util = require('util'), 6 | net = require('net'), 7 | fs = require('fs'), 8 | vm = require('vm'), 9 | repl = require('repl'), 10 | _ = require('lodash'); 11 | 12 | function uncacheModules() { 13 | // Clear the module cache 14 | var key; 15 | for (key in require.cache) { 16 | delete require.cache[key]; 17 | } 18 | } 19 | 20 | var irc = global.nodebot = (function () { 21 | var buffer, ignoredb, listeners, socket; 22 | 23 | socket = new net.Socket(); 24 | socket.setNoDelay(true); 25 | socket.setEncoding('utf8'); 26 | 27 | buffer = { 28 | b: new Buffer(4096), 29 | size: 0 30 | }; 31 | 32 | listeners = []; 33 | 34 | ignoredb = require('./lib/listdb').getDB('ignore'); 35 | 36 | var pingServer = _.debounce(function () { 37 | irc.ping(); 38 | }, nodebot_prefs.ping_interval || 58000); 39 | 40 | function send(data) { 41 | if (!data || data.length == 0) { 42 | console.error("ERROR tried to send no data"); 43 | } else if (data.length > 510) { 44 | console.error("ERROR tried to send data > 510 chars in length: " + data); 45 | } else { 46 | socket.write(data + '\r\n', 'utf8', function () { 47 | var sensitiveMatch = require('./regexFactory').password().exec(data); 48 | if (sensitiveMatch) { 49 | console.log("-> " + sensitiveMatch[1] + "***"); 50 | } else { 51 | console.log("-> " + data); 52 | } 53 | }); 54 | pingServer(); 55 | } 56 | } 57 | 58 | socket.setTimeout(240 * 1000, function () { 59 | // If the connection is closed, this will fail to send and the 'error' 60 | // and 'close' events will trigger. 61 | send('VERSION'); 62 | }); 63 | 64 | socket.on('close', function () { 65 | process.exit(); 66 | }); 67 | 68 | socket.on('data', function (data) { 69 | var newlineIdx; 70 | data = data.replace('\r', ''); 71 | while ((newlineIdx = data.indexOf('\n')) > -1) { 72 | if (buffer.size > 0) { 73 | data = buffer.b.toString('utf8', 0, buffer.size) + data; 74 | newlineIdx += buffer.size; 75 | buffer.size = 0; 76 | } 77 | handle(data.substr(0, newlineIdx)); 78 | data = data.slice(newlineIdx + 1); 79 | } 80 | if (data.length > 0) { 81 | buffer.b.write(data, buffer.size, 'utf8'); 82 | buffer.size += data.length; 83 | } 84 | }); 85 | 86 | function handle(data) { 87 | var dest, i, user, replyTo, from; 88 | console.log("<- " + data); 89 | user = (/^:([^!]+)!/i).exec(data); 90 | if (user) { 91 | user = user[1]; 92 | if (ignoredb.hasValue(user, true)) { 93 | return; 94 | } 95 | } 96 | replyTo = null; 97 | from = null; 98 | if (data.indexOf('PRIVMSG') > -1) { 99 | dest = (/^:([^!]+)!.*PRIVMSG ([^ ]+) /i).exec(data); 100 | if (dest) { 101 | if (dest[2].toUpperCase() == nodebot_prefs.nickname.toUpperCase()) { 102 | replyTo = from = dest[1]; 103 | } else { 104 | replyTo = dest[2]; 105 | from = dest[1]; 106 | } 107 | } 108 | } 109 | 110 | var match, regex, listenerRequestStop; 111 | for (i = 0; i < listeners.length; i++) { 112 | match = listeners[i][0].exec(data); 113 | 114 | if (match) { 115 | try { 116 | // TODO move data to the end since it is least commonly needed 117 | // and replyTo to the front, since it is always needed 118 | listenerRequestStop = listeners[i][1](match, data, replyTo, from); 119 | } catch (err) { 120 | console.log("Caught error in script " + listeners[i][4] + ": " + err); 121 | } 122 | if (listeners[i][2] /* once */) { 123 | listeners.splice(i, 1); 124 | i--; 125 | } 126 | if (listenerRequestStop) { 127 | break; 128 | } 129 | } 130 | } 131 | 132 | pingServer(); 133 | } 134 | 135 | function sanitize(data) { 136 | if (!data) { 137 | return data; 138 | } 139 | 140 | /* Note: 141 | * 0x00 (null character) is invalid 142 | * 0x01 signals a CTCP message, which we shouldn't ever need to do 143 | * 0x02 is bold in mIRC (and thus other GUI clients) 144 | * 0x03 precedes a color code in mIRC (and thus other GUI clients) 145 | * 0x04 thru 0x19 are invalid control codes, except for: 146 | * 0x16 is "reverse" (swaps fg and bg colors) in mIRC 147 | */ 148 | return data.replace(/\n/g, "\\n").replace(/\r/g, "\\r") 149 | .replace(/[^\x02-\x03|\x16|\x20-\x7e]/g, ""); 150 | } 151 | 152 | return { 153 | /* The following function gives full power to scripts; 154 | * you may want to no-op this function for security reasons, if you 155 | * don't vet your scripts carefully. 156 | */ 157 | raw: function(stuff) { 158 | send(stuff); 159 | }, 160 | sanitize: function (data) { 161 | return sanitize(data); 162 | }, 163 | connect: function (host, port, nickname, username, realname, serverpass) { 164 | port = port || 6667; 165 | socket.connect(port, host, function () { 166 | if (serverpass) { 167 | send('PASS ' + sanitize(serverpass)); 168 | } 169 | send('NICK ' + sanitize(nickname)); 170 | send('USER ' + sanitize(username) + ' localhost * ' + sanitize(realname)); 171 | }); 172 | }, 173 | loadScripts: function () { 174 | var i, k, script, scripts; 175 | socket.pause(); 176 | 177 | uncacheModules(); 178 | 179 | listeners = []; 180 | 181 | scripts = require('./scripts'); 182 | 183 | for (i = 0; i < scripts.length; i++) { 184 | console.log("Loading script " + scripts[i] + "..."); 185 | script = fs.readFileSync('scripts/' + scripts[i] + '.js'); 186 | if (script) { 187 | var scriptName = scripts[i]; 188 | var sandbox = { 189 | irc: irc, 190 | nodebot_prefs: nodebot_prefs, 191 | console: console, 192 | setTimeout: setTimeout, 193 | clearTimeout: clearTimeout, 194 | setInterval: setInterval, 195 | clearInterval: clearInterval, 196 | vm: vm, 197 | fs: fs, 198 | process: process, 199 | require: require, 200 | util: util, 201 | Buffer: Buffer, 202 | _: require('lodash'), 203 | regexFactory: require('./regexFactory'), 204 | listen: function (dataRegex, callback, once, prefixed) { 205 | if (!_.isRegExp(dataRegex)) { 206 | console.err("Error in script " + scripts[i] + ": first parameter to listen is not a RegExp object. Use regexFactory."); 207 | return; 208 | } 209 | once = !!once; 210 | if (typeof prefixed === "undefined" || prefixed === null) { 211 | prefixed = true; 212 | } 213 | listeners.push([dataRegex, callback, once, prefixed, scriptName]); 214 | }, 215 | listen_admin: function (dataRegex, callback, once, prefixed) { 216 | sandbox.listen(dataRegex, function(match, data, replyTo, from) { 217 | if (require('./lib/admins').is(from)) { 218 | callback(match, data, replyTo, from); 219 | } 220 | }, once, prefixed); 221 | } 222 | }; 223 | try { 224 | vm.runInNewContext(script, sandbox, scripts[i]); 225 | } catch (err) { 226 | console.log("Error in script " + scripts[i] + ": " + err); 227 | for (j = 0; j < listeners.length; j++) { 228 | if (listeners[j][3] == scriptName) { 229 | listeners.splice(j, 1); 230 | j--; 231 | } 232 | } 233 | } 234 | } else { 235 | console.log("Error loading script " + scripts[i] + ": readFileSync returned empty/null/undefined"); 236 | } 237 | } 238 | try { 239 | socket.resume(); 240 | } catch (err) {} 241 | console.log("Scripts loaded."); 242 | }, 243 | 244 | /* IRC COMMANDS: */ 245 | ping: function (server) { 246 | send("PING " + (server || nodebot_prefs.server)); 247 | }, 248 | pong: function (server) { 249 | send("PONG " + (server || nodebot_prefs.server)); 250 | }, 251 | join: function (channel, key) { 252 | var cmd = "JOIN :" + sanitize(channel); 253 | if (key) { 254 | cmd += " " + sanitize(key); 255 | } 256 | send(cmd); 257 | }, 258 | part: function (channel) { 259 | send("PART :" + sanitize(channel)); 260 | }, 261 | privmsg: function (user, message, sanitizeMessage) { 262 | if (user && message) { 263 | user = sanitize(user); //avoid sanitizing these more than once 264 | if (sanitizeMessage !== false) { 265 | message = sanitize(message); 266 | } 267 | 268 | var privmsg = "PRIVMSG " + user + " :"; 269 | var max = 510 - privmsg.length; 270 | 271 | var maxmessages = 3; 272 | while (message && (maxmessages--) > 0) { 273 | send(privmsg + message.slice(0, max)); 274 | message = message.slice(max); 275 | } 276 | } 277 | }, 278 | action: function (channel, action) { 279 | if (channel && action) { 280 | send("PRIVMSG " + sanitize(channel) + " :\x01ACTION " + sanitize(action) + "\x01"); 281 | } 282 | }, 283 | 284 | /* ADDITIONAL GLOBAL IRC FUNCTIONALITY */ 285 | ignore: function (user) { 286 | ignoredb.add(user); 287 | }, 288 | unignore: function (user) { 289 | ignoredb.remove(user, true); 290 | }, 291 | chatignorelist: function (channel) { 292 | irc.chatmsg(channel, "Ignore list: " + sanitize(ignoredb.getAll().join(","))); 293 | } 294 | } 295 | })(); // end of object 'irc' 296 | 297 | process.on('uncaughtException', function (err) { 298 | console.log("caught error in script: " + err); 299 | }); 300 | 301 | irc.loadScripts(); 302 | irc.connect(nodebot_prefs.server, nodebot_prefs.port, nodebot_prefs.nickname, 303 | nodebot_prefs.username, nodebot_prefs.realname, 304 | nodebot_prefs.serverpass); 305 | 306 | repl.start({ prompt: '> ', ignoreUndefined: true }); 307 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodebot", 3 | "private": true, 4 | "version": "0.3.0", 5 | "author": "Richard Carter ", 6 | "description": "IRC bot written in node.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Ricket/nodebot.git" 10 | }, 11 | "keywords": [ 12 | "irc", 13 | "bot" 14 | ], 15 | "dependencies": { 16 | "coffee-script": "~1.9.0", 17 | "goo.gl": "~0.1.2", 18 | "irc-colors": "1.1.0", 19 | "lodash": "~3.0.0", 20 | "oauth": "~0.9.12", 21 | "request": "~2.51.0", 22 | "timeago": "~0.2.0" 23 | }, 24 | "engines": { 25 | "node": "~0.10.36" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /regexFactory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Scripts should use these functions to populate the regex parameter 3 | * of the listen function in a standardized way. 4 | */ 5 | 6 | require("./config.js"); 7 | var _ = require("lodash"); 8 | 9 | // http://stackoverflow.com/a/6969486 10 | function escapeRegExp(str) { 11 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 12 | } 13 | 14 | function ensureArray(stringOrArray) { 15 | if (typeof stringOrArray === "string") { 16 | return [stringOrArray]; 17 | } else { 18 | return stringOrArray; 19 | } 20 | } 21 | 22 | function makePrefix(prefixed) { 23 | if (prefixed === false) { 24 | return ""; 25 | } else { 26 | return "(?:" 27 | + escapeRegExp(nodebot_prefs.command_prefix) + " ?" 28 | + "|" 29 | + escapeRegExp(nodebot_prefs.nickname) + " *[:,]? +" 30 | + ")" 31 | + (prefixed === "optional" ? "?" : ""); 32 | } 33 | } 34 | 35 | function matchAny(strings, escaped) { 36 | return "(?:" 37 | + _.map(strings, function (s) { return (escaped === false) ? "(?:" + s + ")" : escapeRegExp(s); }).join("|") 38 | + ")"; 39 | } 40 | 41 | exports.password = function () { 42 | return new RegExp( 43 | "^(PASS |PRIVMSG " + nodebot_prefs.nickserv_nickname + " :IDENTIFY )", "i"); 44 | }; 45 | 46 | exports.only = function (keywords, prefixed) { 47 | keywords = ensureArray(keywords); 48 | 49 | return new RegExp( 50 | "PRIVMSG [^ ]+ :" + makePrefix(prefixed) + matchAny(keywords) + "$", "i"); 51 | }; 52 | 53 | exports.startsWith = function (keywords, prefixed) { 54 | keywords = ensureArray(keywords); 55 | return new RegExp( 56 | "PRIVMSG [^ ]+ :" + makePrefix(prefixed) + matchAny(keywords) + "\\b ?(.*)$", "i"); 57 | }; 58 | 59 | exports.matches = function (regexStrings, prefixed, only) { 60 | regexStrings = ensureArray(regexStrings); 61 | return new RegExp( 62 | "PRIVMSG [^ ]+ :" + makePrefix(prefixed) + matchAny(regexStrings, false) + (only ? "$" : ""), "i"); 63 | }; 64 | 65 | -------------------------------------------------------------------------------- /scripts.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'admin', 3 | 'autojoin', 4 | 'bitly', 5 | 'botsnack', 6 | 'coin', 7 | 'colortest', 8 | 'counter', 9 | 'dice', 10 | 'doyou', 11 | 'emoticons', 12 | 'eval', 13 | 'excuse', 14 | 'fire', 15 | 'greet', 16 | 'googl', 17 | 'help', 18 | 'mdn', 19 | 'nickserv', 20 | 'numbergame', 21 | 'ping', 22 | 'rockpaperscissors', 23 | 'sayitagain', 24 | 'smack', 25 | 'tell', 26 | 'time', 27 | 'twitter', 28 | 'title' 29 | ]; 30 | -------------------------------------------------------------------------------- /scripts/admin.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~secret password - authenticate to become an admin 6 | // ~makeadmin user - make user an admin 7 | // ~unadmin user - demote user from admin status 8 | // ~admins - list admins 9 | // ~ignore user - the bot will no longer respond to messages from [user] 10 | // ~unignore user - the bot will once more respond to messages from [user] 11 | // ~reload - reload scripts 12 | 13 | require('./config.js'); 14 | var admins = require('./lib/admins'); 15 | 16 | listen(regexFactory.startsWith("secret"), function(match, data, replyTo, from) { 17 | if (admins.is(from)) { 18 | irc.privmsg(replyTo, "You are already an admin."); 19 | } else if (match[1] === nodebot_prefs.secret) { 20 | admins.add(from); 21 | irc.privmsg(replyTo, "You are now an admin."); 22 | } 23 | }); 24 | 25 | listen(regexFactory.only("admins"), function(match, data, replyTo) { 26 | irc.privmsg(replyTo, "Admins: " + admins.list()); 27 | }); 28 | 29 | listen_admin(regexFactory.startsWith("makeadmin"), function(match, data, replyTo, from) { 30 | if (admins.is(match[1])) { 31 | irc.privmsg(replyTo, match[1] + " is already an admin."); 32 | } else { 33 | admins.add(match[1]); 34 | irc.privmsg(replyTo, match[1] + " is now an admin."); 35 | } 36 | }); 37 | 38 | listen_admin(regexFactory.startsWith("unadmin"), function(match, data, replyTo, from) { 39 | if (admins.is(match[1])) { 40 | admins.remove(match[1]); 41 | irc.privmsg(replyTo, match[1] + " is no longer an admin."); 42 | } else { 43 | irc.privmsg(replyTo, match[1] + " isn't an admin."); 44 | } 45 | }); 46 | 47 | listen_admin(regexFactory.startsWith("ignore"), function(match, data, replyTo, from) { 48 | if (admins.is(match[1])) { 49 | irc.privmsg(replyTo, match[1] + " is an admin, can't be ignored."); 50 | } else { 51 | irc.ignore(match[1]); 52 | irc.privmsg(replyTo, match[1] + " is now ignored."); 53 | } 54 | }); 55 | 56 | listen_admin(regexFactory.startsWith("unignore"), function(match, data, replyTo, from) { 57 | irc.unignore(match[1]); 58 | irc.privmsg(replyTo, match[1] + " unignored."); 59 | }); 60 | 61 | listen_admin(regexFactory.only("ignorelist"), function (match, data, replyTo) { 62 | irc.chatignorelist(replyTo); 63 | }); 64 | 65 | var exec = require('child_process').exec; 66 | listen_admin(regexFactory.only("git pull"), function(match, data, replyTo) { 67 | exec('git pull', function(error, stdout, stderr) { 68 | var feedback, stdouts; 69 | stdouts = stdout.replace(/\n$/, "").split("\n"); 70 | feedback = ((error) ? "Error: " : "Result: ") + stdouts[stdouts.length - 1]; 71 | 72 | irc.privmsg(replyTo, feedback); 73 | }); 74 | }); 75 | 76 | listen_admin(regexFactory.only('reload'), function(match, data, replyTo) { 77 | irc.loadScripts(); 78 | irc.privmsg(replyTo, "Reloaded scripts."); 79 | }); 80 | 81 | listen_admin(regexFactory.startsWith('raw'), function(match) { 82 | irc.raw(match[1]); 83 | }); 84 | 85 | -------------------------------------------------------------------------------- /scripts/autojoin.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // On end of MOTD, autojoin all channels in autojoin.txt 6 | // ~join channel - cause bot to join channel 7 | // ~autojoin channel - add channel to autojoin.txt, thus autojoining on next run 8 | // ~unautojoin channel - remove channel from autojoin.txt 9 | // ~part [channel] - part channel (current channel if not specified) 10 | 11 | var db = require('./lib/listdb').getDB('autojoin'); 12 | 13 | listen(/376/i, function() { 14 | // 376 is the end of MOTD 15 | setTimeout(function () { 16 | var channels = db.getAll(), i; 17 | for (i in channels) { 18 | irc.join(channels[i]); 19 | } 20 | }, 5000); // wait 5 seconds for a cloak to apply 21 | }, true /* (one time only) */); 22 | 23 | function isChannelName(str) { 24 | return str[0] === "#"; 25 | } 26 | 27 | listen_admin(regexFactory.startsWith("join"), function(match, data, replyTo) { 28 | if (isChannelName(match[1])) { 29 | irc.join(match[1]); 30 | } 31 | }); 32 | 33 | listen_admin(regexFactory.startsWith("autojoin"), function(match, data, replyTo) { 34 | if (isChannelName(match[1])) { 35 | db.add(match[1]); 36 | irc.privmsg(replyTo, "Added " + match[1] + " to autojoin list"); 37 | } 38 | }); 39 | 40 | listen_admin(regexFactory.startsWith("unautojoin"), function(match, data, replyTo) { 41 | if (isChannelName(match[1])) { 42 | db.remove(match[1], true /* (ignore case) */); 43 | irc.privmsg(replyTo, "Removed " + match[1] + " from autojoin list"); 44 | } 45 | }); 46 | 47 | listen_admin(regexFactory.startsWith("part"), function(match, data, replyTo) { 48 | if (isChannelName(match[1])) { 49 | irc.part(match[1]); 50 | } else if (match[1].length === 0 && isChannelName(replyTo)) { 51 | irc.part(replyTo); 52 | } 53 | }); 54 | 55 | -------------------------------------------------------------------------------- /scripts/bitly.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | /* Unfortunately the bitly library is currently broken, so the following 5 | * not only is incomplete, but wouldn't work anyway. 6 | 7 | require('./config.js'); 8 | var Bitly = require('bitly').Bitly; 9 | var bitly = new Bitly(nodebot_prefs.bitly_username, nodebot_prefs.bitly_apikey); 10 | 11 | listen(regexFactory.startsWith("bitly"), function(match, data, replyTo) { 12 | bitly.shorten(match[1], function(result) { 13 | console.log(util.inspect(result)); 14 | }); 15 | }); 16 | */ 17 | -------------------------------------------------------------------------------- /scripts/botsnack.js: -------------------------------------------------------------------------------- 1 | // (c) 2012 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~botsnack - hooray! 6 | 7 | listen(regexFactory.only("botsnack"), function(match, data, replyTo) { 8 | irc.privmsg(replyTo, "Yum!"); 9 | }); 10 | -------------------------------------------------------------------------------- /scripts/britishenglishpolice.js.verbose: -------------------------------------------------------------------------------- 1 | // (c) 2011 Kshitij Parajuli 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // It replaces the words ending in -or like humor, labor, favor with it's British English counterparts. 6 | 7 | listen(/PRIVMSG [^ ]+ :(.*?)([a-zA-Z]+)or(\.| |\t|\n)(.*)?/i, function(match, data, replyTo) { 8 | if (match[2].length > 1) { 9 | irc.privmsg(replyTo, match[1] + match[2] + "our*" + match[3] + match[4]); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /scripts/coin.js: -------------------------------------------------------------------------------- 1 | // (c) 2014 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~coin|flip - randomly chooses heads or tails 6 | // ~heads - ... and you win if it's heads 7 | // ~tails - ... and you win if it's tails 8 | 9 | function isHeads(number) { 10 | return number < 0.5; 11 | } 12 | 13 | function isTails(number) { 14 | return !isHeads(number); 15 | } 16 | 17 | function isTwoHeaded(number) { 18 | return isHeads(number) && Math.abs(number - 0.1337) < 0.009001; 19 | } 20 | 21 | function flipCoin(replyTo, from, expectFunction) { 22 | var flip = Math.random(), 23 | message = from + ": " + (isHeads(flip) ? "Heads!" : "Tails!"); 24 | 25 | if (expectFunction) { 26 | message += expectFunction(flip) ? " You win!" : " You lose."; 27 | } 28 | 29 | var flipMessage = isTwoHeaded(flip) ? "flips a two-headed coin..." : "flips a coin..."; 30 | irc.action(replyTo, flipMessage); 31 | setTimeout(function() { 32 | irc.privmsg(replyTo, message); 33 | }, 1000); 34 | } 35 | 36 | listen(regexFactory.only(["coin", "flip"]), function(match, data, replyTo, from) { 37 | flipCoin(replyTo, from); 38 | }); 39 | 40 | listen(regexFactory.only(["heads"]), function(match, data, replyTo, from) { 41 | flipCoin(replyTo, from, isHeads); 42 | }); 43 | 44 | listen(regexFactory.only(["tails"]), function(match, data, replyTo, from) { 45 | flipCoin(replyTo, from, isTails); 46 | }); 47 | 48 | -------------------------------------------------------------------------------- /scripts/colortest.js: -------------------------------------------------------------------------------- 1 | // (c) 2015 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // Testing colors 6 | 7 | var colors = { 8 | "00": "white", 9 | "01": "black", 10 | "02": "blue", 11 | "03": "green", 12 | "04": "red", 13 | "05": "brown", 14 | "06": "purple", 15 | "07": "orange", 16 | "08": "yellow", 17 | "09": "lime", 18 | "10": "teal", 19 | "11": "aqua", 20 | "12": "royal", 21 | "13": "fuchsia", 22 | "14": "grey", 23 | "15": "silver" 24 | }, 25 | colorCodes = Object.keys(colors); 26 | 27 | listen(regexFactory.matches(['colortest', 'color test']), function (match, data, replyTo) { 28 | var msg = []; 29 | for (var color in colors) { 30 | msg.push("\x03" + color + ",99" + colors[color]); 31 | } 32 | irc.privmsg(replyTo, msg.join(' ')); 33 | }); 34 | 35 | var regexLetter = /[a-zA-Z]/; 36 | 37 | listen(regexFactory.matches('taste the rainbow'), function (match, data, replyTo) { 38 | var str = "SKITTLES, TASTE THE RAINBOW", 39 | colored = "", 40 | colorIdx = 0; 41 | 42 | for (var idx = 0; idx < str.length; idx++) { 43 | var c = str[idx]; 44 | if (!regexLetter.test(c)) { 45 | colored += c; 46 | } else { 47 | colored += "\x03" + colorCodes[colorIdx] + ",99" + c; 48 | colorIdx = (colorIdx + 1) % 16; 49 | } 50 | } 51 | irc.privmsg(replyTo, colored, false); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /scripts/counter.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // Holds onto a number which starts at 0 when the bot loads the script 6 | // ~increment - increments the number 7 | // ~decrement - decrements the number 8 | // ~reset [num] - resets count to num or 0 9 | 10 | var count = 0; 11 | 12 | function announceCount(replyTo) { 13 | irc.privmsg(replyTo, "The count is now " + count); 14 | } 15 | 16 | listen(regexFactory.only("increment"), function(match, data, replyTo) { 17 | count++; 18 | announceCount(replyTo); 19 | }); 20 | 21 | listen(regexFactory.only("decrement"), function(match, data, replyTo) { 22 | count--; 23 | announceCount(replyTo); 24 | }); 25 | 26 | listen(regexFactory.startsWith("reset"), function(match, data, replyTo) { 27 | count = parseInt(match[1]) || 0; 28 | announceCount(replyTo); 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /scripts/dice.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~dice|roll [XdY] - rolls a Y-sided die X times, defaults to 1d6; says the result 6 | 7 | function printHelp(replyTo) { 8 | irc.privmsg(replyTo, "Usage: dice [XdY] - where X is number of dice (up "+ 9 | "to 20) and Y is sides per die (up to 10000)"); 10 | } 11 | 12 | function clamp(num, min, max) { 13 | return Math.min(Math.max(num, min), max); 14 | } 15 | 16 | function roll(dice, faces, replyTo) { 17 | var i, results = []; 18 | 19 | dice = clamp(dice, 1, 20); 20 | faces = clamp(faces, 1, 20); 21 | 22 | for(i = 0; i < dice; i++) { 23 | results.push( (Math.floor(Math.random() * faces) + 1).toString(10) ); 24 | } 25 | irc.privmsg(replyTo, results.join(",")); 26 | } 27 | 28 | listen(regexFactory.startsWith(['dice','roll']), function(match, data, replyTo) { 29 | if (match[1].trim().length === 0) { 30 | // If just dice/roll is said, roll a default 1d6 31 | roll(1, 6, replyTo); 32 | return; 33 | } 34 | 35 | var params = match[1].split('d'); 36 | if (params.length !== 2) { 37 | printHelp(replyTo); 38 | return; 39 | } 40 | 41 | var num1 = parseInt(params[0].trim()), 42 | num2 = parseInt(params[1].trim()); 43 | if (isNaN(num1) || isNaN(num2)) { 44 | printHelp(replyTo); 45 | } else { 46 | roll(num1, num2, replyTo); 47 | } 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /scripts/doyou.js: -------------------------------------------------------------------------------- 1 | // (c) 2014 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // bot, do you __? - answers with yes or no 6 | 7 | function doI() { 8 | return Math.random() < 0.5; 9 | } 10 | 11 | function listenBinary(questionPrefix, yesPrefix, noPrefix) { 12 | listen(regexFactory.matches(questionPrefix + " ([^?]+)\\?*"), function (match, data, replyTo) { 13 | if (doI()) { 14 | irc.privmsg(replyTo, yesPrefix + " " + match[1].trim() + "."); 15 | } else { 16 | irc.privmsg(replyTo, noPrefix + " " + match[1].trim() + "."); 17 | } 18 | }); 19 | } 20 | 21 | listenBinary("do you", "Yes, I", "No, I don't"); 22 | listenBinary("are you", "Yes, I am", "No, I'm not"); 23 | listenBinary("were you", "Yes, I was", "No, I wasn't"); 24 | listenBinary("am I", "Yes, you are", "No, you aren't"); 25 | 26 | -------------------------------------------------------------------------------- /scripts/emoticons.js: -------------------------------------------------------------------------------- 1 | // http://unicodeemoticons.com/ 2 | // http://rishida.net/tools/conversion/ 3 | 4 | listen(regexFactory.only("get mad"), function (match, data, replyTo) { 5 | irc.privmsg(replyTo, "(\u256F\u00B0\u25A1\u00B0\uFF09\u256F\uFE35 \u253B\u2501\u253B", false); 6 | }); 7 | 8 | listen(regexFactory.only("soviet russia"), function (match, data, replyTo) { 9 | irc.privmsg(replyTo, "\u252C\u2500\u252C\uFEFF \uFE35 /(.\u25A1. \\\uFF09", false); 10 | }); 11 | 12 | listen(regexFactory.only("calm down"), function (match, data, replyTo) { 13 | irc.privmsg(replyTo, "\u252C\u2500\u2500\u252C \u30CE( \u309C-\u309C\u30CE)", false); 14 | }); 15 | 16 | listen(regexFactory.only("be cool"), function (match, data, replyTo) { 17 | irc.privmsg(replyTo, "\u2022_\u2022)", false); 18 | setTimeout(function () { 19 | irc.privmsg(replyTo, "( \u2022_\u2022)>\u2310\u25A0-\u25A0", false); 20 | }, 750); 21 | setTimeout(function () { 22 | irc.privmsg(replyTo, "(\u2310\u25A0_\u25A0)", false); 23 | }, 1500); 24 | }); 25 | 26 | listen(regexFactory.only("shrug", "optional"), function (match, data, replyTo) { 27 | irc.privmsg(replyTo, "\u00AF\\_(\u30C4)_/\u00AF", false); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /scripts/eval.child.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This is the helper script for eval.js 5 | 6 | var vm = require('vm'), 7 | _ = require('lodash'); 8 | 9 | process.stdin.setEncoding('utf8'); 10 | process.stdin.resume(); 11 | 12 | var wholeCommand = ""; 13 | process.stdin.on('data', function (command) { 14 | wholeCommand += command; 15 | }); 16 | 17 | process.stdin.on('end', function() { 18 | try { 19 | var result = vm.runInNewContext(wholeCommand, {_: _}); 20 | var stdout = ""; 21 | if (_.isArray(result)) { 22 | stdout = "[" + result + "]"; 23 | } 24 | else if (_.isFunction(result)) { 25 | stdout = result.toString(); 26 | } 27 | else if (_.isObject(result) && !_.isNumber(result) && !_.isDate(result)) { 28 | stdout = JSON.stringify(result); 29 | } 30 | else { 31 | stdout = "" + result; 32 | } 33 | process.stdout.write(stdout.substr(0, 512)); 34 | process.exit(0); 35 | } catch(err) { 36 | process.stdout.write(("" + err).substr(0, 512)); 37 | process.exit(1); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /scripts/eval.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~eval expression - evaluates expression as javascript within an empty 6 | // sandbox 7 | 8 | var vm = require('vm'), 9 | spawn = require('child_process').spawn; 10 | 11 | var TIMEOUT = 5000, 12 | spawnOptions = { 13 | stdio: ['pipe', 'pipe', 'pipe'], 14 | cwd: process.cwd(), 15 | env: {}, 16 | detached: false 17 | }; 18 | 19 | function doEval(childFile, userScript, replyTo) { 20 | var child = spawn('node', [childFile], spawnOptions); 21 | 22 | var timer = setTimeout(function () { child.kill('SIGKILL'); }, TIMEOUT); 23 | 24 | child.stdout.setEncoding('utf8'); 25 | var result = ""; 26 | child.stdout.on('data', function (data) { 27 | result += data; 28 | result = result.substr(0, 512); 29 | }); 30 | child.on('exit', function (code) { 31 | clearTimeout(timer); 32 | if (code === 0) { 33 | irc.privmsg(replyTo, "Result: " + result); 34 | } else if (result !== "") { 35 | irc.privmsg(replyTo, "" + result); 36 | } 37 | }); 38 | child.stdin.end(userScript); 39 | 40 | } 41 | 42 | listen(regexFactory.startsWith(['eval', 'js']), function(match, data, replyTo) { 43 | doEval('scripts/eval.child.js', match[1], replyTo); 44 | }); 45 | 46 | listen(regexFactory.startsWith(['cval', 'coffee']), function(match, data, replyTo) { 47 | doEval('scripts/eval_coffee.child.js', match[1], replyTo); 48 | }); 49 | 50 | listen(regexFactory.startsWith(['cc', 'coffeecompile']), function(match, data, replyTo) { 51 | doEval('scripts/eval_coffee_compile.child.js', match[1], replyTo); 52 | }); 53 | 54 | -------------------------------------------------------------------------------- /scripts/eval_coffee.child.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This is the helper script for eval.js 5 | 6 | var vm = require('vm'), 7 | _ = require('lodash'), 8 | coffee = require('coffee-script'); 9 | 10 | process.stdin.setEncoding('utf8'); 11 | process.stdin.resume(); 12 | 13 | var wholeCommand = ""; 14 | process.stdin.on('data', function (command) { 15 | wholeCommand += command; 16 | }); 17 | 18 | process.stdin.on('end', function() { 19 | try { 20 | // var result = vm.runInNewContext(wholeCommand, {_: _}); 21 | var result = coffee.eval(wholeCommand, {sandbox: {_: _}}); 22 | var stdout = ""; 23 | if (_.isArray(result)) { 24 | stdout = "[" + result + "]"; 25 | } 26 | else if (_.isFunction(result)) { 27 | stdout = result.toString(); 28 | } 29 | else if (_.isObject(result) && !_.isNumber(result) && !_.isDate(result)) { 30 | stdout = JSON.stringify(result); 31 | } 32 | else { 33 | stdout = "" + result; 34 | } 35 | process.stdout.write(stdout.substr(0, 512)); 36 | process.exit(0); 37 | } catch(err) { 38 | process.stdout.write(("" + err).substr(0, 512)); 39 | process.exit(1); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /scripts/eval_coffee_compile.child.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This is the helper script for eval.js 5 | 6 | var vm = require('vm'), 7 | _ = require('lodash'), 8 | coffee = require('coffee-script'); 9 | 10 | process.stdin.setEncoding('utf8'); 11 | process.stdin.resume(); 12 | 13 | var wholeCommand = ""; 14 | process.stdin.on('data', function (command) { 15 | wholeCommand += command; 16 | }); 17 | 18 | process.stdin.on('end', function() { 19 | try { 20 | // var result = vm.runInNewContext(wholeCommand, {_: _}); 21 | var result = coffee.compile(wholeCommand, {bare: true}).replace(/\n+$/, ''); 22 | var stdout = ""; 23 | if (_.isArray(result)) { 24 | stdout = "[" + result + "]"; 25 | } 26 | else if (_.isFunction(result)) { 27 | stdout = result.toString(); 28 | } 29 | else if (_.isObject(result) && !_.isNumber(result) && !_.isDate(result)) { 30 | stdout = JSON.stringify(result); 31 | } 32 | else { 33 | stdout = "" + result; 34 | } 35 | process.stdout.write(stdout.substr(0, 512)); 36 | process.exit(0); 37 | } catch(err) { 38 | process.stdout.write(("" + err).substr(0, 512)); 39 | process.exit(1); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /scripts/excuse.js: -------------------------------------------------------------------------------- 1 | // (c) 2012 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~excuse - gives you a good excuse 6 | // The list of excuses comes from BOfH: 7 | // http://pages.cs.wisc.edu/~ballard/bofh/ 8 | 9 | var EXCUSES; 10 | 11 | listen(regexFactory.only("excuse"), function(match, data, replyTo) { 12 | var excuse = EXCUSES[Math.floor(Math.random() * EXCUSES.length)]; 13 | irc.privmsg(replyTo, excuse); 14 | }); 15 | 16 | EXCUSES = [ 17 | "clock speed", 18 | "solar flares", 19 | "electromagnetic radiation from satellite debris", 20 | "static from nylon underwear", 21 | "static from plastic slide rules", 22 | "global warming", 23 | "poor power conditioning", 24 | "static buildup", 25 | "doppler effect", 26 | "hardware stress fractures", 27 | "magnetic interference from money/credit cards", 28 | "dry joints on cable plug", 29 | "we're waiting for [the phone company] to fix that line", 30 | "sounds like a Windows problem, try calling Microsoft support", 31 | "temporary routing anomaly", 32 | "somebody was calculating pi on the server", 33 | "fat electrons in the lines", 34 | "excess surge protection", 35 | "floating point processor overflow", 36 | "divide-by-zero error", 37 | "POSIX compliance problem", 38 | "monitor resolution too high", 39 | "improperly oriented keyboard", 40 | "network packets travelling uphill (use a carrier pigeon)", 41 | "Decreasing electron flux", 42 | "first Saturday after first full moon in Winter", 43 | "radiosity depletion", 44 | "CPU radiator broken", 45 | "It works the way the Wang did, what's the problem", 46 | "positron router malfunction", 47 | "cellular telephone interference", 48 | "techtonic stress", 49 | "piezo-electric interference", 50 | "(l)user error", 51 | "working as designed", 52 | "dynamic software linking table corrupted", 53 | "heavy gravity fluctuation, move computer to floor rapidly", 54 | "secretary plugged hairdryer into UPS", 55 | "terrorist activities", 56 | "not enough memory, go get system upgrade", 57 | "interrupt configuration error", 58 | "spaghetti cable cause packet failure", 59 | "boss forgot system password", 60 | "bank holiday - system operating credits not recharged", 61 | "virus attack, luser responsible", 62 | "waste water tank overflowed onto computer", 63 | "Complete Transient Lockout", 64 | "bad ether in the cables", 65 | "Bogon emissions", 66 | "Change in Earth's rotational speed", 67 | "Cosmic ray particles crashed through the hard disk platter", 68 | "Smell from unhygienic janitorial staff wrecked the tape heads", 69 | "Little hamster in running wheel had coronary; waiting for replacement to be Fedexed from Wyoming", 70 | "Evil dogs hypnotised the night shift", 71 | "Plumber mistook routing panel for decorative wall fixture", 72 | "Electricians made popcorn in the power supply", 73 | "Groundskeepers stole the root password", 74 | "high pressure system failure", 75 | "failed trials, system needs redesigned", 76 | "system has been recalled", 77 | "not approved by the FCC", 78 | "need to wrap system in aluminum foil to fix problem", 79 | "not properly grounded, please bury computer", 80 | "CPU needs recalibration", 81 | "system needs to be rebooted", 82 | "bit bucket overflow", 83 | "descramble code needed from software company", 84 | "only available on a need to know basis", 85 | "knot in cables caused data stream to become twisted and kinked", 86 | "nesting roaches shorted out the ether cable", 87 | "The file system is full of it", 88 | "Satan did it", 89 | "Daemons did it", 90 | "You're out of memory", 91 | "There isn't any problem", 92 | "Unoptimized hard drive", 93 | "Typo in the code", 94 | "Yes, yes, its called a design limitation", 95 | "Look, buddy: Windows 3.1 IS A General Protection Fault.", 96 | "That's a great computer you have there; have you considered how it would work as a BSD machine?", 97 | "Please excuse me, I have to circuit an AC line through my head to get this database working.", 98 | "Yeah, yo mama dresses you funny and you need a mouse to delete files.", 99 | "Support staff hung over, send aspirin and come back LATER.", 100 | "Someone is standing on the ethernet cable, causing a kink in the cable", 101 | "Windows 95 undocumented \"feature\"", 102 | "Runt packets", 103 | "Password is too complex to decrypt", 104 | "Boss' kid fucked up the machine", 105 | "Electromagnetic energy loss", 106 | "Budget cuts", 107 | "Mouse chewed through power cable", 108 | "Stale file handle (next time use Tupperware(tm)!)", 109 | "Feature not yet implemented", 110 | "Internet outage", 111 | "Pentium FDIV bug", 112 | "Vendor no longer supports the product", 113 | "Small animal kamikaze attack on power supplies", 114 | "The vendor put the bug there.", 115 | "SIMM crosstalk.", 116 | "IRQ dropout", 117 | "Collapsed Backbone", 118 | "Power company testing new voltage spike (creation) equipment", 119 | "operators on strike due to broken coffee machine", 120 | "backup tape overwritten with copy of system manager's favourite CD", 121 | "UPS interrupted the server's power", 122 | "The electrician didn't know what the yellow cable was so he yanked the ethernet out.", 123 | "The keyboard isn't plugged in", 124 | "The air conditioning water supply pipe ruptured over the machine room", 125 | "The electricity substation in the car park blew up.", 126 | "The rolling stones concert down the road caused a brown out", 127 | "The salesman drove over the CPU board.", 128 | "The monitor is plugged into the serial port", 129 | "Root nameservers are out of sync", 130 | "electro-magnetic pulses from French above ground nuke testing.", 131 | "your keyboard's space bar is generating spurious keycodes.", 132 | "the real ttys became pseudo ttys and vice-versa.", 133 | "the printer thinks its a router.", 134 | "the router thinks its a printer.", 135 | "evil hackers from Serbia.", 136 | "we just switched to FDDI.", 137 | "halon system went off and killed the operators.", 138 | "because Bill Gates is a Jehovah's witness and so nothing can work on St. Swithin's day.", 139 | "user to computer ratio too high.", 140 | "user to computer ration too low.", 141 | "we just switched to Sprint.", 142 | "it has Intel Inside", 143 | "Sticky bits on disk.", 144 | "Power Company having EMP problems with their reactor", 145 | "The ring needs another token", 146 | "new management", 147 | "telnet: Unable to connect to remote host: Connection refused", 148 | "SCSI Chain overterminated", 149 | "It's not plugged in.", 150 | "because of network lag due to too many people playing deathmatch", 151 | "You put the disk in upside down.", 152 | "Daemons loose in system.", 153 | "User was distributing pornography on server; system seized by FBI.", 154 | "BNC (brain not connected)", 155 | "UBNC (user brain not connected)", 156 | "LBNC (luser brain not connected)", 157 | "disks spinning backwards - toggle the hemisphere jumper.", 158 | "new guy cross-connected phone lines with ac power bus.", 159 | "had to use hammer to free stuck disk drive heads.", 160 | "Too few computrons available.", 161 | "Flat tire on station wagon with tapes. (\"Never underestimate the bandwidth of a station wagon full of tapes hurling down the highway\" Andrew S. Tannenbaum) ", 162 | "Communications satellite used by the military for star wars.", 163 | "Party-bug in the Aloha protocol.", 164 | "Insert coin for new game", 165 | "Dew on the telephone lines.", 166 | "Arcserve crashed the server again.", 167 | "Some one needed the powerstrip, so they pulled the switch plug.", 168 | "My pony-tail hit the on/off switch on the power strip.", 169 | "Big to little endian conversion error", 170 | "You can tune a file system, but you can't tune a fish (from most tunefs man pages)", 171 | "Dumb terminal", 172 | "Zombie processes haunting the computer", 173 | "Incorrect time synchronization", 174 | "Defunct processes", 175 | "Stubborn processes", 176 | "non-redundant fan failure ", 177 | "monitor VLF leakage", 178 | "bugs in the RAID", 179 | "no \"any\" key on keyboard", 180 | "root rot", 181 | "Backbone Scoliosis", 182 | "/pub/lunch", 183 | "excessive collisions & not enough packet ambulances", 184 | "le0: no carrier: transceiver cable problem?", 185 | "broadcast packets on wrong frequency", 186 | "popper unable to process jumbo kernel", 187 | "NOTICE: alloc: /dev/null: filesystem full", 188 | "pseudo-user on a pseudo-terminal", 189 | "Recursive traversal of loopback mount points", 190 | "Backbone adjustment", 191 | "OS swapped to disk", 192 | "vapors from evaporating sticky-note adhesives", 193 | "sticktion", 194 | "short leg on process table", 195 | "multicasts on broken packets", 196 | "ether leak", 197 | "Atilla the Hub", 198 | "endothermal recalibration", 199 | "filesystem not big enough for Jumbo Kernel Patch", 200 | "loop found in loop in redundant loopback", 201 | "system consumed all the paper for paging", 202 | "permission denied", 203 | "Reformatting Page. Wait...", 204 | "..disk or the processor is on fire.", 205 | "SCSI's too wide.", 206 | "Proprietary Information.", 207 | "Just type 'mv * /dev/null'.", 208 | "runaway cat on system.", 209 | "Did you pay the new Support Fee?", 210 | "We only support a 1200 bps connection.", 211 | "We only support a 28000 bps connection.", 212 | "Me no internet, only janitor, me just wax floors.", 213 | "I'm sorry a pentium won't do, you need an SGI to connect with us.", 214 | "Post-it Note Sludge leaked into the monitor.", 215 | "the curls in your keyboard cord are losing electricity.", 216 | "The monitor needs another box of pixels.", 217 | "RPC_PMAP_FAILURE", 218 | "kernel panic: write-only-memory (/dev/wom0) capacity exceeded.", 219 | "Write-only-memory subsystem too slow for this machine. Contact your local dealer.", 220 | "Just pick up the phone and give modem connect sounds. \"Well you said we should get more lines so we don't have voice lines.\"", 221 | "Quantum dynamics are affecting the transistors", 222 | "Police are examining all internet packets in the search for a narco-net-trafficker", 223 | "We are currently trying a new concept of using a live mouse. Unfortunately, one has yet to survive being hooked up to the computer.....please bear with us.", 224 | "Your mail is being routed through Germany ... and they're censoring us.", 225 | "Only people with names beginning with 'A' are getting mail this week (a la Microsoft)", 226 | "We didn't pay the Internet bill and it's been cut off.", 227 | "Lightning strikes.", 228 | "Of course it doesn't work. We've performed a software upgrade.", 229 | "Change your language to Finnish.", 230 | "Fluorescent lights are generating negative ions. If turning them off doesn't work, take them out and put tin foil on the ends.", 231 | "High nuclear activity in your area.", 232 | "What office are you in? Oh, that one. Did you know that your building was built over the universities first nuclear research site? And wow, aren't you the lucky one, your office is right over where the core is buried!", 233 | "The MGs ran out of gas.", 234 | "The UPS doesn't have a battery backup.", 235 | "Recursivity. Call back if it happens again.", 236 | "Someone thought The Big Red Button was a light switch.", 237 | "The mainframe needs to rest. It's getting old, you know.", 238 | "I'm not sure. Try calling the Internet's head office -- it's in the book.", 239 | "The lines are all busy (busied out, that is -- why let them in to begin with?).", 240 | "Jan 9 16:41:27 huber su: 'su root' succeeded for .... on /dev/pts/1", 241 | "It's those computer people in X {city of world}. They keep stuffing things up.", 242 | "A star wars satellite accidently blew up the WAN.", 243 | "Fatal error right in front of screen", 244 | "That function is not currently supported, but Bill Gates assures us it will be featured in the next upgrade.", 245 | "wrong polarity of neutron flow", 246 | "Lusers learning curve appears to be fractal", 247 | "We had to turn off that service to comply with the CDA Bill.", 248 | "Ionization from the air-conditioning", 249 | "TCP/IP UDP alarm threshold is set too low.", 250 | "Someone is broadcasting pygmy packets and the router doesn't know how to deal with them.", 251 | "The new frame relay network hasn't bedded down the software loop transmitter yet. ", 252 | "Fanout dropping voltage too much, try cutting some of those little traces", 253 | "Plate voltage too low on demodulator tube", 254 | "You did wha... oh _dear_....", 255 | "CPU needs bearings repacked", 256 | "Too many little pins on CPU confusing it, bend back and forth until 10-20% are neatly removed. Do _not_ leave metal bits visible!", 257 | "_Rosin_ core solder? But...", 258 | "Software uses US measurements, but the OS is in metric...", 259 | "The computer fleetly, mouse and all.", 260 | "Your cat tried to eat the mouse.", 261 | "The Borg tried to assimilate your system. Resistance is futile.", 262 | "It must have been the lightning storm we had (yesterday) (last week) (last month)", 263 | "Due to Federal Budget problems we have been forced to cut back on the number of users able to access the system at one time. (namely none allowed....)", 264 | "Too much radiation coming from the soil.", 265 | "Unfortunately we have run out of bits/bytes/whatever. Don't worry, the next supply will be coming next week.", 266 | "Program load too heavy for processor to lift.", 267 | "Processes running slowly due to weak power supply", 268 | "Our ISP is having {switching,routing,SMDS,frame relay} problems", 269 | "We've run out of licenses", 270 | "Interference from lunar radiation", 271 | "Standing room only on the bus.", 272 | "You need to install an RTFM interface.", 273 | "That would be because the software doesn't work.", 274 | "That's easy to fix, but I can't be bothered.", 275 | "Someone's tie is caught in the printer, and if anything else gets printed, he'll be in it too.", 276 | "We're upgrading /dev/null", 277 | "The Usenet news is out of date", 278 | "Our POP server was kidnapped by a weasel.", 279 | "It's stuck in the Web.", 280 | "Your modem doesn't speak English.", 281 | "The mouse escaped.", 282 | "All of the packets are empty.", 283 | "The UPS is on strike.", 284 | "Neutrino overload on the nameserver", 285 | "Melting hard drives", 286 | "Someone has messed up the kernel pointers", 287 | "The kernel license has expired", 288 | "Netscape has crashed", 289 | "The cord jumped over and hit the power switch.", 290 | "It was OK before you touched it.", 291 | "Bit rot", 292 | "U.S. Postal Service", 293 | "Your Flux Capacitor has gone bad.", 294 | "The Dilithium Crystals need to be rotated.", 295 | "The static electricity routing is acting up...", 296 | "Traceroute says that there is a routing problem in the backbone. It's not our problem.", 297 | "The co-locator cannot verify the frame-relay gateway to the ISDN server.", 298 | "High altitude condensation from U.S.A.F prototype aircraft has contaminated the primary subnet mask. Turn off your computer for 9 days to avoid damaging it.", 299 | "Lawn mower blade in your fan need sharpening", 300 | "Electrons on a bender", 301 | "Telecommunications is upgrading. ", 302 | "Telecommunications is downgrading.", 303 | "Telecommunications is downshifting.", 304 | "Hard drive sleeping. Let it wake up on it's own...", 305 | "Interference between the keyboard and the chair.", 306 | "The CPU has shifted, and become decentralized.", 307 | "Due to the CDA, we no longer have a root account.", 308 | "We ran out of dial tone and we're and waiting for the phone company to deliver another bottle.", 309 | "You must've hit the wrong any key.", 310 | "PCMCIA slave driver", 311 | "The Token fell out of the ring. Call us when you find it.", 312 | "The hardware bus needs a new token.", 313 | "Too many interrupts", 314 | "Not enough interrupts", 315 | "The data on your hard drive is out of balance.", 316 | "Digital Manipulator exceeding velocity parameters", 317 | "appears to be a Slow/Narrow SCSI-0 Interface problem", 318 | "microelectronic Riemannian curved-space fault in write-only file system", 319 | "fractal radiation jamming the backbone", 320 | "routing problems on the neural net", 321 | "IRQ-problems with the Un-Interruptible-Power-Supply", 322 | "CPU-angle has to be adjusted because of vibrations coming from the nearby road", 323 | "emissions from GSM-phones", 324 | "CD-ROM server needs recalibration", 325 | "firewall needs cooling", 326 | "asynchronous inode failure", 327 | "transient bus protocol violation", 328 | "incompatible bit-registration operators", 329 | "your process is not ISO 9000 compliant", 330 | "You need to upgrade your VESA local bus to a MasterCard local bus.", 331 | "The recent proliferation of Nuclear Testing", 332 | "Elves on strike. (Why do they call EMAG Elf Magic)", 333 | "Internet exceeded Luser level, please wait until a luser logs off before attempting to log back on.", 334 | "Your EMAIL is now being delivered by the USPS.", 335 | "Your computer hasn't been returning all the bits it gets from the Internet.", 336 | "You've been infected by the Telescoping Hubble virus.", 337 | "Scheduled global CPU outage", 338 | "Your Pentium has a heating problem - try cooling it with ice cold water.(Do not turn off your computer, you do not want to cool down the Pentium Chip while he isn't working, do you?)", 339 | "Your processor has processed too many instructions. Turn it off immediately, do not type any commands!!", 340 | "Your packets were eaten by the terminator", 341 | "Your processor does not develop enough heat.", 342 | "We need a licensed electrician to replace the light bulbs in the computer room.", 343 | "The POP server is out of Coke", 344 | "Fiber optics caused gas main leak", 345 | "Server depressed, needs Prozac", 346 | "quantum decoherence", 347 | "those damn raccoons!", 348 | "suboptimal routing experience", 349 | "A plumber is needed, the network drain is clogged", 350 | "50% of the manual is in .pdf readme files", 351 | "the AA battery in the wallclock sends magnetic interference", 352 | "the xy axis in the trackball is coordinated with the summer solstice", 353 | "the butane lighter causes the pincushioning", 354 | "old inkjet cartridges emanate barium-based fumes", 355 | "manager in the cable duct", 356 | "We'll fix that in the next (upgrade, update, patch release, service pack).", 357 | "HTTPD Error 666 : BOFH was here", 358 | "HTTPD Error 4004 : very old Intel cpu - insufficient processing power", 359 | "The ATM board has run out of 10 pound notes. We are having a whip round to refill it, care to contribute ?", 360 | "Network failure - call NBC", 361 | "Having to manually track the satellite.", 362 | "Your/our computer(s) had suffered a memory leak, and we are waiting for them to be topped up.", 363 | "The rubber band broke", 364 | "We're on Token Ring, and it looks like the token got loose.", 365 | "Stray Alpha Particles from memory packaging caused Hard Memory Error on Server.", 366 | "paradigm shift...without a clutch", 367 | "PEBKAC (Problem Exists Between Keyboard And Chair)", 368 | "The cables are not the same length.", 369 | "Second-system effect.", 370 | "Chewing gum on /dev/sd3c", 371 | "Boredom in the Kernel.", 372 | "the daemons! the daemons! the terrible daemons!", 373 | "I'd love to help you -- it's just that the Boss won't let me near the computer. ", 374 | "struck by the Good Times virus", 375 | "YOU HAVE AN I/O ERROR -> Incompetent Operator error", 376 | "Your parity check is overdrawn and you're out of cache.", 377 | "Communist revolutionaries taking over the server room and demanding all the computers in the building or they shoot the sysadmin. Poor misguided fools.", 378 | "Plasma conduit breach", 379 | "Out of cards on drive D:", 380 | "Sand fleas eating the Internet cables", 381 | "parallel processors running perpendicular today", 382 | "ATM cell has no roaming feature turned on, notebooks can't connect", 383 | "Webmasters kidnapped by evil cult.", 384 | "Failure to adjust for daylight savings time.", 385 | "Virus transmitted from computer to sysadmins.", 386 | "Virus due to computers having unsafe sex.", 387 | "Incorrectly configured static routes on the corerouters.", 388 | "Forced to support NT servers; sysadmins quit.", 389 | "Suspicious pointer corrupted virtual machine", 390 | "It's the InterNIC's fault.", 391 | "Root name servers corrupted.", 392 | "Budget cuts forced us to sell all the power cords for the servers.", 393 | "Someone hooked the twisted pair wires into the answering machine.", 394 | "Operators killed by year 2000 bug bite.", 395 | "We've picked COBOL as the language of choice.", 396 | "Operators killed when huge stack of backup tapes fell over.", 397 | "Robotic tape changer mistook operator's tie for a backup tape.", 398 | "Someone was smoking in the computer room and set off the halon systems.", 399 | "Your processor has taken a ride to Heaven's Gate on the UFO behind Hale-Bopp's comet.", 400 | "it's an ID-10-T error", 401 | "Dyslexics retyping hosts file on servers", 402 | "The Internet is being scanned for viruses.", 403 | "Your computer's union contract is set to expire at midnight.", 404 | "Bad user karma.", 405 | "/dev/clue was linked to /dev/null", 406 | "Increased sunspot activity.", 407 | "We already sent around a notice about that.", 408 | "It's union rules. There's nothing we can do about it. Sorry.", 409 | "Interference from the Van Allen Belt.", 410 | "Jupiter is aligned with Mars.", 411 | "Redundant ACLs. ", 412 | "Mail server hit by UniSpammer.", 413 | "T-1's congested due to porn traffic to the news server.", 414 | "Data for intranet got routed through the extranet and landed on the internet.", 415 | "We are a 100% Microsoft Shop.", 416 | "We are Microsoft. What you are experiencing is not a problem; it is an undocumented feature.", 417 | "Sales staff sold a product we don't offer.", 418 | "Secretary sent chain letter to all 5000 employees.", 419 | "Sysadmin didn't hear pager go off due to loud music from bar-room speakers.", 420 | "Sysadmin accidentally destroyed pager with a large hammer.", 421 | "Sysadmins unavailable because they are in a meeting talking about why they are unavailable so much.", 422 | "Bad cafeteria food landed all the sysadmins in the hospital.", 423 | "Route flapping at the NAP.", 424 | "Computers under water due to SYN flooding.", 425 | "The vulcan-death-grip ping has been applied.", 426 | "Electrical conduits in machine room are melting.", 427 | "Traffic jam on the Information Superhighway.", 428 | "Radial Telemetry Infiltration", 429 | "Cow-tippers tipped a cow onto the server.", 430 | "tachyon emissions overloading the system", 431 | "Maintenance window broken", 432 | "We're out of slots on the server", 433 | "Computer room being moved. Our systems are down for the weekend.", 434 | "Sysadmins busy fighting SPAM.", 435 | "Repeated reboots of the system failed to solve problem", 436 | "Feature was not beta tested", 437 | "Domain controller not responding", 438 | "Someone else stole your IP address, call the Internet detectives!", 439 | "It's not RFC-822 compliant.", 440 | "operation failed because: there is no message for this error (#1014)", 441 | "stop bit received", 442 | "internet is needed to catch the etherbunny", 443 | "network down, IP packets delivered via UPS", 444 | "Firmware update in the coffee machine", 445 | "Temporal anomaly", 446 | "Mouse has out-of-cheese-error", 447 | "Borg implants are failing", 448 | "Borg nanites have infested the server", 449 | "error: one bad user found in front of screen", 450 | "Please state the nature of the technical emergency", 451 | "Internet shut down due to maintenance", 452 | "Daemon escaped from pentagram", 453 | "crop circles in the corn shell", 454 | "sticky bit has come loose", 455 | "Hot Java has gone cold", 456 | "Cache miss - please take better aim next time", 457 | "Hash table has woodworm", 458 | "Trojan horse ran out of hay", 459 | "Zombie processes detected, machine is haunted.", 460 | "overflow error in /dev/null", 461 | "Browser's cookie is corrupted -- someone's been nibbling on it.", 462 | "Mailer-daemon is busy burning your message in hell.", 463 | "According to Microsoft, it's by design", 464 | "vi needs to be upgraded to vii", 465 | "greenpeace free'd the mallocs", 466 | "Terrorists crashed an airplane into the server room, have to remove /bin/laden. (rm -rf /bin/laden)", 467 | "astropneumatic oscillations in the water-cooling", 468 | "Somebody ran the operating system through a spelling checker.", 469 | "Rhythmic variations in the voltage reaching the power supply.", 470 | "Keyboard Actuator Failure. Order and Replace.", 471 | "Packet held up at customs.", 472 | "Propagation delay.", 473 | "High line impedance.", 474 | "Someone set us up the bomb.", 475 | "Power surges on the Underground.", 476 | "Don't worry; it's been deprecated. The new one is worse.", 477 | "Excess condensation in cloud network", 478 | "It is a layer 8 problem" 479 | ]; 480 | -------------------------------------------------------------------------------- /scripts/fire.js: -------------------------------------------------------------------------------- 1 | var OBJECTS = ["pants", "house", "cat", "mouse"], 2 | object_on_fire = null; 3 | 4 | listen(regexFactory.only("fire"), function(match, data, replyTo) { 5 | if (!object_on_fire) { 6 | object_on_fire = OBJECTS[Math.floor(Math.random() * OBJECTS.length)]; 7 | } 8 | irc.action(replyTo, "HELP!!! My " + object_on_fire + " is on fire!!! Please ~douse it!"); 9 | }); 10 | 11 | listen(regexFactory.only("douse"), function(match, data, replyTo) { 12 | if (object_on_fire) { 13 | irc.action(replyTo, "Thank you for saving my " + object_on_fire + "!"); 14 | object_on_fire = null; 15 | } 16 | else { 17 | irc.action(replyTo, "You got my stuff wet; why'd you do that?!"); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /scripts/googl.js: -------------------------------------------------------------------------------- 1 | // (c) 2015 Erik Weber, Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | require('./config.js'); 5 | var googl = require('goo.gl'); 6 | 7 | googl.setKey(nodebot_prefs.google_api_key); 8 | 9 | listen(regexFactory.startsWith("googl"), function(match, data, replyTo) { 10 | googl.shorten(match[1]) 11 | .then(function(result) { 12 | irc.privmsg(replyTo, result); 13 | }) 14 | .catch(function(err) { 15 | console.error("goo.gl error: ", err); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /scripts/greet.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // Looks for "hello", "hi", "howdy", "hola" and says hello; intended to 6 | // join in on greeting someone when someone else greets them. 7 | // Same thing for "goodbye" and "bye" 8 | 9 | require('./config.js'); 10 | 11 | var waiting = false; 12 | function privmsg_throttled(replyTo, msg) { 13 | if(!waiting) { 14 | irc.privmsg(replyTo, msg, false); 15 | waiting = true; 16 | setTimeout(function(){ waiting = false; }, 10000); 17 | } 18 | } 19 | 20 | var HELLOS = ['hello', 'hi', 'hey', 'howdy', 'hola', 'good ?morning', 'good ?afternoon', 'good ?evening'], 21 | BYES = ['goodbye', 'good ?night', 'bye', 'cya', 'cya later', 'adios', 'ttyl', 'night'], 22 | SUFFIXES = [nodebot_prefs.nickname, 'guys', 'all', 'folks', 'every ?body', 'every ?one', 'there'], 23 | PUNCTUATION = ['!+', '\\.+']; 24 | 25 | listenWithResponse(permutations(HELLOS, SUFFIXES, PUNCTUATION), "Hello! (^_^)/"); 26 | listenWithResponse(permutations(BYES, SUFFIXES, PUNCTUATION), "Bye! \\( \uFF65_\uFF65)"); 27 | 28 | function permutations(greetings, suffixes, punctuation) { 29 | return "(?:" + greetings.join("|") + ")(?: " + suffixes.join("| ") + ")?" + 30 | "(?:" + punctuation.join("|") + ")?"; 31 | } 32 | 33 | function listenWithResponse(regexString, replyMessage) { 34 | listen(regexFactory.matches(regexString, "optional", true), function(match, data, replyTo) { 35 | privmsg_throttled(replyTo, replyMessage); 36 | }); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /scripts/help.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~help - output a list of commands 6 | 7 | listen(regexFactory.only("help"), function(match, data, replyTo) { 8 | irc.privmsg(replyTo, 9 | "List of scripts: https://github.com/Ricket/nodebot/tree/master/scripts (more may exist)"); 10 | }); 11 | -------------------------------------------------------------------------------- /scripts/mdn.js: -------------------------------------------------------------------------------- 1 | // (c) 2015 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~mdn - search the Mozilla Developer Network webpage and return first result 6 | 7 | require('./config.js'); 8 | var entities = require('./lib/entities'), 9 | fs = require('fs'), 10 | exec = require('child_process').exec, 11 | googl = require('goo.gl'); 12 | 13 | googl.setKey(nodebot_prefs.google_api_key); 14 | 15 | var request = require('request').defaults({ 16 | strictSSL: false, 17 | timeout: 10000, 18 | encoding: 'utf8', 19 | headers: { 20 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36' 21 | } 22 | }); 23 | 24 | listen(regexFactory.startsWith('mdn'), function (match, data, replyTo) { 25 | var searchQuery = encodeURIComponent(match[1].trim()).trim(), 26 | url = 'https://developer.mozilla.org/en-US/search.json?q=' + searchQuery; 27 | 28 | if (searchQuery === '') return; 29 | 30 | console.log('Looking up MDN URL: ' + url); 31 | 32 | request({url: url}, function (error, response) { 33 | if (error) { 34 | console.log('Error: ', error); 35 | return; 36 | } 37 | if (!response.body || response.body === "") { 38 | console.log('MDN response empty or no data'); 39 | irc.privmsg(replyTo, "[MDN] No response"); 40 | return; 41 | } 42 | 43 | var mdnJson; 44 | try { 45 | mdnJson = JSON.parse(response.body); 46 | } catch (e) { 47 | console.log('MDN return invalid JSON: ', response.body); 48 | return; 49 | } 50 | 51 | if (mdnJson == null || mdnJson.documents == null) { 52 | console.log('MDN response null or documents null'); 53 | irc.privmsg(replyTo, "[MDN] No response"); 54 | return; 55 | } 56 | 57 | if (mdnJson.documents.length == 0) { 58 | irc.privmsg(replyTo, "[MDN] No results"); 59 | return; 60 | } 61 | 62 | var doc = mdnJson.documents[0]; 63 | 64 | googl.shorten(doc.url) 65 | .then(function (shortUrl) { 66 | var cleanExcerpt = doc.excerpt.replace(/<[^>]+>/g, '') 67 | .replace('<', '<').replace('>', '>'); 68 | irc.privmsg(replyTo, irc.sanitize("[MDN] " + doc.title + " " + shortUrl + 69 | " " + cleanExcerpt) + '\u2026', false); 70 | }) 71 | .catch(function (err) { 72 | console.log('Error shortening MDN url: ', err); 73 | irc.privmsg(replyTo, "[MDN] " + doc.title + " " + doc.url); 74 | }); 75 | 76 | }); 77 | 78 | }); 79 | 80 | -------------------------------------------------------------------------------- /scripts/nickserv.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // Listens for "This nickname is registered" from NickServ (in config.js) and replies with an IDENTIFY message. 6 | 7 | require('./config.js'); 8 | 9 | listen(new RegExp('^:' + nodebot_prefs.nickserv_nickname + '!' + nodebot_prefs.nickserv_hostname + ' NOTICE [^ ]+ :This nickname is registered', 'i'), function(match, data, replyTo) { 10 | irc.privmsg('NickServ', 'IDENTIFY ' + nodebot_prefs.nickserv_password); 11 | }); 12 | -------------------------------------------------------------------------------- /scripts/numbergame.js: -------------------------------------------------------------------------------- 1 | // (c) 2013 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | var number = {}; 5 | 6 | function newNumber() { 7 | return Math.ceil(Math.random() * 100); 8 | } 9 | 10 | function isPlaying(replyTo) { 11 | return number[replyTo] != null || number[replyTo] === 0; 12 | } 13 | 14 | listen(regexFactory.startsWith(["numbergame", "number game", "numgame"]), function (match, data, replyTo, from) { 15 | if (!isPlaying(replyTo)) { 16 | number[replyTo] = newNumber(); 17 | irc.action(replyTo, "chose a number between 1 and 100 inclusive. Guess the number! Make sure to mention my name."); 18 | } else { 19 | irc.privmsg(replyTo, from + ": I already chose a number, keep guessing!"); 20 | } 21 | }); 22 | 23 | listen(regexFactory.matches("([0-9]{1,3})"), function (match, data, replyTo, from) { 24 | if (isPlaying(replyTo)) { 25 | var userNumber = parseInt(match[1]), 26 | message = from + ": " + userNumber + " is "; 27 | if (userNumber < number[replyTo]) { 28 | message += "too low"; 29 | } else if (userNumber > number[replyTo]) { 30 | message += "too high"; 31 | } else { 32 | message += "correct! " + from + " wins!"; 33 | number[replyTo] = null; 34 | } 35 | irc.privmsg(replyTo, message); 36 | } 37 | }); 38 | 39 | -------------------------------------------------------------------------------- /scripts/ping.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | /* This is the most useful of all the plugins! It keeps the bot 5 | * connected to the server by responding to IRC "PING" commands! 6 | * Do not remove or disable, or the bot will be kicked within 7 | * minutes of connecting! 8 | */ 9 | 10 | // This script handles the following functions: 11 | // Responds to the PING command with a PONG command. 12 | 13 | listen(/^PING :(.+)/i, function(match) { 14 | irc.pong(match[1]); 15 | }); 16 | -------------------------------------------------------------------------------- /scripts/rockpaperscissors.js: -------------------------------------------------------------------------------- 1 | // (c) 2012 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~rps {r|p|s} - Play rock-paper-scissors. 6 | 7 | function isValidRPS(choice) { 8 | return choice.length === 1 && "rps".indexOf(choice) !== -1; 9 | } 10 | 11 | listen(regexFactory.startsWith("rps"), function(match, data, replyTo, from) { 12 | var botChoice = Math.floor(Math.random() * 3); 13 | var playerChoice = match[1].toLowerCase(); 14 | 15 | if (!isValidRPS(match[1])) { 16 | irc.privmsg(replyTo, "Usage: rps (r|p|s)"); 17 | return; 18 | } 19 | 20 | var output = "chose "; 21 | if(botChoice == 0) { 22 | output += "rock"; 23 | } else if(botChoice == 1) { 24 | output += "paper"; 25 | } else { 26 | output += "scissors"; 27 | } 28 | output += ". "; 29 | if( 30 | (playerChoice == 'r' && botChoice == 2) || 31 | (playerChoice == 'p' && botChoice == 0) || 32 | (playerChoice == 's' && botChoice == 1) 33 | ) { 34 | output += from + " wins!"; 35 | } else if( 36 | (playerChoice == 'r' && botChoice == 0) || 37 | (playerChoice == 'p' && botChoice == 1) || 38 | (playerChoice == 's' && botChoice == 2) 39 | ) { 40 | output += "We tied. How about a nice game of chess?"; 41 | } else { 42 | output += from + " loses. :-P"; 43 | } 44 | 45 | irc.action(replyTo, output); 46 | }); 47 | 48 | -------------------------------------------------------------------------------- /scripts/sayitagain.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // When someone says something that starts with "say", repeats what was to be said. Example: 6 | // say it again 7 | // it again 8 | 9 | listen(regexFactory.startsWith("say", "optional"), function(match, data, replyTo) { 10 | irc.privmsg(replyTo, match[1]); 11 | }); 12 | -------------------------------------------------------------------------------- /scripts/smack.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~smack user - smacks user with a random object 6 | // ~slap user - slaps user with a random object 7 | 8 | require('./config.js'); 9 | 10 | var verbs = ["smack", "slap", "hit", "pummel"], 11 | smackThings = ["smelly fish", "tin pot", "frying pan", "mouse", 12 | "keyboard", "fly swatter", "old boot"], 13 | pronouns = ["me", "you", "himself", "herself", "itself", "yourself", 14 | "self", nodebot_prefs.nickname]; 15 | 16 | function randomThing() { 17 | return _.sample(smackThings); 18 | } 19 | 20 | function isPronoun(str) { 21 | return _.any(pronouns, function (pronoun) { 22 | return str.toUpperCase() === pronoun.toUpperCase(); 23 | }); 24 | } 25 | 26 | function smack(verb, recipient, replyTo) { 27 | irc.action(replyTo, verb + "s " + recipient + " with a " + randomThing() + "."); 28 | } 29 | 30 | _.each(verbs, function(verb) { 31 | listen(regexFactory.startsWith(verb), function(match, data, replyTo, from) { 32 | var target = match[1].trim(); 33 | 34 | if (isPronoun(target)) { 35 | smack(verb, from, replyTo); 36 | } else { 37 | smack(verb, target, replyTo); 38 | } 39 | }); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /scripts/tell.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~tell someone something - Saves the message to tell the person when they get back 6 | // On user join, looks to see if the new person has any saved messages and if so, says them 7 | 8 | var db = require('./lib/listdb').getDB('messages'); 9 | 10 | function addMessage(room, user, message) { 11 | db.add(room+"@"+user+": "+message); 12 | } 13 | 14 | function getMessages(room, user) { 15 | var i, messages, prefix, userMessages, messagesToRemove; 16 | userMessages = [], messagesToRemove = []; 17 | prefix = room + "@" + user + ": "; 18 | 19 | messages = db.getAll(); 20 | for (i in messages) { 21 | if (messages[i].toLowerCase().indexOf(prefix.toLowerCase()) == 0) { 22 | messagesToRemove.push(messages[i]); 23 | userMessages.push(messages[i].substr(prefix.length)); 24 | } 25 | } 26 | 27 | for (i in messagesToRemove) { 28 | db.remove(messagesToRemove[i], true); 29 | } 30 | 31 | return userMessages; 32 | } 33 | 34 | function isUser(str) { 35 | return str[0] !== "#"; 36 | } 37 | 38 | listen(regexFactory.startsWith("tell"), function(match, data, replyTo, from) { 39 | var msgMatch = /^([^ ]+) (.+)$/.exec(match[1]); 40 | 41 | if (msgMatch && isUser(msgMatch[1])) { 42 | addMessage(replyTo, msgMatch[1], "message from " + from + ": " + msgMatch[2]); 43 | 44 | irc.privmsg(replyTo, "I'll tell them when they get back."); 45 | } else { 46 | irc.privmsg(replyTo, "Usage: tell {user} {some message}"); 47 | } 48 | }); 49 | 50 | // listen for join 51 | listen(/:([^!]+)!.*JOIN :?(.*)$/i, function(match) { 52 | // search tell folder for any messages to give 53 | var i, userMessages = getMessages(match[2], match[1]); 54 | for (i in userMessages) { 55 | irc.privmsg(match[2], match[1] + ", " + userMessages[i]); 56 | } 57 | }); 58 | 59 | -------------------------------------------------------------------------------- /scripts/time.js: -------------------------------------------------------------------------------- 1 | // (c) 2011 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // ~(date|time) - gives the current date/time 6 | 7 | listen(regexFactory.only(["date", "time"]), function(match, data, replyTo) { 8 | irc.privmsg(replyTo, "Current datetime is " + (new Date()).toString()); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /scripts/title.js: -------------------------------------------------------------------------------- 1 | // (c) 2012 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // some url - look up the url's title and announce it 6 | 7 | var entities = require('./lib/entities'), 8 | fs = require('fs'), 9 | exec = require('child_process').exec; 10 | 11 | var request = require('request').defaults({ 12 | strictSSL: false, 13 | timeout: 10000, 14 | encoding: 'utf8', 15 | headers: { 16 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36' 17 | } 18 | }); 19 | 20 | listen(/PRIVMSG [^ ]+ :.*?\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?]))/i, function(match, data, replyTo) { 21 | var url = match[1]; 22 | 23 | if(url.indexOf('http') != 0) { 24 | url = 'http://' + url; 25 | } 26 | 27 | console.log('title: Found url: ' + url); 28 | 29 | var maxSize = 10485760; 30 | 31 | request({url: url, method: 'HEAD'}, function (error, headRes) { 32 | if (error) { 33 | if((""+error).indexOf('SSL routines') < 0) { 34 | irc.privmsg(replyTo, "Error looking up URL: " + error); 35 | } 36 | return; 37 | } 38 | 39 | var size = headRes.headers['content-length']; 40 | if (size && size > maxSize) { 41 | console.log('Page size too large: ' + size); 42 | return; 43 | } 44 | 45 | var req = request({url: url}); 46 | req.on('error', function (error) { 47 | if((""+error).indexOf('SSL routines') < 0) { 48 | irc.privmsg(replyTo, "Error looking up URL: " + error); 49 | } 50 | }); 51 | var hostname = ""; 52 | req.on('response', function (response) { 53 | hostname = response.request.host; 54 | 55 | var statusCode = response.statusCode; 56 | if (statusCode != 200) { 57 | req.abort(); 58 | // Ignore 403 Access Forbidden; some websites block bots with this 59 | // code (e.g. Wikipedia). 60 | if (statusCode != 403) { 61 | irc.privmsg(replyTo, "" + response.statusCode); 62 | } 63 | } 64 | }); 65 | 66 | var pageBody = "", foundTitle = false; 67 | req.on('data', function (data) { 68 | if (foundTitle) { 69 | return; 70 | } 71 | pageBody += data; 72 | if (pageBody.length > maxSize) { 73 | console.log('Page size exceeded limit (' + pageBody.length + ')'); 74 | req.abort(); 75 | } 76 | 77 | var titleMatch = /]+)?>([^<]+)<\/title>/i.exec(pageBody); 78 | 79 | if(titleMatch && titleMatch[1]) { 80 | req.abort(); 81 | foundTitle = true; 82 | 83 | var title = titleMatch[1]; 84 | 85 | // replace multi-spaces/newlines with spaces 86 | title = title.replace(/\s{2,}/g, " "); 87 | 88 | // trim front and back 89 | title = title.replace(/^\s+/, ""); 90 | title = title.replace(/\s+$/, ""); 91 | 92 | // decode HTML entities 93 | title = entities.decode(title); 94 | 95 | irc.privmsg(replyTo, hostname + " : " + title, false); 96 | } 97 | }); 98 | 99 | }); 100 | 101 | }); 102 | 103 | -------------------------------------------------------------------------------- /scripts/twitter.js: -------------------------------------------------------------------------------- 1 | // (c) 2012 Richard Carter 2 | // This code is licensed under the MIT license; see LICENSE.txt for details. 3 | 4 | // This script handles the following functions: 5 | // a twitter url - look up the tweet and announce it 6 | 7 | require('./config.js'); 8 | var timeago = require('timeago'), 9 | oauth = require('oauth'); 10 | 11 | function numberWithCommas(x) { 12 | // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript 13 | return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 14 | } 15 | 16 | var twitterAccessToken = null; 17 | var twitterOauth = new oauth.OAuth2( 18 | nodebot_prefs.twitter_consumerkey, 19 | nodebot_prefs.twitter_secret, 20 | 'https://api.twitter.com/', 21 | null, 22 | '/oauth2/token', 23 | null 24 | ); 25 | twitterOauth.useAuthorizationHeaderforGET(true); 26 | 27 | twitterOauth.getOAuthAccessToken( 28 | '', 29 | {'grant_type': 'client_credentials'}, 30 | function (e, access_token, refresh_token, results) { 31 | if (e) { 32 | console.error(e); 33 | return; 34 | } 35 | console.log('Got Twitter bearer token'); 36 | twitterAccessToken = access_token; 37 | } 38 | ); 39 | 40 | listen(regexFactory.matches(".*?(?:https?://)?(?:www\\.)?twitter.com/(?:#!/)?[a-z0-9]+/status/([0-9]+)", false), 41 | function(match, data, replyTo) { 42 | if (!twitterAccessToken) { 43 | irc.privmsg(replyTo, "Twitter API not yet connected"); 44 | return; 45 | } 46 | 47 | var tweetId = match[1]; 48 | 49 | twitterOauth.get( 50 | 'https://api.twitter.com/1.1/statuses/show.json?id=' + tweetId, 51 | twitterAccessToken, 52 | function (e, data, res) { 53 | if (e) { 54 | if (e.data) { 55 | try { 56 | var errorData = JSON.parse(e.data); 57 | if (errorData.errors && errorData.errors[0] && 58 | errorData.errors[0].message) { 59 | irc.privmsg(replyTo, "Twitter error: " + 60 | errorData.errors[0].message); 61 | } 62 | } catch (e) {} 63 | } 64 | console.error("Twitter error", e); 65 | return; 66 | } 67 | try { 68 | data = JSON.parse(data); 69 | var prettyDate = timeago(data.created_at); 70 | 71 | irc.privmsg(replyTo, "" + data.text + " -- " + data.user.name + " (@" + 72 | data.user.screen_name + "), " + prettyDate); 73 | } catch (e) { 74 | irc.privmsg(replyTo, "Twitter error: " + e); 75 | } 76 | 77 | }); 78 | 79 | // do not allow title plugin to process url 80 | return true; 81 | }); 82 | 83 | listen(regexFactory.matches(".*?(?:https?://)?(?:www\\.)?twitter.com/(?:#!/)?([a-z0-9]+)/?", false), 84 | function(match, data, replyTo) { 85 | if (!twitterAccessToken) { 86 | irc.privmsg(replyTo, "Twitter API not yet connected"); 87 | return; 88 | } 89 | 90 | var screenName = match[1]; 91 | 92 | twitterOauth.get( 93 | 'https://api.twitter.com/1.1/users/show.json?screen_name=' + screenName, 94 | twitterAccessToken, 95 | function (e, data, res) { 96 | if (e) { 97 | if (e.data) { 98 | try { 99 | var errorData = JSON.parse(e.data); 100 | if (errorData.errors && errorData.errors[0] && 101 | errorData.errors[0].message) { 102 | irc.privmsg(replyTo, "Twitter error: " + 103 | errorData.errors[0].message); 104 | } 105 | } catch (e) {} 106 | } 107 | console.error("Twitter error", e); 108 | return; 109 | } 110 | try { 111 | data = JSON.parse(data); 112 | 113 | var dataUrl = data.url; 114 | if (data.entities && data.entities.url && data.entities.url.urls) { 115 | var entitiesUrls = data.entities.url.urls; 116 | for (var urlIdx = 0; urlIdx < entitiesUrls.length; urlIdx++) { 117 | if (entitiesUrls[urlIdx].url == dataUrl && entitiesUrls[urlIdx].expanded_url) { 118 | dataUrl = entitiesUrls[urlIdx].expanded_url; 119 | } 120 | } 121 | } 122 | irc.privmsg(replyTo, "" + data.name + " (@" + 123 | data.screen_name + "; " + numberWithCommas(data.followers_count) + " followers): " + (data.description || "(no description)") + 124 | (dataUrl ? " -- " + dataUrl : "")); 125 | } catch (e) { 126 | irc.privmsg(replyTo, "Twitter error: " + e); 127 | } 128 | 129 | }); 130 | 131 | // do not allow title plugin to process url 132 | return true; 133 | }); 134 | --------------------------------------------------------------------------------