forked from GitHub/dbot
		
	Merge reality's stuff
This commit is contained in:
		
						commit
						feb51aaceb
					
				
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,9 @@ | ||||
| [submodule "jsbot"] | ||||
| 	path = jsbot | ||||
| 	url = git://github.com/reality/jsbot.git | ||||
| [submodule "modules/stats"] | ||||
| 	path = modules/stats | ||||
| 	url = git://github.com/SamStudio8/stats.git | ||||
| [submodule "modules/github"] | ||||
| 	path = modules/github | ||||
| 	url = git://github.com/zuzak/dbot-github.git | ||||
|  | ||||
							
								
								
									
										18
									
								
								LICENCE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								LICENCE
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| Copyright (c) 2012-2013 Luke Slater (tinmachin3@gmail.com) | ||||
| 
 | ||||
| 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: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| 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. | ||||
							
								
								
									
										111
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										111
									
								
								README.md
									
									
									
									
									
								
							| @ -3,8 +3,8 @@ | ||||
| ## Introduction | ||||
| 
 | ||||
| Depressionbot is an IRC bot which aims to be the fanciest IRC bot around - On | ||||
| the general standard of software fanciness, dbot is rated as being '81% the same | ||||
| as bathing in fine, fine grape juice.' | ||||
| the general standard of software fanciness, dbot is statistically rated as being  | ||||
| '82% the same as bathing in fine, fine grape juice.' | ||||
| 
 | ||||
| Please note that this documentation is not complete and is a work in progress,  | ||||
| given I started it rather a long time after I began development of the project.  | ||||
| @ -15,111 +15,14 @@ Requirements: | ||||
| 
 | ||||
| - Node JS | ||||
| - [JSBot](http://github.com/reality/JSBot "JSBot"), a Javascript library which | ||||
|   handles the IRC protocol. | ||||
|   handles the IRC protocol | ||||
| - Underscore JS library | ||||
| - Various modules have their own requirements also. | ||||
| 
 | ||||
| ### JSBot | ||||
| ### External Modules | ||||
| 
 | ||||
| JSBot can be imported by running the following commands in the cloned repository: | ||||
| JSBot and externally developed modules can be imported by running the following  | ||||
| commands in the cloned repository: | ||||
| 	 | ||||
| 	git submodule init | ||||
| 	git submodule update | ||||
| 
 | ||||
| ## Modules: | ||||
| 
 | ||||
| ### Command | ||||
| 
 | ||||
| This handles the command execution logic for DBot. | ||||
| 
 | ||||
| 1. Does the input match a command key in *dbot.commands* ? | ||||
| 2. Is there a quote category which matches the first part of the input | ||||
|    (*~category*)? | ||||
| 3. Is there a command name similar to to the first part of the input (*~name*) | ||||
|    in *dbot.commands*? | ||||
| 
 | ||||
| This is the only module which is force loaded, even if it's not in the | ||||
| configuration. | ||||
| 
 | ||||
| ### Quotes | ||||
| 
 | ||||
| This is the original reason that DBot was created, stores and displays quotes. | ||||
| 
 | ||||
| Commands: | ||||
| 
 | ||||
| - _~q category_ - Display a random quote from a given category. | ||||
| - _~qadd category=newquote_ - Add a new quote to the database. | ||||
| - _~qstats_ - Show a list of the biggest quote categories. | ||||
| - _~qsearch category needle_ - Search for a quote in a given category. | ||||
| - _~rmlast [category]_ - Remove the last quote added to a given category, or the | ||||
|   last quote added. | ||||
| - _~rm category quote_ - Remove a given quote from the given category. | ||||
| - _~qcount category_ - Show the number of quotes stored in the given category. | ||||
| - _~rq_ - Show a random quote from a random category. | ||||
| - _~d_ - Show a quote from the category which matches the bot's name. | ||||
| - _~link category_ - Create a link to the page on the web interface which displays the | ||||
|   given category's quotes. | ||||
| - _~qprune_ - Delete empty quote categories. | ||||
| 
 | ||||
| Unfortunately, this module is fairly highly coupled with certain other areas of | ||||
| the program. I am working on this, but note, for example, that one can still | ||||
| access quotes with the *~category* syntax even if the quotes module isn't | ||||
| loaded. | ||||
| 
 | ||||
| ### Admin | ||||
| 
 | ||||
| Various administration functionality such as banning users, hot-reloading the  | ||||
| code and ordering him to talk. Note that commands added here are handled with | ||||
| their own listener, rather than being part of the command logic which is handled | ||||
| by the Command module. Functionality in this module can be slightly unsafe as | ||||
| not much error checking on the input is performed. | ||||
| 
 | ||||
| TODO: Add summaries for each command in this module. | ||||
| 
 | ||||
| ### Spelling  | ||||
| 
 | ||||
| Will attempt to correct a users' spelling by using the levenshtein distance | ||||
| algorithm. One corrects the spelling of their previous message by simply posting | ||||
| a message with their correction and an asterisk: | ||||
| 
 | ||||
|     > user: I am a tutrle. | ||||
|     > user: *turtle | ||||
|     user meant: I am a turtle. | ||||
| 
 | ||||
| The regular expression for this module also accepts two asterisks at the | ||||
| beginning of the correction, or at the end; it also accepts several words as the | ||||
| correction and deals with these fairly intelligently. Users may also attempt  | ||||
| to correct another users like so: | ||||
| 
 | ||||
|     > userone: I am a tutrle. | ||||
|     > usertwo: userone: *turtle | ||||
|     > usertwo thinks userone meant: I am a turtle. | ||||
| 
 | ||||
| ### JS | ||||
| 
 | ||||
| This module provides two commands which allow the execution of Javascript code. | ||||
| For regular users, there is the *~js* command, which is completely sandboxed, | ||||
| but can still be used for calculation and the like. | ||||
| 
 | ||||
|     > ~js Array(16).join('wat'-1) + " Batman!"; | ||||
|     'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!' | ||||
| 
 | ||||
| This feature is fairly safe as the user doesn't have access to anything | ||||
| dangerous, and is safe from infinite loops or locking DBot up because the code | ||||
| which is run is killed if it does not finish within a short amount of time. | ||||
| 
 | ||||
| For administrators, the incredibly useful *~ajs* command is also available. The | ||||
| input for this command is simply 'eval'-ed and therefore has full access to | ||||
| DBot's memory. Of course, this is incredibly unsafe, but I find it rather fun; | ||||
| remember to only give extremely trusted friends administrator access to DBot, as | ||||
| there's nothing to stop them wiping the database or something similar - if | ||||
| you're worried about that kind of thing, do not load this module. | ||||
| 
 | ||||
| However, it's useful for many things, such as administrative activity for  | ||||
| which there isn't a command in the admin module. For example, you could hot-add | ||||
| a new administrator like this: | ||||
| 
 | ||||
|     > ~ajs dbot.admin.push('batman'); | ||||
|     2 | ||||
| 
 | ||||
| You can also use this for debugging, or even adding new commands while DBot is | ||||
| running. | ||||
|  | ||||
							
								
								
									
										5
									
								
								VERSION
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								VERSION
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| depressionbot version 0.4-dev | ||||
| 
 | ||||
| "the database is a grilled cheese" | ||||
| _.each(dbot.modules, function(module) { "RESCORE EVERYTHING" }); | ||||
| "He called his bot depressionbot, and that's when he was happy." | ||||
| @ -12,6 +12,8 @@ | ||||
|         } | ||||
|     }, | ||||
|     "admins": [ "batman" ], | ||||
|     "moduleNames": [ "ignore", "admin", "command", "dice", "js", "kick", "puns", "quotes", "spelling", "youare" ], | ||||
|     "language": "english" | ||||
|     "moderators": [ "whatever" ], | ||||
|     "moduleNames": [ "ignore", "admin", "command", "dice", "js", "kick", "quotes", "spelling", "youare", "stats", "users" ], | ||||
|     "language": "english", | ||||
|     "debugMode": true | ||||
| } | ||||
|  | ||||
							
								
								
									
										35
									
								
								install.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										35
									
								
								install.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,35 @@ | ||||
| #!/bin/bash | ||||
| cat LICENCE | ||||
| git submodule init | ||||
| git submodule update | ||||
| 
 | ||||
| npm install underscore request sandbox express moment jade@0.25 | ||||
| 
 | ||||
| cd public/ | ||||
| wget http://twitter.github.com/bootstrap/assets/bootstrap.zip | ||||
| unzip bootstrap.zip | ||||
| rm bootstrap.zip | ||||
| 
 | ||||
| mkdir d3 | ||||
| cd d3 | ||||
| wget http://d3js.org/d3.v3.zip | ||||
| unzip d3.v3.zip | ||||
| rm d3.v3.zip | ||||
| 
 | ||||
| cd ../.. | ||||
| 
 | ||||
| if [ ! -f config.json ]; | ||||
| then | ||||
|     echo 'Creating configuration file...' | ||||
|     cp config.json.sample config.json | ||||
|     vim config.json | ||||
| fi | ||||
| 
 | ||||
| read -p "Setup complete. Run depressionbot now? [y/N]" | ||||
| if [[ ! $REPLY =~ ^[Yy]$ ]] | ||||
| then | ||||
|     echo 'Okay. To run the bot, use "node run.js"' | ||||
|     exit | ||||
| fi | ||||
| node run.js | ||||
| 
 | ||||
							
								
								
									
										2
									
								
								jsbot
									
									
									
									
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								jsbot
									
									
									
									
									
								
							| @ -1 +1 @@ | ||||
| Subproject commit dbd987551bd8ce68655bbedb6bc5e98e90557708 | ||||
| Subproject commit 35910d9025fa3af15b24cecc3f6e7ee897aee4dc | ||||
							
								
								
									
										57
									
								
								modules/admin/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								modules/admin/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| ## Admin | ||||
| 
 | ||||
| Administrator functionality. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| Various administration functionality such as banning users, hot-reloading the  | ||||
| code and ordering him to talk. Note that commands added here are handled with | ||||
| their own listener, rather than being part of the command logic which is handled | ||||
| by the Command module. Functionality in this module can be slightly unsafe as | ||||
| not everything is thoroughly sanity checked. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### join [#channel] | ||||
| Join the given channel. | ||||
| 
 | ||||
| #### part [#channel] | ||||
| Leave the given channel. | ||||
| 
 | ||||
| #### opme [#channel] | ||||
| Gives the caller ops in a given channel if possible. If called without a | ||||
| channel, it will attempt to give the caller ops in the current channel. | ||||
| 
 | ||||
| #### greload | ||||
| Perform a git pull, and then execute the 'reload' command. Saves a lot of time | ||||
| updating! | ||||
| 
 | ||||
| #### reload | ||||
| Reload all of the modules currently in use by DBot. By using this, all module | ||||
| functionality should be reloadable and replaceable without having to restart the | ||||
| bot or interrupt the connection to the server. | ||||
| 
 | ||||
| #### say [#channel] [message] | ||||
| Have DBot post the given message in the given channel (uses the server from | ||||
| which you are sending the message). You may replace channel with '@' to have him | ||||
| post the message in the current channel. Channel may also be replaced with a | ||||
| nick on the server. | ||||
| 
 | ||||
| #### load [module] | ||||
| Load a new module. This works by adding a module name to the roster and then | ||||
| triggering a reload of all modules, at which point the new module is actually | ||||
| loaded by the standard DBot process. | ||||
| 
 | ||||
| #### unload [module] | ||||
| Unload a currently loaded module. This removes the module, and then triggers a | ||||
| reload of all modules. | ||||
| 
 | ||||
| #### ban [user] [command] | ||||
| Ban a user from using a command. Command may be replaced with '\*,' which will | ||||
| ban a user from use of all commands. Users banned from all commands will still | ||||
| be subject to module listeners. | ||||
| 
 | ||||
| #### unban [user] [command] | ||||
| Unban a user from using a given command. If a user was previously banned using | ||||
| the '\*' wildcard, they may also be unbanned from such by replacing command with | ||||
| an asterisk here as well. | ||||
| @ -3,147 +3,12 @@ | ||||
|  * Description: Set of commands which only one who is a DepressionBot | ||||
|  * administrator can run - as such, it has its own command execution listener. | ||||
|  */ | ||||
| var fs = require('fs'); | ||||
| var sys = require('sys') | ||||
| var exec = require('child_process').exec; | ||||
| var fs = require('fs'), | ||||
|     _ = require('underscore')._; | ||||
| 
 | ||||
| var admin = function(dbot) { | ||||
|     var commands = { | ||||
|         // Join a channel
 | ||||
|         'join': function(event) { | ||||
|             var channel = event.params[1]; | ||||
|             if(event.allChannels.hasOwnProperty(channel)) { | ||||
|                 event.reply("I'm already in that channel."); | ||||
|             } else { | ||||
|                 dbot.instance.join(event, channel);  | ||||
|                 event.reply(dbot.t('join', {'channel': channel})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Leave a channel
 | ||||
|         'part': function(event) { | ||||
|             var channel = event.params[1]; | ||||
|             if(!event.allChannels.hasOwnProperty(channel)) { | ||||
|                 event.reply("I'm not in that channel."); | ||||
|             } else { | ||||
|                 event.instance.part(event, channel);  | ||||
|                 event.reply(dbot.t('part', {'channel': channel})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Op admin caller in given channel
 | ||||
|         'opme': function(event) { | ||||
|             var channel = event.params[1]; | ||||
| 
 | ||||
|             // If given channel isn't valid just op in current one.
 | ||||
|             if(!event.allChannels.hasOwnProperty(channel)) { | ||||
|                 channel = event.channel.name; | ||||
|             } | ||||
|             dbot.instance.mode(event, channel, ' +o ' + event.user); | ||||
|         }, | ||||
| 
 | ||||
|         // Do a git pull and reload
 | ||||
|         'greload': function(event) { | ||||
|             var child = exec("git pull", function (error, stdout, stderr) { | ||||
|                 event.reply(dbot.t('gpull')); | ||||
|                 commands.reload(event); | ||||
|             }.bind(this)); | ||||
|         }, | ||||
| 
 | ||||
|         // Reload DB, translations and modules.
 | ||||
|         'reload': function(event) { | ||||
|             dbot.db = JSON.parse(fs.readFileSync('db.json', 'utf-8')); | ||||
|             dbot.reloadModules(); | ||||
|             event.reply(dbot.t('reload')); | ||||
|         }, | ||||
| 
 | ||||
|         // Say something in a channel (TODO probably doesn't work.)
 | ||||
|         'say': function(event) { | ||||
|             var channel = event.params[1]; | ||||
|             if(event.params[1] === "@") { | ||||
|                 var channel = event.channel.name; | ||||
|             }              | ||||
|             var message = event.params.slice(2).join(' '); | ||||
|             dbot.say(event.server, channel, message); | ||||
|         }, | ||||
| 
 | ||||
|         // Load new module 
 | ||||
|         'load': function(event) { | ||||
|             var moduleName = event.params[1]; | ||||
|             dbot.config.moduleNames.push(moduleName); | ||||
|             dbot.reloadModules(); | ||||
|             event.reply(dbot.t('load_module', {'moduleName': moduleName})); | ||||
|         }, | ||||
| 
 | ||||
|         // Unload a loaded module
 | ||||
|         'unload': function(event) { | ||||
|             var moduleNames = dbot.config.moduleNames; | ||||
|             var moduleName = event.params[1]; | ||||
|             if(moduleNames.include(moduleName)) { | ||||
|                 var moduleDir = '../' + moduleName + '/'; | ||||
|                 var cacheKey = require.resolve(moduleDir + moduleName); | ||||
|                 delete require.cache[cacheKey]; | ||||
| 
 | ||||
|                 var moduleIndex = moduleNames.indexOf(moduleName); | ||||
|                 moduleNames.splice(moduleIndex, 1); | ||||
|                 dbot.reloadModules(); | ||||
| 
 | ||||
|                 event.reply(dbot.t('unload_module', {'moduleName': moduleName})); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('unload_error', {'moduleName': moduleName})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Ban user from command or *
 | ||||
|         'ban': function(event) { | ||||
|             var username = event.params[1]; | ||||
|             var command = event.params[2]; | ||||
| 
 | ||||
|             if(!dbot.db.bans.hasOwnProperty(command)) { | ||||
|                 dbot.db.bans[command] = [ ]; | ||||
|             } | ||||
|             dbot.db.bans[command].push(username); | ||||
|             event.reply(dbot.t('banned', {'user': username, 'command': command})); | ||||
|         }, | ||||
| 
 | ||||
|         // Unban a user from command or *
 | ||||
|         'unban': function(event) { | ||||
|             var username = event.params[1]; | ||||
|             var command = event.params[2]; | ||||
|             if(dbot.db.bans.hasOwnProperty(command) && dbot.db.bans[command].include(username)) { | ||||
|                 dbot.db.bans[command].splice(dbot.db.bans[command].indexOf(username), 1); | ||||
|                 event.reply(dbot.t('unbanned', {'user': username, 'command': command})); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('unban_error', {'user': username})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Lock quote category so quotes can't be removed
 | ||||
|         'lock': function(event) { | ||||
|             var category = event.params[1]; | ||||
|             dbot.db.locks.push(category); | ||||
|             event.reply(dbot.t('qlock', {'category': category})); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'admin', | ||||
|         'ignorable': false, | ||||
| 
 | ||||
|         /** | ||||
|          * Run the appropriate admin command given the input (and user). | ||||
|          */ | ||||
|         'listener': function(event) { | ||||
|             var commandName = event.params[0]; | ||||
|             if(commands.hasOwnProperty(commandName) && dbot.config.admins.include(event.user)) { | ||||
|                 commands[commandName](event); | ||||
|                 dbot.save(); | ||||
|             } | ||||
|         }, | ||||
|         'on': 'PRIVMSG' | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return admin(dbot); | ||||
|     return new admin(dbot); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										312
									
								
								modules/admin/commands.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								modules/admin/commands.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,312 @@ | ||||
| var fs = require('fs'), | ||||
|     _ = require('underscore')._, | ||||
|     sys = require('sys'), | ||||
|     exec = require('child_process').exec; | ||||
| 
 | ||||
| var commands = function(dbot) { | ||||
|     var noChangeConfig = [ 'servers', 'name', 'moduleNames' ]; | ||||
| 
 | ||||
|     var getCurrentConfig = function(configKey) { | ||||
|         var defaultConfigPath = dbot.config; | ||||
|         var userConfigPath = dbot.db.config; | ||||
| 
 | ||||
|         if(configKey) { | ||||
|             var configKey = configKey.split('.'); | ||||
|             for(var i=0;i<configKey.length-1;i++) { | ||||
|                 if(_.has(defaultConfigPath, configKey[i])) { | ||||
|                     if(!_.has(userConfigPath, configKey[i])) { | ||||
|                         userConfigPath[configKey[i]] = {}; | ||||
|                     } | ||||
|                     userConfigPath = userConfigPath[configKey[i]]; | ||||
|                     defaultConfigPath = defaultConfigPath[configKey[i]]; | ||||
|                 } else { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         }  | ||||
| 
 | ||||
|         var currentOption; | ||||
|         if(configKey.length != 1) { | ||||
|             configKey = _.last(configKey); | ||||
|             if(_.has(userConfigPath, configKey) && !_.isUndefined(userConfigPath[configKey])) { | ||||
|                 currentOption = userConfigPath[configKey]; | ||||
|             } else if(_.has(defaultConfigPath, configKey)) { | ||||
|                 currentOption = defaultConfigPath[configKey]; | ||||
|             } | ||||
|         } else { | ||||
|             currentOption = defaultConfigPath[configKey]; | ||||
|         } | ||||
| 
 | ||||
|         return {  | ||||
|             'user': userConfigPath, | ||||
|             'default': defaultConfigPath, | ||||
|             'value': currentOption | ||||
|         }; | ||||
|    }; | ||||
| 
 | ||||
|     var commands = { | ||||
|         // Join a channel
 | ||||
|         'join': function(event) { | ||||
|             var channel = event.params[1]; | ||||
|             if(_.has(event.allChannels, channel)) { | ||||
|                 event.reply(dbot.t('already_in_channel', {'channel': channel})); | ||||
|             } else { | ||||
|                 dbot.instance.join(event, channel);  | ||||
|                 event.reply(dbot.t('join', {'channel': channel})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Leave a channel
 | ||||
|         'part': function(event) { | ||||
|             var channel = event.params[1]; | ||||
|             if(!_.has(event.allChannels, channel)) { | ||||
|                 event.reply(dbot.t('not_in_channel', {'channel': channel})); | ||||
|             } else { | ||||
|                 event.instance.part(event, channel);  | ||||
|                 event.reply(dbot.t('part', {'channel': channel})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Op admin caller in given channel
 | ||||
|         'opme': function(event) { | ||||
|             var channel = event.params[1]; | ||||
| 
 | ||||
|             // If given channel isn't valid just op in current one.
 | ||||
|             if(!_.has(event.allChannels, channel)) { | ||||
|                 channel = event.channel.name; | ||||
|             } | ||||
|             dbot.instance.mode(event, channel, ' +o ' + event.user); | ||||
|         }, | ||||
| 
 | ||||
|         // Do a git pull and reload
 | ||||
|         'greload': function(event) { | ||||
|             exec("git pull", function (error, stdout, stderr) { | ||||
|                 exec("git submodule update", function (error, stdout, stderr) { | ||||
|                     event.reply(dbot.t('gpull')); | ||||
|                     commands.reload(event); | ||||
|                     event.message = 'version'; | ||||
|                     event.action = 'PRIVMSG';                                        | ||||
|                     event.params = event.message.split(' ');                         | ||||
|                     dbot.instance.emit(event);   | ||||
|                 }.bind(this)); | ||||
|             }.bind(this)); | ||||
|         }, | ||||
| 
 | ||||
|         // Display commit information for part of dbot
 | ||||
|         'version': function(event){ | ||||
|             var cmd = "git log --pretty=format:'%h (%s): %ar' -n 1 -- "; | ||||
|             if(event.params[1]){ | ||||
|                 var input = event.params[1].trim(); | ||||
|                 if(_.has(dbot.modules, input.split("/")[0])){ | ||||
|                     cmd += "modules/"+input; | ||||
|                 } | ||||
|                 else{ | ||||
|                     cmd += input; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             exec(cmd, function(error, stdout, stderr){ | ||||
|                 if(stdout.length > 0){ | ||||
|                     event.reply(stdout); | ||||
|                 } | ||||
|                 else{ | ||||
|                     event.reply("No version information or queried module not loaded"); | ||||
|                 } | ||||
|             }.bind(this)); | ||||
|         }, | ||||
| 
 | ||||
| 
 | ||||
|         'status': function(event) { | ||||
|             var moduleName = event.params[1]; | ||||
|             if(_.has(dbot.status, moduleName)) { | ||||
|                 var status = dbot.status[moduleName]; | ||||
|                 if(status === true) { | ||||
|                     event.reply(moduleName + ' status: Shit looks good.'); | ||||
|                 } else { | ||||
|                     event.reply(moduleName + ' status: Failed to load: ' + status);  | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply('Either that module wasn\'t on the roster or shit is totally fucked.'); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Reload DB, translations and modules.
 | ||||
|         'reload': function(event) { | ||||
|             dbot.db = JSON.parse(fs.readFileSync('db.json', 'utf-8')); | ||||
|             dbot.reloadModules(); | ||||
|             event.reply(dbot.t('reload')); | ||||
|         }, | ||||
| 
 | ||||
|         // Say something in a channel
 | ||||
|         'say': function(event) { | ||||
|             var channel = event.params[1]; | ||||
|             if(event.params[1] === "@") { | ||||
|                 var channel = event.channel.name; | ||||
|             }              | ||||
|             var message = event.params.slice(2).join(' '); | ||||
|             dbot.say(event.server, channel, message); | ||||
|         }, | ||||
| 
 | ||||
|         // Load new module 
 | ||||
|         'load': function(event) { | ||||
|             var moduleName = event.params[1]; | ||||
|             if(!_.include(dbot.config.moduleNames, moduleName)) { | ||||
|                 dbot.config.moduleNames.push(moduleName); | ||||
|                 dbot.reloadModules(); | ||||
|                 if(dbot.status[moduleName] === true) { | ||||
|                     event.reply(dbot.t('load_module', {'moduleName': moduleName})); | ||||
|                 } else { | ||||
|                     event.reply('Failed to load ' + moduleName + '. See \'status ' + moduleName + '\'.'); | ||||
|                 } | ||||
|             } else { | ||||
|                 if(moduleName == 'web') { | ||||
|                     event.reply(dbot.t('already_loaded_web')); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('already_loaded', {'moduleName': moduleName})); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Unload a loaded module
 | ||||
|         'unload': function(event) { | ||||
|             var moduleNames = dbot.config.moduleNames; | ||||
|             var moduleName = event.params[1]; | ||||
|             if(_.include(moduleNames, moduleName)) { | ||||
|                 var moduleDir = '../' + moduleName + '/'; | ||||
|                 var cacheKey = require.resolve(moduleDir + moduleName); | ||||
|                 delete require.cache[cacheKey]; | ||||
|                 dbot.config.moduleNames = _.without(dbot.config.moduleNames, moduleName); | ||||
|                 dbot.reloadModules(); | ||||
| 
 | ||||
|                 event.reply(dbot.t('unload_module', {'moduleName': moduleName})); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('unload_error', {'moduleName': moduleName})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Ban user from command or *
 | ||||
|         'ban': function(event) { | ||||
|             var username = event.params[1]; | ||||
|             var command = event.params[2]; | ||||
| 
 | ||||
|             if(!_.has(dbot.db.bans, command)) { | ||||
|                 dbot.db.bans[command] = [ ]; | ||||
|             } | ||||
|             dbot.db.bans[command].push(username); | ||||
|             event.reply(dbot.t('banned', {'user': username, 'command': command})); | ||||
|         }, | ||||
| 
 | ||||
|         // Unban a user from command or *
 | ||||
|         'unban': function(event) { | ||||
|             var username = event.params[1]; | ||||
|             var command = event.params[2]; | ||||
|             if(_.has(dbot.db.bans, command) && _.include(dbot.db.bans[command], username)) { | ||||
|                 _.reject(dbot.db.bans[command], function(bans) { | ||||
|                     return bans == username; | ||||
|                 }, this); | ||||
|                 event.reply(dbot.t('unbanned', {'user': username, 'command': command})); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('unban_error', {'user': username})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         /*** Config options ***/ | ||||
| 
 | ||||
|         'setconfig': function(event) { | ||||
|             var configPathString = event.params[1], | ||||
|                 configKey = _.last(configPathString.split('.')), | ||||
|                 newOption = event.params[2]; | ||||
| 
 | ||||
|             if(!_.include(noChangeConfig, configKey)) { | ||||
|                 var configPath = getCurrentConfig(configPathString); | ||||
| 
 | ||||
|                 if(configPath == false || _.isUndefined(configPath.value)) { | ||||
|                     event.reply("Config key doesn't exist bro"); | ||||
|                     return; | ||||
|                 } | ||||
|                 var currentOption = configPath.value; | ||||
| 
 | ||||
|                 // Convert to boolean type if config item boolean
 | ||||
|                 if(_.isBoolean(currentOption)) { | ||||
|                     newOption = (newOption == "true"); | ||||
|                 } | ||||
| 
 | ||||
|                 if(_.isArray(currentOption)) { | ||||
|                     event.reply("Config option is an array. Try 'pushconfig'."); | ||||
|                 } | ||||
|                  | ||||
|                 event.reply(configPathString + ": " + currentOption + " -> " + newOption); | ||||
|                 configPath['user'][configKey] = newOption; | ||||
|                 dbot.reloadModules(); | ||||
|             } else { | ||||
|                 event.reply("This config option cannot be altered while the bot is running."); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         'pushconfig': function(event) { | ||||
|             var configPathString = event.params[1], | ||||
|                 configKey = _.last(configPathString.split('.')), | ||||
|                 newOption = event.params[2]; | ||||
| 
 | ||||
|             if(!_.include(noChangeConfig, configKey)) { | ||||
|                 var configPath = getCurrentConfig(configPathString); | ||||
|                 if(configPath == false || _.isUndefined(configPath.value)) { | ||||
|                     event.reply("Config key doesn't exist bro"); | ||||
|                     return; | ||||
|                 } | ||||
|                 var currentArray = configPath.value; | ||||
| 
 | ||||
|                 if(!_.isArray(currentArray)) { | ||||
|                     event.reply("Config option is not an array. Try 'setconfig'."); | ||||
|                     return | ||||
|                 } | ||||
| 
 | ||||
|                 event.reply(configPathString + ": " + currentArray + " << " + newOption); | ||||
|                 currentArray.push(newOption); | ||||
|                 dbot.reloadModules();  | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         'showconfig': function(event) { | ||||
|             var configPathString = event.params[1]; | ||||
|             var configPath = getCurrentConfig(configPathString); | ||||
|              | ||||
|             if(configPathString) { | ||||
|                 var configKey = _.last(configPathString.split('.')); | ||||
|                 if(configKey == false) { | ||||
|                     event.reply("Config path doesn't exist"); | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 if(_.isArray(configPath.value)) { | ||||
|                     event.reply(configKey + ': ' + configPath.value); | ||||
|                 } else if(_.isObject(configPath.value)) { | ||||
|                     event.reply('Config keys in ' + configPathString + ': ' + Object.keys(configPath.value)); | ||||
|                 } else { | ||||
|                     event.reply(configKey + ': ' + configPath.value); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply('Config keys in root: ' + Object.keys(configPath['default'])); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     commands['greload'].access = 'admin'; | ||||
|     commands['reload'].access = 'admin'; | ||||
|     commands['unload'].access = 'admin'; | ||||
|     commands['load'].access = 'admin'; | ||||
|     commands['setconfig'].access = 'admin'; | ||||
|     commands['showconfig'].access = 'moderator'; | ||||
|     commands['join'].access = 'moderator'; | ||||
|     commands['part'].access = 'moderator'; | ||||
|     commands['opme'].access = 'moderator'; | ||||
|     commands['say'].access = 'moderator'; | ||||
|     commands['ban'].access = 'moderator'; | ||||
|     commands['unban'].access = 'moderator'; | ||||
| 
 | ||||
|     return commands; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return commands(dbot); | ||||
| } | ||||
| @ -1,3 +1,6 @@ | ||||
| { | ||||
|     "dbKeys": [ "bans", "locks" ] | ||||
|     "ignorable": false, | ||||
|     "dbKeys": [ "bans" ], | ||||
|     "dependencies": [ "command" ], | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/admin/README.md" | ||||
| } | ||||
|  | ||||
| @ -64,5 +64,21 @@ | ||||
|         "spanish": "Cerrado la categoría: {category}", | ||||
|         "na'vi": "{category}ìri oel 'upxareti fmoli", | ||||
|         "welsh": "Categori wedi cloi: {category}" | ||||
|     }, | ||||
|     "already_in_channel": { | ||||
|         "english": "I'm already in {channel}", | ||||
|         "na'vi": "Oel {channel}it tok li" | ||||
|     }, | ||||
|     "not_in_channel": { | ||||
|         "english": "I'm not in {channel}", | ||||
|         "na'vi": "Oel {channel}it ke tok" | ||||
|     }, | ||||
|     "already_loaded_web": { | ||||
|         "english": "WHY CAN'T I LOAD ALL THIS WEB? (web already loaded)", | ||||
|         "na'vi": "PELUN OEL KE TSUN OMUM FÌWETIT NÌWOTX (wetìri oe omum li)" | ||||
|     }, | ||||
|     "already_loaded": { | ||||
|         "english": "{moduleName} is already loaded.", | ||||
|         "na'vi": "Oel omum teri {moduleName}it li." | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										67
									
								
								modules/command/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								modules/command/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| ## Command | ||||
| 
 | ||||
| Handles the command execution logic for DBot. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| Command flow: | ||||
| 
 | ||||
| 1. Does the input match a command key in the loaded commands? | ||||
|     * If command not found and quotes is loaded, attempt to print quote of given | ||||
|       command name | ||||
| 2. Is the user banned from running the given command? | ||||
| 3. Is the user ignoring the command? | ||||
| 4. Is the channel ignoring the command? | ||||
| 5. Does the use have the access level to run the command? | ||||
| 6. Is the command set as disabled? | ||||
| 7. Apply regex to the command, pass into event object. | ||||
|     * If regex does not apply, show usage info. | ||||
| 8. Run the command. | ||||
| 
 | ||||
| This is the only module which is force loaded, even if it's not specified in | ||||
| the configuration file. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~usage [command] | ||||
| Show usage information for a given command. | ||||
| 
 | ||||
| #### ~help [command|module] | ||||
| Link module help for a module given either the module name or the name of a | ||||
| command belonging to a module. | ||||
| 
 | ||||
| ### API | ||||
| 
 | ||||
| #### isBanned(user, command) | ||||
| Return whether a user is currently banned from a given commands. | ||||
| 
 | ||||
| #### hasAccess(user, command) | ||||
| Return whether a user has the access level (moderator, admin) to run a given | ||||
| command. | ||||
| 
 | ||||
| #### isIgnoring(user, command) | ||||
| Return whether a user is currently marked as ignoring a given command. | ||||
| 
 | ||||
| #### addHook(command, callback) | ||||
| This API function allows you to hook functions into DBot commands. For example, | ||||
| you may add a hook to post on Identica when a new quote is added to the database | ||||
| with the ~qadd command. As a less useful example, here is how you might add a | ||||
| hook to log to the console every time someone uses the reload command: | ||||
| 
 | ||||
|     dbot.api.command.addHook('reload', function() { | ||||
|         console.log('Reload run!');     | ||||
|     }); | ||||
| 
 | ||||
| Hook arguments are populated by the return values of the functions they are | ||||
| hooked into, and command hooks are not run if the command explicitly returns | ||||
| 'false.' For example, the ~qadd command returns *[ key, quote ]*, and the hook | ||||
| function will be called with these variables given in the order they were | ||||
| returned, so you would retrieve the key and the quote from a hook to ~qadd like | ||||
| this: | ||||
| 
 | ||||
|     dbot.api.command.addHook('~qadd', function(key, quote) { ... | ||||
| 
 | ||||
| The best place to add hooks to commands is in the 'onLoad' function of your | ||||
| module, as this ensures it will be run while all other modules are loaded. If | ||||
| the target command does not exist (for example if its module was not loaded), | ||||
| the hook will not be added and no errors will be thrown. | ||||
							
								
								
									
										78
									
								
								modules/command/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								modules/command/api.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var api = function(dbot) { | ||||
|     return { | ||||
|         'isBanned': function(user, command) { | ||||
|             var banned = false; | ||||
|             if(_.has(dbot.db.bans, command)) { | ||||
|                 if(_.include(dbot.db.bans[command], user) || _.include(dbot.db.bans['*'], user)) { | ||||
|                     banned = true; | ||||
|                 } | ||||
|             } | ||||
|             return banned; | ||||
|         }, | ||||
| 
 | ||||
|         /** | ||||
|          * Does the user have the correct access level to use the command? | ||||
|          */ | ||||
|         'hasAccess': function(user, command) { | ||||
|             var access = true; | ||||
|             var accessNeeded = dbot.commands[command].access; | ||||
| 
 | ||||
|             if(accessNeeded == 'admin') { | ||||
|                 if(!_.include(dbot.config.admins, user)) { | ||||
|                     access = false; | ||||
|                 } | ||||
|             } else if(accessNeeded == 'moderator') { | ||||
|                 if(!_.include(dbot.config.moderators, user) &&  | ||||
|                         !_.include(dbot.config.admins, user)) { | ||||
|                     access = false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return access; | ||||
|         }, | ||||
| 
 | ||||
|         /** | ||||
|          * Is item (user or channel) ignoring command? | ||||
|          */ | ||||
|         'isIgnoring': function(item, command) { | ||||
|             var module = dbot.commands[command].module; | ||||
|             return (_.has(dbot.db.ignores, item) && | ||||
|                 _.include(dbot.db.ignores[item], module)); | ||||
|         }, | ||||
| 
 | ||||
|         /** | ||||
|          * Apply Regex to event message, store result. Return false if it doesn't | ||||
|          * apply. | ||||
|          */ | ||||
|         'applyRegex': function(commandName, event) { | ||||
|             var applies = false; | ||||
|             if(_.has(dbot.commands[commandName], 'regex')) { | ||||
|                 var cRegex = dbot.commands[commandName].regex; | ||||
|                 var q = event.message.valMatch(cRegex[0], cRegex[1]); | ||||
|                 if(q) { | ||||
|                     applies = true; | ||||
|                     event.input = q; | ||||
|                 } | ||||
|             } else { | ||||
|                 applies = true; | ||||
|             } | ||||
|             return applies; | ||||
|         }, | ||||
| 
 | ||||
|         'addHook': function(command, callback) { | ||||
|             console.log('adding hook'); | ||||
|             if(_.has(dbot.commands, command)) { | ||||
|                 if(!_.has(dbot.commands[command], 'hooks')) { | ||||
|                     dbot.commands[command].hooks = []; | ||||
|                 } | ||||
|                 dbot.commands[command].hooks.push(callback); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return api(dbot); | ||||
| }; | ||||
| @ -4,100 +4,62 @@ | ||||
|  * command and then runs that command, given the user isn't banned from or | ||||
|  * ignoring that command. | ||||
|  */ | ||||
| var _ = require('underscore')._; | ||||
| var command = function(dbot) { | ||||
|     this.dbot = dbot; | ||||
|      | ||||
|     /** | ||||
|      * Is user banned from using command? | ||||
|      * Run the appropriate command given the input. | ||||
|      */ | ||||
|     var isBanned = function(user, command) { | ||||
|         var banned = false; | ||||
|         if(dbot.db.bans.hasOwnProperty(command)) { | ||||
|             if(dbot.db.bans[command].include(user) || dbot.db.bans['*'].include(user)) { | ||||
|                 banned = true; | ||||
|             } | ||||
|         } | ||||
|         return banned; | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Is user ignoring command? | ||||
|      */ | ||||
|     var isIgnoring = function(user, command) { | ||||
|         var module = dbot.commandMap[command]; | ||||
|         var ignoring = false; | ||||
|         if(dbot.db.ignores.hasOwnProperty(user) && dbot.db.ignores[user].include(module)) { | ||||
|             ignoring = true; | ||||
|         } | ||||
|         return ignoring; | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Apply Regex to event message, store result. Return false if it doesn't | ||||
|      * apply. | ||||
|      */ | ||||
|     var applyRegex = function(commandName, event) { | ||||
|         var applies = false; | ||||
|         if(dbot.commands[commandName].hasOwnProperty('regex')) { | ||||
|             var cRegex = dbot.commands[commandName].regex; | ||||
|             var q = event.message.valMatch(cRegex[0], cRegex[1]); | ||||
|             if(q) { | ||||
|                 applies = true; | ||||
|                 event.input = q; | ||||
|             } | ||||
|         } else { | ||||
|             applies = true; | ||||
|         } | ||||
|         return applies; | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'command', | ||||
|         'ignorable': false, | ||||
| 
 | ||||
|         'commands': { | ||||
|             '~usage': function(event) { | ||||
|                 var commandName = event.params[1]; | ||||
|                 if(dbot.usage.hasOwnProperty(commandName)) { | ||||
|                     event.reply('Usage for ' + commandName + ': ' + | ||||
|                         dbot.usage[commandName]);  | ||||
|                 } else { | ||||
|                     event.reply('No usage information for ' + commandName); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         /** | ||||
|          * Run the appropriate command given the input. | ||||
|          */ | ||||
|         'listener': function(event) { | ||||
|             var commandName = event.params[0]; | ||||
|             if(!dbot.commands.hasOwnProperty(commandName)) { | ||||
|     this.listener = function(event) { | ||||
|         var commandName = event.params[0]; | ||||
|         if(!_.has(dbot.commands, commandName)) { | ||||
|             if(_.has(dbot.modules, 'quotes')) { | ||||
|                 commandName = '~'; | ||||
|             } | ||||
| 
 | ||||
|             if(isBanned(event.user, commandName)) { | ||||
|                 event.reply(dbot.t('command_ban', {'user': event.user}));  | ||||
|             } else { | ||||
|                 if(!isIgnoring(event.user, commandName)) { | ||||
|                     if(applyRegex(commandName, event)) { | ||||
|                         dbot.commands[commandName](event); | ||||
|                         dbot.save(); | ||||
|                     } else { | ||||
|                         if(commandName !== '~') { | ||||
|                             if(dbot.usage.hasOwnProperty(commandName)){ | ||||
|                                 event.reply('Usage: ' + dbot.usage[commandName]); | ||||
|                             } else { | ||||
|                                 event.reply(dbot.t('syntax_error')); | ||||
|                             } | ||||
|                 return; | ||||
|             } | ||||
|         }  | ||||
|          | ||||
|         if(this.api.isBanned(event.user, commandName)) { | ||||
|             event.reply(dbot.t('command_ban', {'user': event.user}));  | ||||
|         } else { | ||||
|             if(!this.api.isIgnoring(event.user, commandName) &&  | ||||
|                     !this.api.isIgnoring(event.channel, commandName) && | ||||
|                     this.api.hasAccess(event.user, commandName) && | ||||
|                     dbot.commands[commandName].disabled !== true) { | ||||
|                 if(this.api.applyRegex(commandName, event)) { | ||||
|                     try { | ||||
|                         var command = dbot.commands[commandName]; | ||||
|                         var results = command.apply(dbot.modules[command.module], [event]); | ||||
|                         if(_.has(command, 'hooks') && results !== false) { | ||||
|                             _.each(command['hooks'], function(hook) { | ||||
|                                 hook.apply(hook.module, _.values(results));  | ||||
|                             }, this); | ||||
|                         } | ||||
|                     } catch(err) { | ||||
|                         if(dbot.config.debugMode == true) { | ||||
|                             event.reply('- Error in ' + commandName + ':'); | ||||
|                             event.reply('- Message: ' + err); | ||||
|                             event.reply('- Top of stack: ' + err.stack.split('\n')[1].trim()); | ||||
|                         } | ||||
|                     } | ||||
|                     dbot.save(); | ||||
|                 } else { | ||||
|                     if(commandName !== '~') { | ||||
|                         if(_.has(dbot.usage, commandName)) { | ||||
|                             event.reply('Usage: ' + dbot.usage[commandName]); | ||||
|                         } else { | ||||
|                             event.reply(dbot.t('syntax_error')); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         'on': 'PRIVMSG' | ||||
|     }; | ||||
|         } | ||||
|     }.bind(this); | ||||
|     this.on = 'PRIVMSG'; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return command(dbot); | ||||
|     return new command(dbot); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										43
									
								
								modules/command/commands.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								modules/command/commands.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var commands = function(dbot) { | ||||
|     return { | ||||
|         '~usage': function(event) { | ||||
|             var commandName = event.params[1]; | ||||
|             if(_.has(dbot.usage, commandName)) { | ||||
|                 event.reply(dbot.t('usage', { | ||||
|                     'command': commandName, | ||||
|                     'usage': dbot.usage[commandName] | ||||
|                 })); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('no_usage_info', {  | ||||
|                     'command': commandName  | ||||
|                 })); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~help': function(event) { | ||||
|             var moduleName = event.params[1]; | ||||
|             if(!_.has(dbot.modules, moduleName)) { | ||||
|                 var moduleName = dbot.commands[moduleName].module;  | ||||
|             } | ||||
| 
 | ||||
|             if(moduleName && _.has(dbot.config[moduleName], 'help')) { | ||||
|                 var help = dbot.config[moduleName].help; | ||||
|                 event.reply(dbot.t('help_link', { | ||||
|                     'module': moduleName, | ||||
|                     'link': help | ||||
|                 })); | ||||
|             } else { | ||||
|                 if(!moduleName) { | ||||
|                     moduleName = event.params[1]; | ||||
|                 } | ||||
|                 event.reply(dbot.t('no_help', { 'module': moduleName })) | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return commands(dbot); | ||||
| }; | ||||
							
								
								
									
										5
									
								
								modules/command/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								modules/command/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| { | ||||
|     "ignorable": false, | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/command/README.md", | ||||
|     "dbKeys": [ "ignores" ] | ||||
| } | ||||
| @ -10,5 +10,21 @@ | ||||
|         "spanish": "Sintaxis no válida. Iniciar incineración.", | ||||
|         "na'vi": "Ngeyä pamrel keyawr lu. Nga skxawng lu.", | ||||
|         "welsh": "Cystrawen annilys. Cychwyn orfflosgiad" | ||||
|     }, | ||||
|     "usage": { | ||||
|         "english": "Usage for {command}: {usage}.", | ||||
|         "na'vi": "Nga tsun sivar ìlä {command}: {usage}." | ||||
|     }, | ||||
|     "no_usage_info": { | ||||
|         "english": "No usage information found for {command}.", | ||||
|         "na'vi": "Oel ke tsun sivar {comamnd}it" | ||||
|     }, | ||||
|     "help_link": { | ||||
|         "english": "Help for {module}: {link}", | ||||
|         "na'vi": "{module}ä srungìl {link} it tok" | ||||
|     }, | ||||
|     "no_help": { | ||||
|         "english": "No help found for {module}.", | ||||
|         "na'vi": "Fì{module}ìri oel ke tsun run srungit" | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										31
									
								
								modules/dent/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								modules/dent/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| ## Dent | ||||
| 
 | ||||
| Post dents. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| Allows the posting of dents to Identica. Easily abused for posting status | ||||
| messages to Twitter by linking the Identica account. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~dent [text] | ||||
| Post the given text to Identica. | ||||
| 
 | ||||
| ### Configuration | ||||
| 
 | ||||
| #### username | ||||
| Identica username to post with. | ||||
| 
 | ||||
| #### password | ||||
| Identica password to post with. | ||||
| 
 | ||||
| ### API | ||||
| 
 | ||||
| #### post(content) | ||||
| Post the given content to Identica. | ||||
| 
 | ||||
| ### Hooks | ||||
| 
 | ||||
| #### ~qadd | ||||
| Posts new quote additions. | ||||
							
								
								
									
										7
									
								
								modules/dent/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								modules/dent/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| { | ||||
|     "username": "youruserhere", | ||||
|     "password": "yourpasswordhere", | ||||
|     "dependencies": [ "command" ], | ||||
|     "ignorable": true, | ||||
|     "dentQuotes": false | ||||
| } | ||||
							
								
								
									
										47
									
								
								modules/dent/dent.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								modules/dent/dent.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| var request = require('request'); | ||||
|     _ = require('underscore')._; | ||||
| 
 | ||||
| var dent = function(dbot) { | ||||
|     this.dbot = dbot; | ||||
| 
 | ||||
|     this.api = { | ||||
|         'post': function(content) { | ||||
|             var username = dbot.config.dent.username, | ||||
|                 password = dbot.config.dent.password, | ||||
|                 info, | ||||
|                 auth = "Basic " +  | ||||
|                 new Buffer(username + ":" + password).toString("base64"); | ||||
| 
 | ||||
|             request.post({ | ||||
|                 'url': 'http://identi.ca/api/statuses/update.json?status=' + | ||||
|                     content,  | ||||
|                 'headers': { | ||||
|                     'Authorization': auth | ||||
|                 } | ||||
|             }, | ||||
|             function(error, response, body) { | ||||
|                 console.log(body); | ||||
|             }.bind(this)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     this.commands = { | ||||
|         '~dent': function(event) { | ||||
|             this.api.post(event.input[1]); | ||||
|             event.reply('Dent posted (probably).'); | ||||
|         } | ||||
|     }; | ||||
|     this.commands['~dent'].regex = [/^~dent (.+)$/, 2]; | ||||
| 
 | ||||
|     this.onLoad = function() { | ||||
|         if(dbot.config.dent.dentQuotes === true && _.has(dbot.modules, 'quotes')) { | ||||
|             dbot.api.command.addHook('~qadd', function(key, text) { | ||||
|                 this.api.post(key + ': ' + text);  | ||||
|             }.bind(this)); | ||||
|         } | ||||
|     }.bind(this); | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return new dent(dbot); | ||||
| }; | ||||
							
								
								
									
										9
									
								
								modules/dice/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								modules/dice/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| ## Dice | ||||
| Rolls virtual dice. | ||||
| 
 | ||||
| ### Description | ||||
| Rolls a virtual die and outputs the result to the channel. | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~roll <die type> | ||||
| Rolls a die. 1d6 will be rolled by default. | ||||
							
								
								
									
										4
									
								
								modules/dice/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								modules/dice/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| { | ||||
|     "ignorable": true, | ||||
|     "dependencies": [ "command" ] | ||||
| } | ||||
| @ -92,14 +92,9 @@ var dice = function(dbot) { | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'dice', | ||||
|         'commands': commands, | ||||
|         'ignorable': true | ||||
|     }; | ||||
|     this.commands = commands; | ||||
| } | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return dice(dbot); | ||||
|     return new dice(dbot); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										30
									
								
								modules/event/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								modules/event/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| ## Event | ||||
| 
 | ||||
| Emit events for whatever you want man idk. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| This is a library module designed for other modules to use to emit various | ||||
| events at any point, and also to attach functions to said events. These are | ||||
| similar to command hooks, however their advantage is that they may be called | ||||
| anywhere in your code; they are particularly useful when you want to attach a  | ||||
| callback to a listener. | ||||
| 
 | ||||
| ### API | ||||
| 
 | ||||
| #### addHook(eventName, callback) | ||||
| This function will set a given callback to be executed every time the | ||||
| emit API function is executed with the given event name. The arguments of your | ||||
| callback are defined as an array in the emit call. | ||||
| 
 | ||||
| The best place to add hooks to commands is in the 'onLoad' function of your | ||||
| module, as this ensures it will be run while all other modules are loaded so  | ||||
| nothing will be missed. | ||||
| 
 | ||||
| #### emit(eventName, [ arguments ]) | ||||
| This function executes all of the functions associated with the given eventName,  | ||||
| passing your given array of arguments. | ||||
| 
 | ||||
| For example, to emit an event when you detect a nick change: | ||||
|      | ||||
|     dbot.api.event.emit('nick_changed', [ event.server, newNick ]); | ||||
							
								
								
									
										28
									
								
								modules/event/event.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								modules/event/event.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| /** | ||||
|  * Module Name: event | ||||
|  * Description: Allow other modules to emit events and that | ||||
|  */ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var event = function(dbot) { | ||||
|     this.dbot = dbot; | ||||
|     this.hooks = {}; | ||||
|     this.api = { | ||||
|         'addHook': function(eventName, callback) { | ||||
|             if(!_.has(this.hooks, eventName)) this.hooks[eventName] = []; | ||||
|             this.hooks[eventName].push(callback); | ||||
|         }, | ||||
| 
 | ||||
|         'emit': function(eventName, args) { | ||||
|             if(_.has(this.hooks, eventName)) { | ||||
|                 _.each(this.hooks[eventName], function(callback) { | ||||
|                     callback.apply(callback.module, args);  | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return new event(dbot); | ||||
| }; | ||||
							
								
								
									
										1
									
								
								modules/github
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								modules/github
									
									
									
									
									
										Submodule
									
								
							| @ -0,0 +1 @@ | ||||
| Subproject commit ca25f8f94e205e2a6e3119227a6255eb80cd26df | ||||
							
								
								
									
										26
									
								
								modules/ignore/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								modules/ignore/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| ## Ignore | ||||
| 
 | ||||
| Ignore modules. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| Commands with which users can choose to ignore listeners and commands from | ||||
| certain modules persistently, by storing their choices in the database. This is | ||||
| an interface for the JSBot ignoreTag functionality which actually implements  | ||||
| the ignoration. | ||||
| 
 | ||||
| ### Configuration | ||||
| 
 | ||||
| All modules may return with them an 'ignorable' property, which defines whether | ||||
| or not their functionality may be ignored by users. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~ignore [module] | ||||
| Ignore a given module. If the user does not specify a module, or provides an | ||||
| invalid one a list of modules which are available to ignore will be given. | ||||
| 
 | ||||
| #### ~unignore [module] | ||||
| Unignore a previously ignored module. If the user does not specify a module, or | ||||
| provides an invalid choice a list of modules which are currently ignored will be | ||||
| given. | ||||
| @ -1,3 +1,6 @@ | ||||
| { | ||||
|     "dbKeys": [ "ignores" ] | ||||
|     "ignorable": false, | ||||
|     "dependencies": [ "command" ], | ||||
|     "dbKeys": [ "ignores" ], | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/ignore/README.md" | ||||
| } | ||||
|  | ||||
| @ -2,80 +2,137 @@ | ||||
|  * Module Name: Ignore | ||||
|  * Description: Handles commands in which users can choose to ignore listeners | ||||
|  * and commands from certain modules. It also populates the JSBot instance with | ||||
|  * this information, since that actually performs the ignorance. | ||||
|  * this information, since that actually performs the ignorance. Also provides | ||||
|  * commands for moderators to choose the bot to ignore certain channels. | ||||
|  */ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var ignore = function(dbot) { | ||||
|     var commands = { | ||||
|         '~ignore': function(event) { | ||||
|             var ignorableModules = []; | ||||
|             for(var i=0;i<dbot.modules.length;i++) { | ||||
|                 if(dbot.modules[i].ignorable != null && dbot.modules[i].ignorable == true) { | ||||
|                     ignorableModules.push(dbot.modules[i].name); | ||||
|                 } | ||||
|             } | ||||
|             var module = event.params[1]; | ||||
|             var ignorableModules = _.chain(dbot.modules) | ||||
|                 .filter(function(module, name) { | ||||
|                     return dbot.config[module].ignorable === true; | ||||
|                 }) | ||||
|                 .pluck('name') | ||||
|                 .value(); | ||||
| 
 | ||||
|             if(module === undefined) { | ||||
|                 event.reply(dbot.t('ignore_usage', {'user': event.user, 'modules': ignorableModules.join(', ')})); | ||||
|             if(_.isUndefined(module)) { | ||||
|                 event.reply(dbot.t('ignore_usage', { | ||||
|                     'user': event.user,  | ||||
|                     'modules': ignorableModules.join(', ') | ||||
|                 })); | ||||
|             } else { | ||||
|                 if(ignorableModules.include(module)) { | ||||
|                     if(dbot.db.ignores.hasOwnProperty(event.user) && dbot.db.ignores[event.user].include(module)) { | ||||
|                         event.reply(dbot.t('already_ignoring', {'user': event.user})); | ||||
|                 if(_.include(ignorableModules, module)) { | ||||
|                     if(_.has(dbot.db.ignores, event.user) && _.include(dbot.db.ignores[event.user], module)) { | ||||
|                         event.reply(dbot.t('already_ignoring', { 'user': event.user })); | ||||
|                     } else { | ||||
|                         if(dbot.db.ignores.hasOwnProperty(module)) { | ||||
|                         if(_.has(dbot.db.ignores, module)) { | ||||
|                             dbot.db.ignores[event.user].push(module); | ||||
|                         } else { | ||||
|                             dbot.db.ignores[event.user] = [module]; | ||||
|                         } | ||||
| 
 | ||||
|                         dbot.instance.ignoreTag(event.user, module); | ||||
|                         event.reply(dbot.t('ignored', {'user': event.user, 'module': module})); | ||||
|                         event.reply(dbot.t('ignored', { | ||||
|                             'user': event.user,  | ||||
|                             'module': module | ||||
|                         })); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('invalid_ignore', {'user': event.user})); | ||||
|                     event.reply(dbot.t('invalid_ignore', { 'user': event.user })); | ||||
|                 } | ||||
|             } | ||||
|         },  | ||||
| 
 | ||||
|         '~unignore': function(event) { | ||||
|             var ignoredModules = []; | ||||
|             if(dbot.db.ignores.hasOwnProperty(event.user)) { | ||||
|             if(_.has(dbot.db.ignores, event.user)) { | ||||
|                 ignoredModules = dbot.db.ignores[event.user]; | ||||
|             } | ||||
|             var module = event.params[1]; | ||||
| 
 | ||||
|             if(module === undefined) { | ||||
|                 event.reply(dbot.t('unignore_usage', {'user': event.user, 'modules': ignoredModules.join(', ')})); | ||||
|             if(_.isUndefined(module)) { | ||||
|                 event.reply(dbot.t('unignore_usage', { | ||||
|                     'user': event.user,  | ||||
|                     'modules': ignoredModules.join(', ') | ||||
|                 })); | ||||
|             } else { | ||||
|                 if(ignoredModules.include(module) == false) { | ||||
|                     event.reply(dbot.t('invalid_unignore', {'user': event.user})); | ||||
|                 } else { | ||||
|                 if(_.include(ignoredModules, module)) { | ||||
|                     dbot.db.ignores[event.user].splice(dbot.db.ignores[event.user].indexOf(module), 1); | ||||
|                     dbot.instance.removeIgnore(event.user, module) | ||||
|                     event.reply(dbot.t('unignored', {'user': event.user, 'module': module})); | ||||
|                     event.reply(dbot.t('unignored', {  | ||||
|                         'user': event.user,  | ||||
|                         'module': module | ||||
|                     })); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('invalid_unignore', { 'user': event.user })); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~ignorechannel': function(event) { | ||||
|             var channel = ((event.params[1] == '@') ? event.channel.name : event.params[1]); | ||||
|             var module = event.params[2]; | ||||
| 
 | ||||
|             // Ignoring the value of 'ignorable' at the moment
 | ||||
|             if(_.include(dbot.config.moduleNames, module)) { | ||||
|                 if(!_.has(dbot.db.ignores, channel)) dbot.db.ignores[channel] = []; | ||||
|                 if(!_.include(dbot.db.ignores[channel], module)) { | ||||
|                     dbot.db.ignores[channel].push(module); | ||||
|                     dbot.instance.ignoreTag(channel, module); | ||||
|                     event.reply(dbot.t('ignoring_channel', { | ||||
|                         'module': module, | ||||
|                         'channel': channel | ||||
|                     })); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('already_ignoring_channel', { | ||||
|                         'module': module, | ||||
|                         'channel': channel | ||||
|                     })); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('module_not_exist', { 'module': module })); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~unignorechannel': function(event) { | ||||
|             var channel = ((event.params[1] == '@') ? event.channel.name : event.params[1]); | ||||
|             var module = event.params[2]; | ||||
| 
 | ||||
|             if(!_.has(dbot.db.ignores, channel)) dbot.db.ignores[channel] = []; | ||||
|             if(_.include(dbot.db.ignores[channel], module)) { | ||||
|                 dbot.db.ignores[channel] = _.without(dbot.db.ignores[channel], module);  | ||||
|                 dbot.instance.removeIgnore(channel, module); | ||||
|                 event.reply(dbot.t('unignoring_channel', { | ||||
|                     'module': module, | ||||
|                     'channel': channel | ||||
|                 })); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('not_ignoring_channel', { | ||||
|                     'module': module, | ||||
|                     'channel': channel | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'ignore', | ||||
|         'ignorable': false,  | ||||
|         'commands': commands, | ||||
|     commands['~ignorechannel'].access = 'moderator'; | ||||
|     commands['~unignorechannel'].access = 'moderator'; | ||||
| 
 | ||||
|         'onLoad': function() { | ||||
|             dbot.instance.clearIgnores(); | ||||
|             for(var user in dbot.db.ignores) { | ||||
|                 if(dbot.db.ignores.hasOwnProperty(user)) { | ||||
|                     for(var i=0;i<dbot.db.ignores[user].length;i++) { | ||||
|                         dbot.instance.ignoreTag(user, dbot.db.ignores[user][i]); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     this.commands = commands; | ||||
| 
 | ||||
|     this.onLoad = function() { | ||||
|         dbot.instance.clearIgnores(); | ||||
|         _.each(dbot.db.ignores, function(ignores, item) { | ||||
|             _.each(ignores, function(ignore) { | ||||
|                     dbot.instance.ignoreTag(item, ignore); | ||||
|             }, this); | ||||
|         }, this); | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return ignore(dbot); | ||||
|     return new ignore(dbot); | ||||
| }; | ||||
|  | ||||
| @ -40,5 +40,25 @@ | ||||
|         "spanish": "{user}: Ya no ignoras {module}.", | ||||
|         "na'vi": "{user}: Nga terìng mikyun {module}ne set", | ||||
|         "welsh": "{user}: Ddim yn anwybyddu {module} bellach" | ||||
|     }, | ||||
|     "ignoring_channel": { | ||||
|         "english": "Now ignoring {module} in {channel}", | ||||
|         "na'vi": "Oe ke stayawm {module}ur mì {channel}" | ||||
|     }, | ||||
|     "already_ignoring_channel": { | ||||
|         "english": "Already ignoring {module} in {channel}", | ||||
|         "na'vi": "Oe ke stayawm {module}ur mì {channel} li" | ||||
|     }, | ||||
|     "module_not_exist": { | ||||
|         "english": "{module} isn't loaded or doesn't exist.", | ||||
|         "na'vi": "Oel ke omum teri {module}it fu {module} ke fkeytok" | ||||
|     }, | ||||
|     "unignoring_channel": { | ||||
|         "english": "No longer ignoring {module} in {channel}", | ||||
|         "na'vi": "Oel stayawm {module}ur mì {channel} set." | ||||
|     }, | ||||
|     "not_ignoring_channel": { | ||||
|         "english": "{module} wasn't being ignored in {channel}", | ||||
|         "na'vi": "Oel stayawm {module}ur mì {channel} li." | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										40
									
								
								modules/js/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								modules/js/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| ## JS | ||||
| 
 | ||||
| Run JavaScript. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| This module provides two commands which allow the execution of Javascript code | ||||
| from the bot. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~js [code] | ||||
| For regular users, there is the *~js* command, which is completely sandboxed, | ||||
| but can still be used for calculation and the like. | ||||
| 
 | ||||
|     > ~js Array(16).join('wat'-1) + " Batman!"; | ||||
|     'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!' | ||||
| 
 | ||||
| This feature is fairly safe as the user doesn't have access to anything | ||||
| dangerous, and is safe from infinite loops or locking DBot up because the code | ||||
| which is run is killed if it does not finish within a short amount of time. | ||||
| 
 | ||||
| #### ~ajs [code] | ||||
| For administrators, the incredibly useful *~ajs* command is also available. The | ||||
| input for this command is simply 'eval'-ed and therefore has full access to | ||||
| DBot's memory. Of course, this is incredibly unsafe, but I find it rather fun; | ||||
| remember to only give extremely trusted friends administrator access to your | ||||
| DBot instance, as there's nothing to stop them wiping the database or probably | ||||
| even your hard drive - if you're worried about that kind of thing, do not load  | ||||
| this module. | ||||
| 
 | ||||
| However, it's useful for many things, such as administrative activity for  | ||||
| which there isn't a command in the admin module. For example, you could hot-add | ||||
| a new administrator like this: | ||||
| 
 | ||||
|     > ~ajs dbot.admin.push('batman'); | ||||
|     2 | ||||
| 
 | ||||
| You can also use it for debugging, or even adding new commands while DBot is | ||||
| running. | ||||
							
								
								
									
										10
									
								
								modules/js/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								modules/js/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| { | ||||
|     "commands": { | ||||
|         "~js": { | ||||
|             "disabled": true | ||||
|         } | ||||
|     }, | ||||
|     "dependencies": [ "command" ], | ||||
|     "ignorable": true, | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/js/README.md" | ||||
| } | ||||
| @ -8,12 +8,11 @@ var vm = require('vm'); | ||||
| var sbox = require('sandbox'); | ||||
| 
 | ||||
| var js = function(dbot) { | ||||
|     var s = new sbox(); | ||||
| 
 | ||||
|     var commands = { | ||||
|         // Run JS code sandboxed, return result to channel.
 | ||||
|         '~js': function(event) { | ||||
|             try { | ||||
|                 var s = new sbox(); | ||||
|                 s.run(event.input[1], function(output) { | ||||
|                     event.reply(output.result); | ||||
|                 }.bind(this)); | ||||
| @ -22,11 +21,9 @@ var js = function(dbot) { | ||||
| 
 | ||||
|         // Run JS code un-sandboxed, with access to DBot memory (admin-only).
 | ||||
|         '~ajs': function(event) { | ||||
|             if(dbot.config.admins.include(event.user) ) { | ||||
|                 var ret = eval(event.input[1]); | ||||
|                 if(ret !== undefined) { | ||||
|                     event.reply(ret); | ||||
|                 } | ||||
|             var ret = eval(event.input[1]); | ||||
|             if(ret !== undefined) { | ||||
|                 event.reply(ret); | ||||
|             } | ||||
|         }, | ||||
|         'jesus': function (event) { | ||||
| @ -36,14 +33,13 @@ var js = function(dbot) { | ||||
|     commands['~js'].regex = [/^~js (.*)/, 2]; | ||||
|     commands['~ajs'].regex = [/^~ajs (.*)/, 2]; | ||||
|     commands['jesus'].regex = [/^jesus$/, 2]; | ||||
|     commands['~ajs'].access = 'admin'; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'js', | ||||
|         'ignorable': true, | ||||
|         'commands': commands | ||||
|     }; | ||||
|     this.name = 'js'; | ||||
|     this.ignorable = true; | ||||
|     this.commands = commands; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return js(dbot); | ||||
|     return new js(dbot); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										17
									
								
								modules/kick/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								modules/kick/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| ## Kick | ||||
| 
 | ||||
| Kicking and kicking-related accessories.  | ||||
| 
 | ||||
| ### Description | ||||
| This module counts the number of times people are kicked from and kick people | ||||
| from channels, and provides commands for viewing this data. It also has the bot | ||||
| attempt to rejoin the channel if it is kicked. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~kickcount [username] | ||||
| Show the number of times a given user has been kicked and has kicked other | ||||
| people. | ||||
| 
 | ||||
| #### ~kickstats | ||||
| Show a list of top kickers and kickees. | ||||
| @ -1,3 +1,6 @@ | ||||
| { | ||||
|     "dbKeys": [ "kicks", "kickers" ] | ||||
|     "dbKeys": [ "kicks", "kickers" ], | ||||
|     "dependencies": [ "command" ], | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/kick/README.md", | ||||
|     "ignorable": true | ||||
| } | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var kick = function(dbot) { | ||||
|     var commands = { | ||||
|         // Give the number of times a given user has been kicked and has kicked
 | ||||
| @ -5,27 +7,35 @@ var kick = function(dbot) { | ||||
|         '~kickcount': function(event) { | ||||
|             var username = event.params[1]; | ||||
| 
 | ||||
|             if(!dbot.db.kicks.hasOwnProperty(username)) { | ||||
|             if(!_.has(dbot.db.kicks, username)) { | ||||
|                 var kicks = '0'; | ||||
|             } else { | ||||
|                 var kicks = dbot.db.kicks[username]; | ||||
|             } | ||||
| 
 | ||||
|             if(!dbot.db.kickers.hasOwnProperty(username)) { | ||||
|             if(!_.has(dbot.db.kickers, username)) { | ||||
|                 var kicked = '0'; | ||||
|             } else { | ||||
|                 var kicked = dbot.db.kickers[username]; | ||||
|             } | ||||
| 
 | ||||
|             event.reply(dbot.t('user_kicks', {'user': username, 'kicks': kicks, 'kicked': kicked})); | ||||
|             event.reply(dbot.t('user_kicks', { | ||||
|                 'user': username,  | ||||
|                 'kicks': kicks,  | ||||
|                 'kicked': kicked | ||||
|             })); | ||||
|         }, | ||||
| 
 | ||||
|         // Output a list of the people who have been kicked the most and those
 | ||||
|         // who have kicked other people the most.
 | ||||
|         '~kickstats': function(event) { | ||||
|             var orderedKickLeague = function(list, topWhat) { | ||||
|                 var kickArr = Object.prototype.sort(list, function(key, obj) { return obj[key]; }); | ||||
|                 kickArr = kickArr.slice(kickArr.length - 10).reverse(); | ||||
|                 var kickArr = _.chain(list) | ||||
|                     .pairs() | ||||
|                     .sortBy(function(kick) { return kick[1] }) | ||||
|                     .reverse() | ||||
|                     .first(10) | ||||
|                     .value(); | ||||
| 
 | ||||
|                 var kickString = "Top " + topWhat + ": "; | ||||
|                 for(var i=0;i<kickArr.length;i++) { | ||||
| @ -39,38 +49,36 @@ var kick = function(dbot) { | ||||
|             event.reply(orderedKickLeague(dbot.db.kickers, 'Kickers')); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'kick', | ||||
|         'ignorable': false, | ||||
|         'commands': commands, | ||||
| 
 | ||||
|         'listener': function(event) { | ||||
|            if(event.kickee == dbot.config.name) { | ||||
|                 dbot.instance.join(event, event.channel); | ||||
|                 event.reply(dbot.t('kicked_dbot', {'botname': dbot.config.name})); | ||||
|                 dbot.db.kicks[dbot.config.name] += 1; | ||||
|     this.commands = commands; | ||||
|      | ||||
|     this.listener = function(event) { | ||||
|        if(event.kickee == dbot.config.name) { | ||||
|             dbot.instance.join(event, event.channel); | ||||
|             event.reply(dbot.t('kicked_dbot', { 'botname': dbot.config.name })); | ||||
|             dbot.db.kicks[dbot.config.name] += 1; | ||||
|         } else { | ||||
|             if(!_.has(dbot.db.kicks, event.kickee)) { | ||||
|                 dbot.db.kicks[event.kickee] = 1; | ||||
|             } else { | ||||
|                 if(!dbot.db.kicks.hasOwnProperty(event.kickee)) { | ||||
|                     dbot.db.kicks[event.kickee] = 1; | ||||
|                 } else { | ||||
|                     dbot.db.kicks[event.kickee] += 1; | ||||
|                 } | ||||
| 
 | ||||
|                 if(!dbot.db.kickers.hasOwnProperty(event.user)) { | ||||
|                     dbot.db.kickers[event.user] = 1;  | ||||
|                 } else { | ||||
|                     dbot.db.kickers[event.user] += 1; | ||||
|                 } | ||||
| 
 | ||||
|                 event.reply(event.kickee + '-- (' + dbot.t('user_kicks',  | ||||
|                     {'user': event.kickee, 'kicks': dbot.db.kicks[event.kickee], 'kicked': dbot.db.kickers[event.kickee]}) + ')'); | ||||
|                 dbot.db.kicks[event.kickee] += 1; | ||||
|             } | ||||
|         }, | ||||
|         on: 'KICK' | ||||
| 
 | ||||
|             if(!_.has(dbot.db.kickers, event.user)) { | ||||
|                 dbot.db.kickers[event.user] = 1;  | ||||
|             } else { | ||||
|                 dbot.db.kickers[event.user] += 1; | ||||
|             } | ||||
| 
 | ||||
|             event.reply(event.kickee + '-- (' + dbot.t('user_kicks', { | ||||
|                 'user': event.kickee,  | ||||
|                 'kicks': dbot.db.kicks[event.kickee],  | ||||
|                 'kicked': dbot.db.kickers[event.kickee] | ||||
|             }) + ')'); | ||||
|         } | ||||
|     }; | ||||
|     this.on = 'KICK'; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return kick(dbot); | ||||
|     return new kick(dbot); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										25
									
								
								modules/link/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								modules/link/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| ## Link | ||||
| 
 | ||||
| Retrieves page titles. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| This module stores the last posted link in each channel, and provides a command | ||||
| for retrieving the title of a given link or the last posted link in the channel. | ||||
| 
 | ||||
| ### Configuration | ||||
| 
 | ||||
| #### autoTitle: false | ||||
| If this is set to true, the bot will automatically post the titles of links as | ||||
| they are posted in the channel. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~title [link] | ||||
| If called with a link, the bot will attempt to find and return the title of that | ||||
| page. If called without a link, the bot will attempt the same on the last link | ||||
| which was posted in the current channel. | ||||
| #### ~ud [headword] | ||||
| Returns the first [Urban Dictionary](http://www.urbandictionary.com) definition for the headword provided. | ||||
| #### ~xkcd <comic ID> | ||||
| Returns a link to the [xkcd](http://xkcd.com) comic specified, or the latest one if a comic is not given. | ||||
							
								
								
									
										6
									
								
								modules/link/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								modules/link/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| { | ||||
|     "autoTitle": false, | ||||
|     "dependencies": [ "command" ], | ||||
|     "ignorable": true, | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/link/README.md" | ||||
| } | ||||
| @ -3,50 +3,85 @@ | ||||
|  * Description: Stores recent channel links, with commands to retrieve | ||||
|  * information about links. | ||||
|  */ | ||||
| var request = require('request'); | ||||
| var request = require('request'), | ||||
|     _ = require('underscore')._; | ||||
| 
 | ||||
| var link = function(dbot) { | ||||
|     var urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; | ||||
|     var links = {};  | ||||
|     this.urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; | ||||
|     this.links = {};  | ||||
|     this.fetchTitle = function(event, link) { | ||||
|         request(link, function(error, response, body) { | ||||
|             if(!error && response.statusCode == 200) { | ||||
|                 body = body.replace(/(\r\n|\n\r|\n)/gm, " "); | ||||
|                 var title = body.valMatch(/<title>(.*)<\/title>/, 2); | ||||
|                 if(title) { | ||||
|                     event.reply(title[1]); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     var commands = { | ||||
|         '~title': function(event) { | ||||
|             var link = links[event.channel.name]; | ||||
|             if(event.params[1] !== undefined) { | ||||
|                 var urlMatches = event.params[1].match(urlRegex); | ||||
|             var link = this.links[event.channel.name]; | ||||
|             if(!_.isUndefined(event.params[1])) { | ||||
|                 var urlMatches = event.params[1].match(this.urlRegex); | ||||
|                 if(urlMatches !== null) { | ||||
|                     link = urlMatches[0]; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             request(link, function (error, response, body) { | ||||
|                 if(!error && response.statusCode == 200) { | ||||
|                     body = body.replace(/(\r\n|\n\r|\n)/gm, " "); | ||||
|                     var title = body.valMatch(/<title>(.*)<\/title>/, 2); | ||||
|                     if(title) { | ||||
|                         event.reply(title[1]); | ||||
|                     } else { | ||||
|                         event.reply('no title found'); | ||||
|                     } | ||||
|             this.fetchTitle(event, link); | ||||
|         }, | ||||
|          | ||||
|         '~xkcd': function(event) { | ||||
|             var comicId = event.params[1]; | ||||
|             if(comicId){ | ||||
|                 comicId = comicId + "/"; | ||||
|             } else { | ||||
|                 comicId = ""; | ||||
|             } | ||||
|             var link = "http://xkcd.com/"+comicId+"info.0.json"; | ||||
|             request(link,  function(error, response, body) { | ||||
|                 if (response.statusCode == "200") { | ||||
|                     data = JSON.parse(body); | ||||
|                     event.reply(dbot.t("xkcd",data)); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t("no-hits")); | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
| 
 | ||||
|         '~ud': function(event) { | ||||
| 	    	var query = event.input[1]; | ||||
|             var reqUrl = 'http://api.urbandictionary.com/v0/define?term=' + encodeURI(query);  | ||||
|             request(reqUrl, function(error, response, body) { | ||||
|             	try { | ||||
| 	                var result = JSON.parse(body); | ||||
| 	                if(_.has(result, 'result_type') && result.result_type != 'no_results') { | ||||
| 	                    event.reply(query + ': ' + result.list[0].definition.split('\n')[0]); | ||||
| 	                } else { | ||||
| 	                    event.reply(event.user + ': No definition found.'); | ||||
| 	                } | ||||
|             	} catch(err) { } | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
|     commands['~ud'].regex = [/~ud (.+)/, 2]; | ||||
|     this.commands = commands; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'link',  | ||||
|         'ignorable': true, | ||||
|         'commands': commands, | ||||
|     this.listener = function(event) { | ||||
|         var urlMatches = event.message.match(this.urlRegex); | ||||
|         if(urlMatches !== null) { | ||||
|             this.links[event.channel.name] = urlMatches[0]; | ||||
| 
 | ||||
|         'listener': function(event) { | ||||
|             var urlMatches = event.message.match(urlRegex); | ||||
|             if(urlMatches !== null) { | ||||
|                 links[event.channel.name] = urlMatches[0]; | ||||
|             if(dbot.config.link.autoTitle == true) { | ||||
|                 this.fetchTitle(event, urlMatches[0]); | ||||
|             } | ||||
|         }, | ||||
|         'on': 'PRIVMSG' | ||||
|     }; | ||||
|         } | ||||
|     }.bind(this); | ||||
|     this.on = 'PRIVMSG'; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return link(dbot); | ||||
|     return new link(dbot); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										12
									
								
								modules/link/strings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								modules/link/strings.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| { | ||||
|     "title_not_found": { | ||||
|         "english": "No page title found.", | ||||
|         "na'vi": "Oel ke tsun run 'upxare atxin." | ||||
|     }, | ||||
|     "xkcd": { | ||||
|         "english": "xkcd {num}: {title} https://xkcd.com/{num}" | ||||
|     }, | ||||
|     "no-hits": { | ||||
|         "english": "No hits." | ||||
|     } | ||||
| } | ||||
							
								
								
									
										35
									
								
								modules/poll/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								modules/poll/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| ## Poll | ||||
| 
 | ||||
| Pollers gonna poll. | ||||
| 
 | ||||
| ### Description | ||||
| This module allows creation of and voting in polls, with associated | ||||
| functionality. | ||||
| 
 | ||||
| Note that while in terms of the interface all votes are anonymous, users'  | ||||
| voting choices are stored in the database for the purpose of users being  | ||||
| able to change their votes. Therefore an admin can technically go delving in  | ||||
| the database to see users' voting choices. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~newpoll [pollname] options=[each,poll,option] [Poll Description] | ||||
| Creates a new poll with the given name, options and descriptions. From this | ||||
| point people will be able to use the ~vote command to cast their vote in the | ||||
| poll. | ||||
| 
 | ||||
| #### ~addoption [pollname] [newoption] | ||||
| Using this command you can add a given option to a poll you are the creator of. | ||||
| 
 | ||||
| #### ~rmoption [pollname] [optiontoremove] | ||||
| Using this command you can remove a given option from a poll you are the creator | ||||
| of. | ||||
| 
 | ||||
| #### ~vote [pollname] [option] | ||||
| Cast your vote for the given option in the given poll. If you have already cast | ||||
| your vote in the given poll, your vote will be changed to the new option you | ||||
| have provided.  | ||||
| 
 | ||||
| #### ~pdesc [pollname] | ||||
| Show the full description for a given poll name along with its available voting | ||||
| options. | ||||
							
								
								
									
										197
									
								
								modules/poll/commands.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								modules/poll/commands.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,197 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var commands = function(dbot) { | ||||
|     var polls = dbot.db.polls; | ||||
|     var commands = { | ||||
|         '~newpoll': function(event) { | ||||
|             var name = event.input[1].toLowerCase(), | ||||
|                 options = event.input[2].toLowerCase().split(','), | ||||
|                 description = event.input[3]; | ||||
|              | ||||
|             if(_.has(polls, name)) { | ||||
|                 event.reply(dbot.t('poll_exists', { 'name': name })); | ||||
|             } else { | ||||
|                 polls[name] = { | ||||
|                     'name': name, | ||||
|                     'description': description, | ||||
|                     'owner': dbot.api.users.resolveUser(event.server, event.user), | ||||
|                     'votes': {}, | ||||
|                     'votees': {} | ||||
|                 }; | ||||
|                 for(var i=0;i<options.length;i++) { | ||||
|                     polls[name]['votes'][options[i]] = 0; | ||||
|                 } | ||||
|                 event.reply(dbot.t('poll_created', { | ||||
|                     'name': name,  | ||||
|                     'description': description,  | ||||
|                     'url': dbot.t('url', { | ||||
|                         'host': dbot.config.web.webHost, | ||||
|                         'port': dbot.config.web.webPort,  | ||||
|                         'path': 'polls/' + name | ||||
|                     }) | ||||
|                 }));  | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~addoption': function(event) { | ||||
|             var name = event.input[1].toLowerCase(), | ||||
|                 option = event.input[2].toLowerCase(), | ||||
|                 user = dbot.api.users.resolveUser(event.server, event.user); | ||||
|              | ||||
|             if(_.has(polls, name)) { | ||||
|                 if(polls[name].owner === user) { | ||||
|                     if(!_.has(polls[name].votes, option)) { | ||||
|                         polls[name]['votes'][option] = 0; | ||||
|                         event.reply(dbot.t('option_added', { | ||||
|                             'user': event.user,  | ||||
|                             'name': name,  | ||||
|                             'option': option | ||||
|                         })); | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('option_exists', { | ||||
|                             'option': option, | ||||
|                             'name': name,  | ||||
|                             'user': event.user | ||||
|                         })); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('not_poll_owner', { | ||||
|                         'user': event.user, | ||||
|                         'name': name | ||||
|                     })); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', {'name': name})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~rmoption': function(event) { | ||||
|             var name = event.input[1].toLowerCase(), | ||||
|                 option = event.input[2].toLowerCase(), | ||||
|                 user = dbot.api.users.resolveUser(event.server, event.user); | ||||
|              | ||||
|             if(_.has(polls, name)) { | ||||
|                 if(polls[name].owner === user) { | ||||
|                     if(_.has(polls[name].votes, option)) { | ||||
|                         delete polls[name]['votes'][option]; | ||||
|                         event.reply(dbot.t('option_removed', { | ||||
|                             'user': event.user, | ||||
|                             'name': name,  | ||||
|                             'option': option | ||||
|                         })); | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('invalid_vote', { 'vote': option })); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('not_poll_owner', { 'name': name })); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', { 'name': name })); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~vote': function(event) { | ||||
|             var name = event.input[1].toLowerCase(), | ||||
|                 vote = event.input[2].toLowerCase(), | ||||
|                 user = dbot.api.users.resolveUser(event.server, event.user); | ||||
| 
 | ||||
|             if(_.has(polls, name)) { | ||||
|                 if(_.has(polls[name].votes, vote)) { | ||||
|                     if(_.has(polls[name].votees, user)) { | ||||
|                         var oldVote = polls[name].votees[user]; | ||||
|                         polls[name].votes[oldVote]--; | ||||
|                         polls[name].votes[vote]++; | ||||
|                         polls[name].votees[user] = vote; | ||||
| 
 | ||||
|                         event.reply(dbot.t('changed_vote', { | ||||
|                             'vote': vote,  | ||||
|                             'poll': name, | ||||
|                             'count': polls[name].votes[vote],  | ||||
|                             'user': event.user | ||||
|                         })); | ||||
|                     } else { | ||||
|                         polls[name].votes[vote]++; | ||||
|                         polls[name].votees[user] = vote; | ||||
|                         event.reply(dbot.t('voted', { | ||||
|                             'vote': vote,  | ||||
|                             'poll': name, | ||||
|                             'count': polls[name].votes[vote],  | ||||
|                             'user': event.user | ||||
|                         })); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('invalid_vote', { 'vote': vote })); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', { 'name': name })); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~pdesc': function(event) { | ||||
|             var name = event.input[1].toLowerCase(); | ||||
| 
 | ||||
|             if(_.has(polls, name)) { | ||||
|                 var options = _.keys(polls[name].votes); | ||||
|                 var optionString = " Choices: "; | ||||
|                 for(var i=0;i<options.length;i++) { | ||||
|                     optionString += options[i] + ', '; | ||||
|                 } | ||||
|                 optionString = optionString.slice(0, -2) + '.'; | ||||
| 
 | ||||
|                 event.reply(dbot.t('poll_describe', { | ||||
|                     'name': name,  | ||||
|                     'description': polls[name].description, | ||||
|                     'url': dbot.t('url', { | ||||
|                         'host': dbot.config.web.webHost,  | ||||
|                         'port': dbot.config.web.webPort,  | ||||
|                         'path': 'polls/' + name | ||||
|                     }) | ||||
|                 }) + optionString); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', { 'name': name })); | ||||
|             } | ||||
|         }, | ||||
|          | ||||
|         '~count': function(event) { | ||||
|             var name = event.input[1].toLowerCase(); | ||||
|              | ||||
|             if(_.has(polls, name)) { | ||||
|                 var order; | ||||
|                 var votesArr = []; | ||||
| 
 | ||||
|                 var order = _.chain(polls[name].votes) | ||||
|                     .pairs() | ||||
|                     .sortBy(function(option) { return option[1] }) | ||||
|                     .reverse() | ||||
|                     .value(); | ||||
| 
 | ||||
|                 var orderString = ""; | ||||
|                 for(var i=0;i<order.length;i++) { | ||||
|                     orderString += order[i][0] + | ||||
|                         " (" + order[i][1] + "), "; | ||||
|                 } | ||||
|                 orderString = orderString.slice(0, -2); | ||||
| 
 | ||||
|                 event.reply(dbot.t('count', { | ||||
|                     'poll': name,  | ||||
|                     'description': polls[name].description,  | ||||
|                     'places': orderString | ||||
|                 })); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', {'name': name})); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|     commands['~newpoll'].regex = [/~newpoll ([^ ]+) options=([^ ]+) (.+)/, 4]; | ||||
|     commands['~addoption'].regex = [/~addoption ([^ ]+) ([^ ]+)/, 3]; | ||||
|     commands['~rmoption'].regex = [/~rmoption ([^ ]+) ([^ ]+)/, 3]; | ||||
|     commands['~vote'].regex = [/~vote ([^ ]+) ([^ ]+)/, 3]; | ||||
|     commands['~pdesc'].regex = [/~pdesc ([^ ]+)/, 2]; | ||||
|     commands['~count'].regex = [/~count ([^ ]+)/, 2]; | ||||
| 
 | ||||
|     return commands; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return commands(dbot); | ||||
| } | ||||
							
								
								
									
										6
									
								
								modules/poll/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								modules/poll/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| { | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/poll/README.md", | ||||
|     "dbKeys": [ "polls" ], | ||||
|     "ignorable": true, | ||||
|     "dependencies": [ "users", "command" ] | ||||
| } | ||||
							
								
								
									
										45
									
								
								modules/poll/pages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								modules/poll/pages.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var pages = function(dbot) { | ||||
|     var polls = dbot.db.polls; | ||||
|     var pages = { | ||||
|         // Shows the results of a poll
 | ||||
|         '/polls/:key': function(req, res) { | ||||
|             var key = req.params.key.toLowerCase(); | ||||
|             if(_.has(dbot.db.polls, key)) { | ||||
|                 var totalVotes = _.reduce(dbot.db.polls[key].votes,  | ||||
|                     function(memo, option) { | ||||
|                         return memo += option; | ||||
|                     }, 0); | ||||
|                 res.render('polls', {  | ||||
|                     'name': dbot.config.name,  | ||||
|                     'description': dbot.db.polls[key].description,  | ||||
|                     'votees': Object.keys(dbot.db.polls[key].votees),  | ||||
|                     'options': dbot.db.polls[key].votes,  | ||||
|                     locals: {  | ||||
|                         'totalVotes': totalVotes,  | ||||
|                         'url_regex': RegExp.prototype.url_regex()  | ||||
|                     }  | ||||
|                 }); | ||||
|             } else { | ||||
|                 res.render('error', {  | ||||
|                     'name': dbot.config.name,  | ||||
|                     'message': 'No polls under that key.'  | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Lists all of the polls
 | ||||
|         '/polls': function(req, res) { | ||||
|             res.render('polllist', {  | ||||
|                 'name': dbot.config.name,  | ||||
|                 'polllist': Object.keys(dbot.db.polls)  | ||||
|             }); | ||||
|         }, | ||||
|     }; | ||||
|     return pages; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return pages(dbot); | ||||
| }; | ||||
| @ -1,229 +1,25 @@ | ||||
| var poll = function(dbot) { | ||||
|     var polls = dbot.db.polls; | ||||
|     var commands = { | ||||
|         '~newpoll': function(event) { | ||||
|             var av = event.input[1] != undefined; | ||||
|             var name = event.input[2]; | ||||
|             var options = event.input[3].split(','); | ||||
|             var description = event.input[4]; | ||||
|              | ||||
|             if(name === undefined || name === 'help') { | ||||
|                 event.reply(dbot.t('newpoll_usage')); | ||||
|             } else { | ||||
|                 if(polls.hasOwnProperty(name)) { | ||||
|                     event.reply(dbot.t('poll_exists', {'name': name})); | ||||
|                 } else { | ||||
|                     if(av) { | ||||
|                         polls[name] = { | ||||
|                             'av': av, | ||||
|                             'name': name, | ||||
|                             'description': description, | ||||
|                             'owner': event.user, | ||||
|                             'votes': {}, | ||||
|                             'options': [] | ||||
|                         }; | ||||
|                         for(var i=0;i<options.length;i++) { | ||||
|                             polls[name].options.push(options[i]); | ||||
|                         } | ||||
|                     } else { | ||||
|                         polls[name] = { | ||||
|                             'av': av, | ||||
|                             'name': name, | ||||
|                             'description': description, | ||||
|                             'owner': event.user, | ||||
|                             'votes': {}, | ||||
|                             'votees': {} | ||||
|                         }; | ||||
|                         for(var i=0;i<options.length;i++) { | ||||
|                             polls[name]['votes'][options[i]] = 0; | ||||
|                         } | ||||
|                     } | ||||
|                      | ||||
|                     event.reply(dbot.t('poll_created', {'name': name, 'description': description,  | ||||
|                         'url': dbot.t('url', {'host': dbot.config.web.webHost, | ||||
|                         'port': dbot.config.web.webPort, 'path': 'polls/' + name})}));  | ||||
|     this.internalAPI = { | ||||
|         'updatePollNicks': function(server, oldNick) { | ||||
|             var newNick = dbot.api.users.resolveUser(server, oldNick); | ||||
|             _.each(dbot.db.polls, function(poll) { | ||||
|                 if(poll.owner === oldNick) { | ||||
|                     poll.owner = newNick; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~addoption': function(event) { | ||||
|             var name = event.input[1]; | ||||
|             var option = event.input[2]; | ||||
|              | ||||
|             if(polls.hasOwnProperty(name)) { | ||||
|                 if(polls[name].owner === event.user) { | ||||
|                     if(!polls[name].votes.hasOwnProperty(name)) { | ||||
|                         polls[name]['votes'][option] = 0; | ||||
|                         event.reply(dbot.t('option_added', {'user': event.user,  | ||||
|                             'name': name, 'option': option})); | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('option_exists', {'option': option, | ||||
|                             'name': name, 'user': event.user})); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('not_poll_owner', {'user': event.user, | ||||
|                         'name': name})); | ||||
|                 if(_.has(poll.votees, oldNick)) { | ||||
|                     poll.votees[newNick] = poll.votees[oldNick]; | ||||
|                     delete poll.votees[oldNick]; | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', {'name': name})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~rmoption': function(event) { | ||||
|             var name = event.input[1]; | ||||
|             var option = event.input[2]; | ||||
|              | ||||
|             if(polls.hasOwnProperty(name)) { | ||||
|                 if(polls[name].owner === event.user) { | ||||
|                     if(polls[name].votes.hasOwnProperty(option)) { | ||||
|                         delete polls[name]['votes'][option]; | ||||
|                         event.reply(dbot.t('option_removed', {'user': event.user, | ||||
|                             'name': name, 'option': option})); | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('invalid_vote', {'vote': option})); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('not_poll_owner', {'name': name})); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', {'name': name})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~vote': function(event) { | ||||
|             var name = event.input[1]; | ||||
|             var vote = event.input[2]; | ||||
| 
 | ||||
|             if(polls.hasOwnProperty(name)) { | ||||
|                 if(polls[name].av) { | ||||
|                     var prefs = vote.split(','); | ||||
|                     prefs = prefs.uniq(); | ||||
|                     var valid = true; | ||||
|                      | ||||
|                     prefs.each(function(pref) { | ||||
|                         valid = valid && polls[name].options.indexOf(pref) != -1; | ||||
|                     }); | ||||
|                     if(valid){ | ||||
|                         if(polls[name].votes.hasOwnProperty(event.user)) { | ||||
|                             polls[name].votes[event.user] = prefs; | ||||
|                             event.reply(dbot.t('av_changed_vote', {'vote': prefs.join(','), 'poll': name, 'user': event.user})); | ||||
|                         } else { | ||||
|                             polls[name].votes[event.user] = prefs; | ||||
|                             event.reply(dbot.t('av_voted', {'vote': prefs.join(','), 'poll': name, 'user': event.user})); | ||||
|                         } | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('invalid_vote', {'vote': vote})); | ||||
|                     } | ||||
|                 } else { | ||||
|                     if(polls[name].votes.hasOwnProperty(vote)) { | ||||
|                         if(polls[name].votees.hasOwnProperty(event.user)) { | ||||
|                             var oldVote = polls[name].votees[event.user]; | ||||
|                             polls[name].votes[oldVote]--; | ||||
|                             polls[name].votes[vote]++; | ||||
|                             polls[name].votees[event.user] = vote; | ||||
|                             event.reply(dbot.t('changed_vote', {'vote': vote, 'poll': name, | ||||
|                                 'count': polls[name].votes[vote], 'user': event.user})); | ||||
|                         } else { | ||||
|                             polls[name].votes[vote]++; | ||||
|                             polls[name].votees[event.user] = vote; | ||||
|                             event.reply(dbot.t('voted', {'vote': vote, 'poll': name, | ||||
|                                 'count': polls[name].votes[vote], 'user': event.user})); | ||||
|                         } | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('invalid_vote', {'vote': vote})); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', {'name': name})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~pdesc': function(event) { | ||||
|             var name = event.input[1]; | ||||
|             if(polls.hasOwnProperty(name)) { | ||||
|                 event.reply(dbot.t('poll_describe', {'name': name, 'description': polls[name].description, | ||||
|                     'url': dbot.t('url', {'host': dbot.config.web.webHost, 'port': | ||||
|                     dbot.config.web.webPort, 'path': 'polls/' + name})})); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', {'name': name})); | ||||
|             } | ||||
|         }, | ||||
|          | ||||
|         '~count': function(event) { | ||||
|             var name = event.input[1]; | ||||
|              | ||||
|             if(polls.hasOwnProperty(name)) { | ||||
|                 var order; | ||||
|                 if(polls[name].av) { | ||||
|                     var finished = false; | ||||
|                     var rounds = []; | ||||
|                     var eliminated = []; | ||||
|                     var voted; | ||||
|                      | ||||
|                     for(var roundn = 0; roundn < polls[name].options.length; roundn++) { | ||||
|                         var roundLoser; | ||||
|                          | ||||
|                         // Populate candidates for this round
 | ||||
|                         rounds[roundn] = {}; | ||||
|                         polls[name].options.each(function (option) { | ||||
|                             if(eliminated.indexOf(option) == -1) | ||||
|                                 rounds[roundn][option] = 0; | ||||
|                         }); | ||||
|                          | ||||
|                         // Count votes
 | ||||
|                         polls[name].votes.withAll(function (name, vote) { | ||||
|                             voted = false; | ||||
|                             vote.each(function (pref) { | ||||
|                                 if(!voted && rounds[roundn].hasOwnProperty(pref)) { | ||||
|                                     rounds[roundn][pref]++; | ||||
|                                     voted = true; | ||||
|                                 } | ||||
|                             }); | ||||
|                         }); | ||||
|                          | ||||
|                         // Find the loser
 | ||||
|                         var min = polls[name].votes.length() + 1; | ||||
|                         rounds[roundn].withAll(function (option, count) { | ||||
|                             if(count < min) { | ||||
|                                 roundLoser = option; | ||||
|                                 min = count; | ||||
|                             } | ||||
|                         }); | ||||
|                          | ||||
|                         // Eliminate loser
 | ||||
|                         eliminated.push(roundLoser); | ||||
|                     } | ||||
|                     order = eliminated.reverse().join(', ') | ||||
|                 } else { | ||||
|                     var votesArr = []; | ||||
|                     polls[name].votes.withAll(function(option, count) { | ||||
|                         votesArr.push([option, count]); | ||||
|                     }); | ||||
| 
 | ||||
|                     votesArr = votesArr.sort(function(a, b) { return b[1] - a[1]; }); | ||||
|                      | ||||
|                     order = votesArr.map(function(vote) { return vote[0]; }); | ||||
|                 } | ||||
|                 event.reply(dbot.t('count', {'poll': name, 'description': polls[name].description, 'places': order})); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('poll_unexistent', {'name': name})); | ||||
|             } | ||||
|             }, this); | ||||
|         } | ||||
|     }; | ||||
|     commands['~newpoll'].regex = [/~newpoll (av )?([^ ]+) options=([^ ]+) (.+)/, 5]; | ||||
|     commands['~addoption'].regex = [/~addoption ([^ ]+) ([^ ]+)/, 3]; | ||||
|     commands['~rmoption'].regex = [/~rmoption ([^ ]+) ([^ ]+)/, 3]; | ||||
|     commands['~vote'].regex = [/~vote ([^ ]+) ([^ ]+)/, 3]; | ||||
|     commands['~pdesc'].regex = [/~pdesc ([^ ]+)/, 2]; | ||||
|     commands['~count'].regex = [/~count ([^ ]+)/, 2]; | ||||
|      | ||||
|     return { | ||||
|         'name': 'poll', | ||||
|         'ignorable': true, | ||||
|         'commands': commands | ||||
|     }; | ||||
| 
 | ||||
|     this.onLoad = function() { | ||||
|         dbot.api.command.addHook('~setaliasparent', this.internalAPI.updatePollNicks); | ||||
|         dbot.api.command.addHook('~mergeusers', this.internalAPI.updatePollNicks); | ||||
|     }.bind(this); | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return poll(dbot); | ||||
|     return new poll(dbot); | ||||
| } | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
|         "na'vi": "sìpawm sna'o '{name}' ngìyop ({description}). Nga tìpe'unit Pe'eiun - {url}" | ||||
|     }, | ||||
|     "poll_describe": { | ||||
|         "english": "{name}: {description} - {url}" | ||||
|         "english": "{name}: {description} - {url}." | ||||
|     }, | ||||
|     "changed_vote": { | ||||
|         "english": "{user} changed their vote in {poll} to '{vote}' ({count}).", | ||||
|  | ||||
							
								
								
									
										71
									
								
								modules/profile/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								modules/profile/api.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var api = function(dbot) { | ||||
|     return { | ||||
| 
 | ||||
|         /** | ||||
|          * Create a profile for a new primary user on a given server. | ||||
|          * If the server does not already exist, create it. | ||||
|          */ | ||||
|         "createProfile": function(server, primary){ | ||||
|             var primaryLower = primary.toLowerCase(); | ||||
| 
 | ||||
|             if(!_.has(this.profiles, server)){ | ||||
|                 this.profiles[server] = {}; | ||||
|             } | ||||
|             if(!_.has(this.profiles[server], primaryLower)){ | ||||
|                 this.profiles[server][primaryLower] = { | ||||
|                     "profile": {}, | ||||
|                     "preferences": {} | ||||
|                 }; | ||||
|                 this.profiles[server][primaryLower].profile.primary = primary; | ||||
|             } | ||||
| 
 | ||||
|             // Ensure all profiles have the keys specified by config.json
 | ||||
|             //TODO(samstudio8) Currently only handles "top-level"
 | ||||
|             _.defaults(this.profiles[server][primaryLower].profile, this.config.schema.profile); | ||||
|             _.defaults(this.profiles[server][primaryLower].preferences, this.config.schema.preferences); | ||||
|         }, | ||||
| 
 | ||||
|         /** | ||||
|          * Given a server and "new" alias, resolve this alias to the user's  | ||||
|          * new primary name and move profile data pertaining to the alias to  | ||||
|          * the new primary name. | ||||
|          */ | ||||
|         'renameProfile': function(server, alias){ | ||||
|             if(!_.has(this.profiles, server)) return; | ||||
|             var profiles = dbot.db.profiles[server]; | ||||
| 
 | ||||
|             if(_.has(profiles, alias)){ | ||||
|                 var primary = dbot.api.users.resolveUser(server, alias, true); | ||||
|                 var primaryLower = primary.toLowerCase(); | ||||
|                 alias = alias.trim().toLowerCase(); | ||||
| 
 | ||||
|                 profiles[primaryLower] = profiles[alias]; | ||||
|                 profiles[primaryLower].profile.primary = primary; | ||||
|                 delete profiles[alias]; | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         /** | ||||
|          * Given a server and a primary username which has been converted to a | ||||
|          * secondary alias find and remove the profile for the alias. | ||||
|          */ | ||||
|         'mergeProfile': function(server, mergeFromPrimary){ | ||||
|             if(!_.has(this.profiles, server)) return; | ||||
|             var profiles = dbot.db.profiles[server]; | ||||
| 
 | ||||
|             mergeFromPrimary = mergeFromPrimary.toLowerCase(); | ||||
|             var mergeToPrimary = dbot.api.users.resolveUser(server, mergeFromPrimary, true).toLowerCase(); | ||||
|             if(!_.has(profiles, mergeToPrimary) | ||||
|                     || !_.has(profiles, mergeFromPrimary)) return; | ||||
| 
 | ||||
|             // Remove the profile of the alias
 | ||||
|             delete profiles[mergeFromPrimary]; | ||||
|         }, | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return api(dbot); | ||||
| }; | ||||
							
								
								
									
										57
									
								
								modules/profile/commands.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								modules/profile/commands.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var commands = function(dbot){ | ||||
|     var commands = { | ||||
| 
 | ||||
|         "~getprop": function(event){ | ||||
|             if(event.params[1]){ | ||||
|                 var primary = dbot.api.users.resolveUser(event.server, event.user); | ||||
|                 var res = dbot.db.profiles[event.server][primary.toLowerCase()].profile[event.params[1]]; | ||||
|                 if(res){ | ||||
|                     event.reply(res); | ||||
|                 } | ||||
|                 else{ | ||||
|                     event.reply("Nope."); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "~setprop": function(event){ | ||||
|             if(event.input[1] && event.input[2]){ | ||||
|                 if(_.has(this.config.schema.profile, event.input[1])){ | ||||
|                     var primary = dbot.api.users.resolveUser(event.server, event.user); | ||||
|                     dbot.db.profiles[event.server][primary.toLowerCase()].profile[event.input[1]] = event.input[2]; | ||||
|                     event.reply("Property set, maybe?"); | ||||
|                 } | ||||
|                 else{ | ||||
|                     event.reply("Invalid property. Go home."); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "~profile": function(event){ | ||||
|             if(event.params[1]){ | ||||
|                 var primary = dbot.api.users.resolveUser(event.server, event.params[1]); | ||||
|                 if(_.has(dbot.db.profiles[event.server], primary.toLowerCase())){ | ||||
|                     event.reply("http://"+dbot.config.web.webHost+":"+dbot.config.web.webPort+"/profile/"+event.server+"/"+primary.toLowerCase()); | ||||
|                 } | ||||
|                 else{ | ||||
|                     event.reply("No profile found for "+event.params[1]); | ||||
|                 } | ||||
|             } | ||||
|             else{ | ||||
|                 event.message = '~profile ' + event.user; | ||||
|                 event.action = 'PRIVMSG'; | ||||
|                 event.params = event.message.split(' '); | ||||
|                 dbot.instance.emit(event); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|     commands['~setprop'].regex = [/~setprop ([^ ]+) (.+)/, 3]; | ||||
| 
 | ||||
|     return commands; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot){ | ||||
|     return commands(dbot); | ||||
| }; | ||||
							
								
								
									
										21
									
								
								modules/profile/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								modules/profile/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| { | ||||
|     "ignorable": false, | ||||
|     "dbKeys": [ "profiles" ], | ||||
|     "help": "https://github.com/reality/depressionbot/blob/master/modules/profile/README.md", | ||||
|     "schema": { | ||||
|         "profile": { | ||||
|             "primary": null, | ||||
|             "name": null, | ||||
|             "tagline": null, | ||||
|             "avatar": null, | ||||
|             "bio": null, | ||||
|             "favourites": { | ||||
|               "colour": null | ||||
|             } | ||||
|         }, | ||||
|         "preferences": { | ||||
|             "timezone": null | ||||
|         } | ||||
|     }, | ||||
|     "dependencies": [ "quotes", "users", "command" ] | ||||
| } | ||||
							
								
								
									
										67
									
								
								modules/profile/pages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								modules/profile/pages.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| var pages = function(dbot) { | ||||
|     var _ = require('underscore')._; | ||||
|     var connections = dbot.instance.connections; | ||||
| 
 | ||||
|     return { | ||||
|         '/profile/:connection/:user': function(req, res) { | ||||
|             var connection = req.params.connection; | ||||
|             var user = dbot.cleanNick(req.params.user); | ||||
| 
 | ||||
|             var primary = dbot.api.users.resolveUser(connection, user, true); | ||||
|             //var profile = dbot.api.profile.getProfile(primary);
 | ||||
|             var profile = dbot.db.profiles[connection][primary.toLowerCase()].profile; | ||||
|             var stats = dbot.api.stats.getUserChansStats(connection, primary.toLowerCase(), [ | ||||
|                     "lines", "words", "lincent", "wpl", "in_mentions"] | ||||
|             ); | ||||
| 
 | ||||
|             res.render('profile', { | ||||
|                 'name': dbot.config.name, | ||||
|                 'connection': connection, | ||||
|                 'primary': primary, | ||||
|                 'profile': profile, | ||||
|                 'stats': stats.channels, | ||||
|             }); | ||||
|         }, | ||||
| 
 | ||||
|         '/profile/:connection': function(req, res) { | ||||
|             var connection = req.params.connection; | ||||
|             var profiles = dbot.db.profiles[connection]; | ||||
| 
 | ||||
|             // TODO: Clean up
 | ||||
|             _.each(profiles, function(profile) { | ||||
|                 if(_.has(dbot.db.quoteArrs, profile.profile.primary) && !profile.profile.avatar) { | ||||
|                     var category = dbot.db.quoteArrs[profile.profile.primary]; | ||||
|                     var avatar = _.find(category, function(quote) { | ||||
|                         return quote.match(/(\.jpg|\.png|\.jpeg)$/i); | ||||
|                     }); | ||||
|                     if(avatar) profile.profile.avatar = avatar; | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             var nicks = []; | ||||
|             for (var p in profiles) { | ||||
|               if (profiles.hasOwnProperty(p) && profiles[p].profile.avatar) { | ||||
|                 nicks.push(p); | ||||
|               } | ||||
|             } | ||||
|             nicks.sort(function(a, b) { | ||||
|               var x = profiles[a].profile.primary.toLowerCase(); | ||||
|               var y = profiles[b].profile.primary.toLowerCase(); | ||||
|               if(x > y) return 1; | ||||
|               if(x < y) return -1; | ||||
|               return 0; | ||||
|             }); | ||||
| 
 | ||||
|             res.render('profile_grid', { | ||||
|                 'name': dbot.config.name, | ||||
|                 'connection': connection, | ||||
|                 'nicks': nicks, | ||||
|                 'profiles': profiles, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return pages(dbot); | ||||
| }; | ||||
							
								
								
									
										32
									
								
								modules/profile/profile.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								modules/profile/profile.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var profile = function(dbot) { | ||||
| 
 | ||||
|     this.profiles = dbot.db.profiles; | ||||
| 
 | ||||
|     /** | ||||
|      * Iterate over known user profiles and ensure they contain all the  | ||||
|      * required properties as defined in the configuation. | ||||
|      */ | ||||
|     this.onLoad = function(){ | ||||
|         var api = this.api; | ||||
|         var schema = this.config.schema; | ||||
| 
 | ||||
|         // Ensure all known users have a profile
 | ||||
|         _.each(dbot.api.users.getAllUsers(), function(server, serverName){ | ||||
|             _.each(server, function(primary, primaryi){ | ||||
|                 api.createProfile(serverName, primary); | ||||
|             }); | ||||
|         }); | ||||
|         dbot.save(); | ||||
|          | ||||
|         // Add API Hooks
 | ||||
|         dbot.api.command.addHook('~setaliasparent', this.api.renameProfile); | ||||
|         dbot.api.command.addHook('~mergeusers', this.api.mergeProfile); | ||||
|         dbot.api.event.addHook('new_user', this.api.createProfile); | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return new profile(dbot); | ||||
| }; | ||||
| @ -1,25 +0,0 @@ | ||||
| var puns = function(dbot) { | ||||
|     var name = 'puns'; | ||||
|     var dbot = dbot; | ||||
| 
 | ||||
|     return { | ||||
|         'name': name, | ||||
|         'ignorable': true, | ||||
| 
 | ||||
|         'listener': function(event) { | ||||
|             event.user = dbot.cleanNick(event.user); | ||||
|             if(dbot.config.moduleNames.include('quotes') && | ||||
|                     dbot.db.quoteArrs.hasOwnProperty(event.user)) { | ||||
|                 event.message = '~q ' + event.user; | ||||
|                 event.action = 'PRIVMSG'; | ||||
|                 event.params = event.message.split(' '); | ||||
|                 dbot.instance.emit(event); | ||||
|             } | ||||
|         }, | ||||
|         'on': 'JOIN' | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return puns(dbot); | ||||
| }; | ||||
							
								
								
									
										73
									
								
								modules/quotes/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								modules/quotes/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | ||||
| ## Quotes | ||||
| 
 | ||||
| Stores and displays quotes. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| This is the original reason that DBot was created, stores and displays quotes. | ||||
| 
 | ||||
| ### Configuration | ||||
| 
 | ||||
| #### rmLimit: 10 | ||||
| Amount of quotes which can be removed before admin approval is required. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~q [category] | ||||
| Display a random quote from a given category. | ||||
| 
 | ||||
| #### ~qadd [category] = [quote] | ||||
| Add a new quote to the database. | ||||
| 
 | ||||
| #### ~qstats | ||||
| Show a list of the biggest quote categories. | ||||
| 
 | ||||
| #### ~qsearch [category] = [needle] | ||||
| Search a category for quotes including the given text. | ||||
| 
 | ||||
| #### ~rmlast [category] | ||||
| Remove the last quote added to a given category. | ||||
| 
 | ||||
| #### ~rmstatus | ||||
| Show how many quotes are currently in the removal cache, and whether they will | ||||
| be randomly removed. | ||||
| 
 | ||||
| #### ~rm [category] = [quote] | ||||
| Remove a given quote from the given category. | ||||
| 
 | ||||
| #### ~qcount [category] | ||||
| Show the number of quotes stored in the given category, or if called without a | ||||
| category it will show the total number of quotes in the database. | ||||
| 
 | ||||
| #### ~rq | ||||
| Show a random quote from the database. | ||||
| 
 | ||||
| #### ~link [category] | ||||
| Show a link to the page on the web interface which shows this category's quotes. | ||||
| 
 | ||||
| ### Admin-only Commands | ||||
| 
 | ||||
| #### ~rmconfirm | ||||
| Confirm that the quotes currently in the removal cache are okay to be removed, | ||||
| and permanently delete them. | ||||
| 
 | ||||
| #### ~rmdeny | ||||
| Re-instate the quotes that are currently in the removal cache back into the main | ||||
| quote database. | ||||
| 
 | ||||
| ### API | ||||
| 
 | ||||
| #### getQuote(event, category) | ||||
| Returns a random quote from the given category. | ||||
| 
 | ||||
| ### Removal Spam Protection | ||||
| 
 | ||||
| When quotes are removed using either the ~rm or ~rmlast commands, the quotes are | ||||
| removed from the main database, but are stored in a removal cache which is cleared  | ||||
| out ten minutes from the last time a quote was removed from the database. If the  | ||||
| number of quotes removed from the database reaches a certain limit (as per rmLimit  | ||||
| in config, default 10) then the counter is removed and the cache will not be deleted  | ||||
| automatically. In such a case, a DBot admin needs to either run the ~rmconfim command | ||||
| to have the removal cache cleared, or ~rmdeny to re-instate all of the quotes in | ||||
| the removal cache back into the main quote database. This is to stop mass | ||||
| removal from the database without limiting the user interface. | ||||
							
								
								
									
										226
									
								
								modules/quotes/commands.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								modules/quotes/commands.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,226 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var commands = function(dbot) { | ||||
|     var quotes = dbot.db.quoteArrs; | ||||
|     var commands = { | ||||
|         // Alternative syntax to ~q
 | ||||
|         '~': function(event) { | ||||
|             commands['~q'].bind(this)(event); | ||||
|         }, | ||||
| 
 | ||||
|         '~rmstatus': function(event) { | ||||
|             var rmCacheCount = this.rmCache.length; | ||||
|             if(rmCacheCount < dbot.config.quotes.rmLimit) { | ||||
|                 event.reply(dbot.t('quote_cache_auto_remove',  | ||||
|                     { 'count': rmCacheCount })); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('quote_cache_manual_remove',  | ||||
|                     { 'count': rmCacheCount })); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~rmconfirm': function(event) { | ||||
|             var rmCacheCount = this.rmCache.length; | ||||
|             this.rmCache.length = 0; | ||||
|             event.reply(dbot.t('quote_cache_cleared',  | ||||
|                 { 'count': rmCacheCount })); | ||||
|         }, | ||||
| 
 | ||||
|         '~rmdeny': function(event) { | ||||
|             var rmCache = this.rmCache; | ||||
|             var rmCacheCount = rmCache.length; | ||||
|             for(var i=0;i<rmCacheCount;i++) { | ||||
|                 if(!_.has(quotes, rmCache[i].key)) { | ||||
|                     quotes[rmCache[i].key] = []; | ||||
|                 } | ||||
|                 quotes[rmCache[i].key].push(rmCache[i].quote); | ||||
|             } | ||||
|             rmCache.length = 0; | ||||
| 
 | ||||
|             event.reply(dbot.t('quote_cache_reinstated',  | ||||
|                 { 'count': rmCacheCount })); | ||||
|         }, | ||||
| 
 | ||||
| 
 | ||||
|         // Retrieve quote from a category in the database.
 | ||||
|         '~q': function(event) {  | ||||
|             var key = event.input[1].trim().toLowerCase(); | ||||
|             var quote = this.api.getQuote(event, event.input[1]); | ||||
|             if(quote) { | ||||
|                 event.reply(key + ': ' + quote); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('category_not_found', {'category': key})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Shows a list of the biggest categories
 | ||||
|         '~qstats': function(event) { | ||||
|             var qSizes = _.chain(quotes) | ||||
|                 .pairs() | ||||
|                 .sortBy(function(category) { return category[1].length }) | ||||
|                 .reverse() | ||||
|                 .first(10) | ||||
|                 .value(); | ||||
| 
 | ||||
|             var qString = dbot.t('large_categories'); | ||||
|             for(var i=0;i<qSizes.length;i++) { | ||||
|                 qString += qSizes[i][0] + " (" + qSizes[i][1].length + "), "; | ||||
|             } | ||||
| 
 | ||||
|             event.reply(qString.slice(0, -2)); | ||||
|         }, | ||||
|          | ||||
|         // Search a given category for some text.
 | ||||
|         // TODO fix
 | ||||
|         '~qsearch': function(event) { | ||||
|             var haystack = event.input[1].trim().toLowerCase(); | ||||
|             var needle = event.input[2]; | ||||
|             if(_.has(quotes, haystack)) { | ||||
|                 var matches = _.filter(quotes[haystack], function(quote) { | ||||
|                     return _.indexOf(quote, needle) != -1; | ||||
|                 }, this); | ||||
| 
 | ||||
|                 if(matches.length == 0) { | ||||
|                     event.reply(dbot.t('no_results')); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('search_results', { | ||||
|                         'category': haystack,  | ||||
|                         'needle': needle, | ||||
|                         'quote': matches.random(),  | ||||
|                         'matches': matches.length | ||||
|                     })); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('empty_category')); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~rmlast': function(event) { | ||||
|             if(this.rmAllowed === true || _.include(dbot.config.admins, event.user)) { | ||||
|                 var key = event.input[1].trim().toLowerCase(); | ||||
|                 if(_.has(quotes, key)) { | ||||
|                     var quote = quotes[key].pop(); | ||||
|                     if(quotes[key].length == 0) { | ||||
|                         delete quotes[key]; | ||||
|                     } | ||||
|                     this.internalAPI.resetRemoveTimer(event, key, quote); | ||||
| 
 | ||||
|                     event.reply(dbot.t('removed_from', { | ||||
|                         'quote': quote,  | ||||
|                         'category': key | ||||
|                     })); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('no_quotes', {'category': q[1]})); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('rmlast_spam')); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~rm': function(event) { | ||||
|             if(this.rmAllowed == true || _.include(dbot.config.admins, event.user)) { | ||||
|                 var key = event.input[1].trim().toLowerCase(); | ||||
|                 var quote = event.input[2]; | ||||
| 
 | ||||
|                 if(_.has(quotes, key)) { | ||||
|                     var category = quotes[key]; | ||||
|                     var index = category.indexOf(quote); | ||||
|                     if(index !== -1) { | ||||
|                         category.splice(index, 1); | ||||
|                         if(category.length === 0) { | ||||
|                             delete quotes[key]; | ||||
|                         } | ||||
|                         this.internalAPI.resetRemoveTimer(event, key, quote); | ||||
| 
 | ||||
|                         event.reply(dbot.t('removed_from', {'category': key, 'quote': quote})); | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('q_not_exist_under', {'category': key, 'quote': quote})); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('category_not_found', {'category': key})); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('rmlast_spam')); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~qcount': function(event) { | ||||
|             var input = event.message.valMatch(/^~qcount ([\d\w\s-]*)/, 2); | ||||
|             if(input) { // Give quote count for named category
 | ||||
|                 var key = input[1].trim().toLowerCase(); | ||||
|                 if(_.has(quotes, key)) { | ||||
|                     event.reply(dbot.t('quote_count', { | ||||
|                         'category': key,  | ||||
|                         'count': quotes[key].length | ||||
|                     })); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('no_quotes', { 'category': key })); | ||||
|                 } | ||||
|             } else { // Give total quote count
 | ||||
|                 var totalQuoteCount = _.reduce(quotes, function(memo, category) { | ||||
|                     return memo + category.length; | ||||
|                 }, 0); | ||||
|                 event.reply(dbot.t('total_quotes', { 'count': totalQuoteCount })); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~qadd': function(event) { | ||||
|             var key = event.input[1].toLowerCase(); | ||||
|             var text = event.input[2]; | ||||
|             if(!_.isArray(quotes[key])) { | ||||
|                 quotes[key] = []; | ||||
|             }  | ||||
| 
 | ||||
|             if(_.include(quotes[key], text)) { | ||||
|                 event.reply(dbot.t('quote_exists')); | ||||
|             } else { | ||||
|                 quotes[key].push(text); | ||||
|                 this.rmAllowed = true; | ||||
|                 event.reply(dbot.t('quote_saved', { | ||||
|                     'category': key,  | ||||
|                     'count': quotes[key].length | ||||
|                 })); | ||||
| 
 | ||||
|                 return { 'key': key, 'text': text }; | ||||
|             } | ||||
|             return false; | ||||
|         }, | ||||
| 
 | ||||
|         '~rq': function(event) { | ||||
|             var category = _.keys(quotes)[_.random(0, _.size(quotes) -1)]; | ||||
|             event.reply(category + ': ' + this.internalAPI.interpolatedQuote(event.server, event.channel.name, category)); | ||||
|         }, | ||||
|          | ||||
|         '~link': function(event) { | ||||
|             var key = event.params[1].trim().toLowerCase(); | ||||
|             if(_.has(quotes, key)) { | ||||
|                 event.reply(dbot.t('quote_link', { | ||||
|                     'category': key,  | ||||
|                     'url': dbot.t('url', { | ||||
|                         'host': dbot.config.web.webHost,  | ||||
|                         'port': dbot.config.web.webPort,  | ||||
|                         'path': 'quotes/' + key | ||||
|                     }) | ||||
|                 })); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('category_not_found', { 'category': key })); | ||||
|             } | ||||
|         }, | ||||
|     }; | ||||
| 
 | ||||
|     commands['~'].regex = [/^~([\d\w\s-]*)/, 2]; | ||||
|     commands['~q'].regex = [/^~q ([\d\w\s-]*)/, 2]; | ||||
|     commands['~qsearch'].regex = [/^~qsearch ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3]; | ||||
|     commands['~rm'].regex = [/^~rm ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3]; | ||||
|     commands['~rmlast'].regex = [/^~rmlast ([\d\w\s-]*)/, 2]; | ||||
|     commands['~qadd'].regex = [/^~qadd ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3]; | ||||
| 
 | ||||
|     commands['~rmconfirm'].access = 'moderator'; | ||||
|     commands['~rmdeny'].access = 'moderator'; | ||||
| 
 | ||||
|     return commands; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return commands(dbot); | ||||
| }; | ||||
| @ -1,3 +1,7 @@ | ||||
| { | ||||
|     "dbKeys": [ "quoteArrs" ] | ||||
|     "dbKeys": [ "quoteArrs" ], | ||||
|     "dependencies": [ "command", "users" ], | ||||
|     "rmLimit": 10, | ||||
|     "ignorable": true, | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/quotes/README.md" | ||||
| } | ||||
|  | ||||
							
								
								
									
										29
									
								
								modules/quotes/pages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								modules/quotes/pages.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| var _ = require('underscore')._; | ||||
| var pages = function(dbot) { | ||||
|     return { | ||||
|         // Lists quotes in a category
 | ||||
|         '/quotes/:key': function(req, res) { | ||||
|             var key = req.params.key.toLowerCase(); | ||||
|             if(_.has(dbot.db.quoteArrs, key)) { | ||||
|                 res.render('quotes', { 'name': dbot.config.name, 'quotes': dbot.db.quoteArrs[key], locals: { 'url_regex': RegExp.prototype.url_regex() } }); | ||||
|             } else { | ||||
|                 res.render('error', { 'name': dbot.config.name, 'message': 'No quotes under that key.' }); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Show quote list.
 | ||||
|         '/quotes': function(req, res) { | ||||
|             res.render('quotelist', { 'name': dbot.config.name, 'quotelist': Object.keys(dbot.db.quoteArrs) }); | ||||
|         }, | ||||
| 
 | ||||
|         // Load random quote category page
 | ||||
|         '/rq': function(req, res) { | ||||
|             var rCategory = Object.keys(dbot.db.quoteArrs).random(); | ||||
|             res.render('quotes', { 'name': dbot.config.name, 'quotes': dbot.db.quoteArrs[rCategory], locals: { 'url_regex': RegExp.prototype.url_regex() } }); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return pages(dbot); | ||||
| }; | ||||
| @ -1,256 +1,115 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var quotes = function(dbot) { | ||||
|     var name = 'quotes'; | ||||
|     var quotes = dbot.db.quoteArrs; | ||||
|     var addStack = []; | ||||
|     var rmAllowed = true; | ||||
|     dbot.sessionData.rmCache = []; | ||||
|     this.quotes = dbot.db.quoteArrs, | ||||
|     this.addStack = [], | ||||
|     this.rmAllowed = true, | ||||
|     this.rmCache = dbot.sessionData.rmCache, | ||||
|     this.rmTimer; | ||||
| 
 | ||||
|     // Retrieve a random quote from a given category, interpolating any quote
 | ||||
|     // references (~~QUOTE CATEGORY~~) within it
 | ||||
|     var interpolatedQuote = function(key, quoteTree) { | ||||
|         if(quoteTree !== undefined && quoteTree.indexOf(key) != -1) {  | ||||
|             return '';  | ||||
|         } else if(quoteTree === undefined) {  | ||||
|             quoteTree = []; | ||||
|         } | ||||
| 
 | ||||
|         var quoteString = quotes[key].random(); | ||||
| 
 | ||||
|         // Parse quote interpolations
 | ||||
|         var quoteRefs = quoteString.match(/~~([\d\w\s-]*)~~/g); | ||||
|         var thisRef; | ||||
| 
 | ||||
|         while(quoteRefs && (thisRef = quoteRefs.shift()) !== undefined) { | ||||
|             var cleanRef = dbot.cleanNick(thisRef.replace(/^~~/,'').replace(/~~$/,'').trim()); | ||||
|             if (quotes.hasOwnProperty(cleanRef)) { | ||||
|                 quoteTree.push(key); | ||||
|                 quoteString = quoteString.replace("~~" + cleanRef + "~~",  | ||||
|                         interpolatedQuote(cleanRef, quoteTree.slice())); | ||||
|                 quoteTree.pop(); | ||||
|     this.internalAPI = { | ||||
|         // Retrieve a random quote from a given category, interpolating any quote
 | ||||
|         // references (~~QUOTE CATEGORY~~) within it
 | ||||
|         'interpolatedQuote': function(server, channel, key, quoteTree) { | ||||
|             if(!_.isUndefined(quoteTree) && quoteTree.indexOf(key) != -1) {  | ||||
|                 return '';  | ||||
|             } else if(_.isUndefined(quoteTree)) {  | ||||
|                 quoteTree = []; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return quoteString; | ||||
|             var index = _.random(0, this.quotes[key].length - 1); | ||||
|             var quoteString = this.quotes[key][index]; | ||||
| 
 | ||||
|             // Parse quote interpolations
 | ||||
|             var quoteRefs = quoteString.match(/~~([\d\w\s-]*)~~/g); | ||||
|             var thisRef; | ||||
| 
 | ||||
|             while(quoteRefs && (thisRef = quoteRefs.shift()) !== undefined) { | ||||
|                 var cleanRef = dbot.cleanNick(thisRef.replace(/^~~/,'').replace(/~~$/,'').trim()); | ||||
|                 if(cleanRef === '-nicks-') { | ||||
|                     var randomNick = dbot.api.users.getRandomChannelUser(server, channel); | ||||
|                     quoteString = quoteString.replace("~~" + cleanRef + "~~", randomNick); | ||||
|                     quoteTree.pop(); | ||||
|                 } else if(_.has(this.quotes, cleanRef)) { | ||||
|                     quoteTree.push(key); | ||||
|                     quoteString = quoteString.replace("~~" + cleanRef + "~~",  | ||||
|                             this.internalAPI.interpolatedQuote(server, channel, cleanRef, quoteTree.slice())); | ||||
|                     quoteTree.pop(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return quoteString; | ||||
|         }.bind(this), | ||||
| 
 | ||||
|         'resetRemoveTimer': function(event, key, quote) { | ||||
|             this.rmAllowed = false; | ||||
|             setTimeout(function() { | ||||
|                 this.rmAllowed = true; | ||||
|             }.bind(this), 5000); | ||||
| 
 | ||||
|             this.rmCache.push({ | ||||
|                 'key': key,  | ||||
|                 'quote': quote | ||||
|             }); | ||||
| 
 | ||||
|             clearTimeout(this.rmTimer); | ||||
|             if(this.rmCache.length < dbot.config.quotes.rmLimit) { | ||||
|                 this.rmTimer = setTimeout(function() { | ||||
|                     this.rmCache.length = 0; // lol what
 | ||||
|                 }.bind(this), 600000); | ||||
|             } else { | ||||
|                 _.each(dbot.config.admins, function(admin) { | ||||
|                     dbot.say(event.server, admin, dbot.t('rm_cache_limit')); | ||||
|                 }); | ||||
|             } | ||||
|         }.bind(this) | ||||
|     }; | ||||
| 
 | ||||
|     var commands = { | ||||
|         // Alternative syntax to ~q
 | ||||
|         '~': function(event) { | ||||
|             commands['~q'](event); | ||||
|         }, | ||||
| 
 | ||||
|         // Retrieve quote from a category in the database.
 | ||||
|         '~q': function(event) {  | ||||
|             var key = event.input[1].trim().toLowerCase(); | ||||
|     this.api = { | ||||
|         'getQuote': function(event, category) { | ||||
|             var key = category.trim().toLowerCase(); | ||||
|             var altKey; | ||||
|             if(key.split(' ').length > 0) { | ||||
|                 altKey = key.replace(/ /g, '_'); | ||||
|             } | ||||
| 
 | ||||
|             if(key.charAt(0) !== '_') { // lol
 | ||||
|                 if(quotes.hasOwnProperty(key)) { | ||||
|                     event.reply(key + ': ' + interpolatedQuote(key)); | ||||
|                 } else if(quotes.hasOwnProperty(altKey)) { | ||||
|                     event.reply(altKey + ': ' + interpolatedQuote(altKey)); | ||||
|                 if(_.has(this.quotes, key)) { | ||||
|                     return this.internalAPI.interpolatedQuote(event.server, event.channel.name, key); | ||||
|                 } else if(_.has(this.quotes, altKey)) { | ||||
|                     return this.internalAPI.interpolatedQuote(event.server, event.channel.name, altKey); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('category_not_found', {'category': key})); | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         // Shows a list of the biggest categories
 | ||||
|         '~qstats': function(event) { | ||||
|             var qSizes = Object.prototype.sort(quotes, function(key, obj) { return obj[key].length }); | ||||
|             qSizes = qSizes.slice(qSizes.length - 10).reverse(); | ||||
| 
 | ||||
|             var qString = dbot.t('large_categories'); | ||||
|             for(var i=0;i<qSizes.length;i++) { | ||||
|                 qString += qSizes[i][0] + " (" + qSizes[i][1] + "), "; | ||||
|             } | ||||
| 
 | ||||
|             event.reply(qString.slice(0, -2)); | ||||
|         }, | ||||
|          | ||||
|         // Search a given category for some text.
 | ||||
|         '~qsearch': function(event) { | ||||
|             var haystack = event.input[1].trim().toLowerCase(); | ||||
|             var needle = event.input[2]; | ||||
|             if(quotes.hasOwnProperty(haystack)) { | ||||
|                 var matches = []; | ||||
|                 quotes[haystack].each(function(quote) { | ||||
|                     if(quote.indexOf(needle) != -1) { | ||||
|                         matches.push(quote); | ||||
|                     } | ||||
|                 }.bind(this)); | ||||
| 
 | ||||
|                 if(matches.length == 0) { | ||||
|                     event.reply(dbot.t('no_results')); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('search_results', {'category': haystack, 'needle': needle, | ||||
|                         'quote': matches.random(), 'matches': matches.length})); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('empty_category')); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~rmlast': function(event) { | ||||
|             if(rmAllowed == true || dbot.config.admins.include(event.user)) { | ||||
|                 var key = event.input[1].trim().toLowerCase(); | ||||
|                 if(quotes.hasOwnProperty(key)) { | ||||
|                     if(!dbot.db.locks.include(key) || dbot.config.admins.include(event.user)) { | ||||
|                         var quote = quotes[key].pop(); | ||||
|                         if(quotes[key].length === 0) { | ||||
|                             delete quotes[key]; | ||||
|                         } | ||||
|                         rmAllowed = false; | ||||
|                         event.reply(dbot.t('removed_from', {'quote': quote, 'category': key})); | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('locked_category', {'category': q[1]})); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('no_quotes', {'category': q[1]})); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('rmlast_spam')); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~rm': function(event) { | ||||
|             if(rmAllowed == true || dbot.config.admins.include(event.user)) { | ||||
|                 var key = event.input[1].trim().toLowerCase(); | ||||
|                 var quote = event.input[2]; | ||||
| 
 | ||||
|                 if(quotes.hasOwnProperty(key)) { | ||||
|                     if(!dbot.db.locks.include(key)) { | ||||
|                         var category = quotes[key]; | ||||
|                         var index = category.indexOf(quote); | ||||
|                         if(index !== -1) { | ||||
|                             category.splice(index, 1); | ||||
|                             if(category.length === 0) { | ||||
|                                 delete quotes[key]; | ||||
|                             } | ||||
|                             event.reply(dbot.t('removed_from', {'category': key, 'quote': quote})); | ||||
|                         } else { | ||||
|                             event.reply(dbot.t('q_not_exist_under', {'category': key, 'quote': quote})); | ||||
|                         } | ||||
|                     } else { | ||||
|                         event.reply(dbot.t('locked_category', {'category': key})); | ||||
|                     } | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('category_not_found', {'category': key})); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply(dbot.t('rmlast_spam')); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~qcount': function(event) { | ||||
|             var input = event.message.valMatch(/^~qcount ([\d\w\s-]*)/, 2); | ||||
|             if(input) { // Give quote count for named category
 | ||||
|                 var key = input[1].trim().toLowerCase(); | ||||
|                 if(quotes.hasOwnProperty(key)) { | ||||
|                     event.reply(dbot.t('quote_count', {'category': key, 'count': quotes[key].length})); | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('no_quotes', {'category': key})); | ||||
|                 } | ||||
|             } else { // Give total quote count
 | ||||
|                 var totalQuoteCount = 0; | ||||
|                 for(var category in quotes) { | ||||
|                     if(quotes.hasOwnProperty(category)) { | ||||
|                         totalQuoteCount += quotes[category].length; | ||||
|                     } | ||||
|                 } | ||||
|                 event.reply(dbot.t('total_quotes', {'count': totalQuoteCount})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~qadd': function(event) { | ||||
|             var key = event.input[1].toLowerCase(); | ||||
|             var text = event.input[2]; | ||||
|             if(!Object.isArray(quotes[key])) { | ||||
|                 quotes[key] = []; | ||||
|             }  | ||||
| 
 | ||||
|             if(quotes[key].include(text)) { | ||||
|                 event.reply(dbot.t('quote_exists')); | ||||
|             } else { | ||||
|                 quotes[key].push(text); | ||||
|                 rmAllowed = true; | ||||
|                 event.reply(dbot.t('quote_saved', {'category': key, 'count': quotes[key].length})); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~rq': function(event) { | ||||
|             var rQuote = Object.keys(quotes).random(); | ||||
|             event.reply(rQuote + ': ' + interpolatedQuote(rQuote)); | ||||
|         }, | ||||
|          | ||||
|         '~link': function(event) { | ||||
|             var key = event.params[1].trim().toLowerCase(); | ||||
|             if(quotes.hasOwnProperty(key)) { | ||||
|                 event.reply(dbot.t('quote_link', {'category': key,  | ||||
|                     'url': dbot.t('url', {'host': dbot.config.web.webHost,  | ||||
|                     'port': dbot.config.web.webPort, 'path': 'quotes/' + key})})); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('category_not_found', {'category': key})); | ||||
|             } | ||||
|         }, | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     commands['~'].regex = [/^~([\d\w\s-]*)/, 2]; | ||||
|     commands['~q'].regex = [/^~q ([\d\w\s-]*)/, 2]; | ||||
|     commands['~qsearch'].regex = [/^~qsearch ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3]; | ||||
|     commands['~rm'].regex = [/^~rm ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3]; | ||||
|     commands['~rmlast'].regex = [/^~rmlast ([\d\w\s-]*)/, 2]; | ||||
|     commands['~qadd'].regex = [/^~qadd ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3]; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'quotes', | ||||
|         'ignorable': true, | ||||
|         'commands': commands, | ||||
| 
 | ||||
|         'onLoad': function() { | ||||
|             dbot.timers.addTimer(1000 * 60 * 3, function() { | ||||
|                 rmAllowed = true; | ||||
|             }); | ||||
|         }, | ||||
| 
 | ||||
|         'listener': function(event) { | ||||
|             // Reality Once listener
 | ||||
|             if((dbot.db.ignores.hasOwnProperty(event) &&  | ||||
|                         dbot.db.ignores[event.user].include(name)) == false) { | ||||
|                 if(event.user == 'reality') { | ||||
|                     var once = event.message.valMatch(/^I ([\d\w\s,'-]* once)/, 2); | ||||
|                 } else { | ||||
|                     var once = event.message.valMatch(/^reality ([\d\w\s,'-]* once)/, 2); | ||||
|                 } | ||||
| 
 | ||||
|                 if(once) { | ||||
|                     if((dbot.db.bans.hasOwnProperty('~qadd') && | ||||
|                     dbot.db.bans['~qadd'].include(event.user)) || | ||||
|                     dbot.db.bans['*'].include(event.user)) { | ||||
|                         event.reply(dbot.t('command_ban', {'user': event.user}));  | ||||
|                     } else { | ||||
|                         if(!dbot.db.quoteArrs.hasOwnProperty('realityonce')) { | ||||
|                             dbot.db.quoteArrs['realityonce'] = []; | ||||
|                         } | ||||
|                         if(dbot.db.quoteArrs['realityonce'].include('reality ' + once[1] + '.')) { | ||||
|                             event.reply(event.user + ': reality has already done that once.'); | ||||
|                         } else { | ||||
|                             dbot.db.quoteArrs['realityonce'].push('reality ' + once[1] + '.'); | ||||
|                             addStack.push('realityonce'); | ||||
|                             rmAllowed = true; | ||||
|                             event.reply('\'reality ' + once[1] + '.\' saved.'); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|     | ||||
|     this.listener = function(event) { | ||||
|         if(event.action == 'PRIVMSG') { | ||||
|             if(event.user == 'reality') { | ||||
|                 var once = event.message.valMatch(/^I ([\d\w\s,'-]* once)/, 2); | ||||
|             } else { | ||||
|                 var once = event.message.valMatch(/^reality ([\d\w\s,'-]* once)/, 2); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         'on': 'PRIVMSG' | ||||
|     }; | ||||
|             if(once) { | ||||
|                 event.message = '~qadd realityonce=reality ' + once[1]; | ||||
|                 event.action = 'PRIVMSG'; | ||||
|                 event.params = event.message.split(' '); | ||||
|                 dbot.instance.emit(event); | ||||
|            } | ||||
|         } else if(event.action == 'JOIN') { | ||||
|             var userQuote = this.api.getQuote(event, event.user) | ||||
|             if(userQuote) { | ||||
|                 event.reply(event.user + ': ' + this.api.getQuote(event, event.user)); | ||||
|             } | ||||
|         } | ||||
|     }.bind(this); | ||||
|     this.on = ['PRIVMSG', 'JOIN']; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return quotes(dbot); | ||||
|     return new quotes(dbot); | ||||
| }; | ||||
|  | ||||
| @ -106,5 +106,25 @@ | ||||
|         "spanish" : "{category} ({needle}): '{quote}' [{matches} resultados]", | ||||
|         "na'vi": "{category} ({needle}): '{quote}' [kum a{matches}]", | ||||
|         "welsh": "{category} ({needle}): '{quote}' [{matches} canlyniad]" | ||||
|     }, | ||||
|     "quote_cache_auto_remove": { | ||||
|         "english": "There are {count} quotes in the removal cache, which will be automatically cleared.", | ||||
|         "na'vi": "{count}a 'upxarel sngelit tok, Oel 'ayku sngelit lukenga." | ||||
|     }, | ||||
|     "quote_cache_manual_remove": { | ||||
|         "english": "There are {count} quotes in the removal cache, which must be manually cleared.", | ||||
|         "na'vi": "{count}a 'upxarel sngelit tok slä oel ke 'ayku sngelit tafral nga zene 'aivku" | ||||
|     }, | ||||
|     "quote_cache_cleared": { | ||||
|         "english": "{count} quotes cleared from the removal cache.", | ||||
|         "na'vi": "Oel 'aìmku {count}a 'upxareti ta sngel." | ||||
|     }, | ||||
|     "quote_cache_reinstated": { | ||||
|         "english": "{count} quotes reinstated from the removal cache.", | ||||
|         "na'vi": "{count}a 'upxare tolätxaw ta sngel." | ||||
|     }, | ||||
|     "rm_cache_limit": { | ||||
|         "english": "Attention: Too many quotes removed, rmCache must be cleared or reinstated manually with ~rmconfirm or ~rmdeny.", | ||||
|         "na'vi": "Oel zerok 'upxareti apxay set, sweylu txo nga 'aivku upxareti ìlä ~rmconfirm fu ~rmdeny." | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										21
									
								
								modules/regex/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								modules/regex/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| ## Regex | ||||
| 
 | ||||
| Apply regex and that. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| Allows you to run regex replaces on both your own and others messages. One may | ||||
| run a regex on their own last message like so: | ||||
| 
 | ||||
|     > user: I like turtles | ||||
|     > user: s/turtles/pizza/ | ||||
| 
 | ||||
| One may run a regex on another user's last message simple by hilighting the nick | ||||
| before the pattern: | ||||
| 
 | ||||
|     > batman: I like TURTLES | ||||
|     > user: batman: s/turtles/pizza/i | ||||
| 
 | ||||
| Note: As this is JS regex, the second part of the regex is actually just a | ||||
| string and therefore some regex features aren't available (such as lookaheads). | ||||
| On a related note, the regex flags available for use are limited to i and g. | ||||
							
								
								
									
										4
									
								
								modules/regex/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								modules/regex/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| { | ||||
|     "ignorable": true, | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/regex/README.md" | ||||
| } | ||||
							
								
								
									
										40
									
								
								modules/regex/regex.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								modules/regex/regex.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var regex = function(dbot) { | ||||
|     this.last = {}; | ||||
|     this.listener = function(event) { | ||||
|         var q = event.message.valMatch(/^([\d\w\s]*)?:? ?s\/(.+)\/(.+)?\/([ig]*)?$/, 5); | ||||
|         if(q) { | ||||
|             var flags = q[4], | ||||
|                 toMatch = new RegExp(q[2], flags), | ||||
|                 replaceWith = q[3], | ||||
|                 last, | ||||
|                 replacement; | ||||
|                  | ||||
|             if(!replaceWith) replaceWith = ""; | ||||
| 
 | ||||
|             if(q[1] != null) { | ||||
|                 var user = q[1]; | ||||
|                 last = this.last[event.channel.name][user]; | ||||
|                 replacement = last.replace(toMatch, replaceWith); | ||||
|                 if(replacement != last) event.reply(event.user + " thinks " + user + " meant: " + replacement); | ||||
|             } else { | ||||
|                 last = this.last[event.channel.name][event.user]; | ||||
|                 replacement = last.replace(toMatch, replaceWith); | ||||
|                 if(replacement != last) event.reply(event.user + " meant: " + replacement); | ||||
|             } | ||||
|         } else { | ||||
|             if(_.has(this.last, event.channel.name)) { | ||||
|                this.last[event.channel.name][event.user] = event.message;  | ||||
|             } else { | ||||
|                 this.last[event.channel.name] = { }; | ||||
|                 this.last[event.channel.name][event.user] = event.message; | ||||
|             } | ||||
|         } | ||||
|     }.bind(this); | ||||
|     this.on = [ 'PRIVMSG' ]; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return new regex(dbot); | ||||
| }; | ||||
							
								
								
									
										19
									
								
								modules/report/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								modules/report/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| ## Report | ||||
| 
 | ||||
| Report users | ||||
| 
 | ||||
| ### Description | ||||
| This module provides a command which allows users to report other users in a | ||||
| channel to the operators of the channel, as well as posting an alert in the | ||||
| administrative channel. It can be done either anonymously or publicly in the | ||||
| channel. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~report [#channel] [username] [reason for reporting] | ||||
| Report a user in a channel for a reason. This command can either be run publicly | ||||
| in a channel or anonymously in a PM to the bot. The result of using this command | ||||
| will be that all of the users which are currently marked as operators in the | ||||
| reporting channel will receive a PM telling them a user has been reported, by | ||||
| whom, in which channel and why. If there is an administrative channel for the | ||||
| reporting channel (e.g. ##channel), the report will be posted there as well. | ||||
							
								
								
									
										5
									
								
								modules/report/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								modules/report/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| { | ||||
|     "ignorable": true, | ||||
|     "dependencies": [ "command", "users" ], | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/report/README.md" | ||||
| } | ||||
| @ -1,3 +1,5 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var report = function(dbot) { | ||||
|     var commands = { | ||||
|         '~report': function(event) { | ||||
| @ -5,47 +7,37 @@ var report = function(dbot) { | ||||
|             var nick = event.input[2]; | ||||
|             var reason = event.input[3]; | ||||
| 
 | ||||
|             if(event.allChannels.hasOwnProperty(channelName)) { | ||||
|             if(_.has(event.allChannels, channelName)) { | ||||
|                 var channel = event.allChannels[channelName]; | ||||
|                 if(channel.nicks.hasOwnProperty(nick)) { | ||||
|                     var ops = []; | ||||
|                     for(var possibOps in channel.nicks) { | ||||
|                         if(channel.nicks[possibOps].op == true) { | ||||
|                             ops.push(possibOps); | ||||
|                         } | ||||
|                     } | ||||
|                 if(dbot.api.users.isChannelUser(event.server, nick, channelName, true)) { | ||||
|                     var nick = dbot.api.users.resolveUser(event.server, nick, true); | ||||
|                     var ops = _.filter(channel.nicks, function(user) { | ||||
|                         return user.op;  | ||||
|                     }); | ||||
| 
 | ||||
|                     // Does the channel have an admin channel?
 | ||||
|                     if(event.allChannels.hasOwnProperty('#' + channelName)) { | ||||
|                         ops.push('#' + channelName); | ||||
|                     } | ||||
|                     _.each(ops, function(user) { | ||||
|                         dbot.say(event.server, user.name, dbot.t('report', { | ||||
|                             'reporter': event.user, | ||||
|                             'reported': nick, | ||||
|                             'channel': channelName, | ||||
|                             'reason': reason | ||||
|                         })); | ||||
|                     }, this); | ||||
| 
 | ||||
|                     for(var i=0;i<ops.length;i++) { | ||||
|                         dbot.say(event.server, ops[i],  | ||||
|                             'Attention: ' + event.user + ' has reported ' + | ||||
|                             nick + ' in ' + channelName + '. The reason ' + | ||||
|                             'given was: "' + reason + '."'); | ||||
|                     } | ||||
| 
 | ||||
|                     event.reply('Thank you, ' + nick + ' has been reported the channel administrators.'); | ||||
|                     event.reply(dbot.t('reported', { 'reported': nick })); | ||||
|                 } else { | ||||
|                     event.reply('User is not in that channel.'); | ||||
|                     event.reply(dbot.t('user_not_found', { 'reported': nick, | ||||
|                         'channel': channelName })); | ||||
|                 } | ||||
|             } else { | ||||
|                 event.reply('I am not in that channel.'); | ||||
|                 event.reply(dbot.t('not_in_channel', { 'channel': channelName })); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     }; | ||||
|     commands['~report'].regex = [/^~report ([^ ]+) ([^ ]+) (.+)$/, 4]; | ||||
| 
 | ||||
|     return { | ||||
|         'name': 'report', | ||||
|         'ignorable': true, | ||||
|         'commands': commands | ||||
|     }; | ||||
|     this.commands = commands; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return report(dbot); | ||||
|     return new report(dbot); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										18
									
								
								modules/report/strings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								modules/report/strings.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| { | ||||
|     "report": { | ||||
|         "english": "Attention: {reporter} has reported {reported} in {channel}. The reason given was: \"{reason}.\"", | ||||
|         "na'vi": "{reporter}ìl fpìl futa {reported} fe' lu taluna {reason}." | ||||
|     }, | ||||
|     "reported": { | ||||
|         "english": "Thank you, {reported} has been reported to the channel administrators.", | ||||
|         "na'vi": "Irayo si ngari, fìtsengìri ayeyktan omum teri {reported}it set." | ||||
|     }, | ||||
|     "user_not_found": { | ||||
|         "english": "{reported} isn't a known user in {channel}.", | ||||
|         "na'vi": "Oel ke omum {reported}it mì {channel}." | ||||
|     }, | ||||
|     "not_in_channel": { | ||||
|         "english": "I am not present in {channel}.", | ||||
|         "na'vi": "Oel {channel}it ke tok." | ||||
|     } | ||||
| } | ||||
							
								
								
									
										22
									
								
								modules/spelling/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								modules/spelling/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| ## Spelling  | ||||
| 
 | ||||
| Fix your spelling. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| Will attempt to correct a users' spelling by using the levenshtein distance | ||||
| algorithm. One corrects the spelling of their previous message by simply posting | ||||
| a message with their correction and an asterisk: | ||||
| 
 | ||||
|     > user: I am a tutrle. | ||||
|     > user: *turtle | ||||
|     user meant: I am a turtle. | ||||
| 
 | ||||
| The regular expression for this module also accepts two asterisks at the | ||||
| beginning of the correction, or at the end; it also accepts several words as the | ||||
| correction and deals with these fairly intelligently. Users may also attempt  | ||||
| to correct another users like so: | ||||
| 
 | ||||
|     > userone: I am a tutrle. | ||||
|     > usertwo: userone: *turtle | ||||
|     > usertwo thinks userone meant: I am a turtle. | ||||
							
								
								
									
										4
									
								
								modules/spelling/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								modules/spelling/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| { | ||||
|     "ignorable": true, | ||||
|     "help": "http://github.com/reality/depressionbot/blob/master/modules/spelling/README.md" | ||||
| } | ||||
| @ -1,8 +1,84 @@ | ||||
| var spelling = function(dbot) { | ||||
|     var last = {}; | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var allGroupings = function(arr) { | ||||
|     if (arr.length == 0) { | ||||
|         return [];  /* short-circuit the empty-array case */ | ||||
|     } | ||||
|     var groupings = []; | ||||
|     for(var n=1;n<=arr.length;n++) { | ||||
|         for(var i=0;i<(arr.length-(n-1));i++) { | ||||
|             groupings.push(arr.slice(i, i+n)); | ||||
|         } | ||||
|     } | ||||
|     return groupings; | ||||
| } | ||||
| var distance = function(s1, s2) { | ||||
|     // Calculate Levenshtein distance between two strings  
 | ||||
|     // 
 | ||||
|     // version: 1109.2015
 | ||||
|     // discuss at: http://phpjs.org/functions/levenshtein    
 | ||||
|     // + original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
 | ||||
|     // + bugfixed by: Onno Marsman
 | ||||
|     // + revised by: Andrea Giammarchi (http://webreflection.blogspot.com)
 | ||||
|     // + reimplemented by: Brett Zamir (http://brett-zamir.me)
 | ||||
|     // + reimplemented by: Alexander M Beedie    
 | ||||
|     if (s1 == s2) { | ||||
|         return 0; | ||||
|     }  | ||||
|     var s1_len = s1.length; | ||||
|     var s2_len = s2.length; | ||||
|     if (s1_len === 0) { | ||||
|         return s2_len;    } | ||||
|     if (s2_len === 0) { | ||||
|         return s1_len; | ||||
|     } | ||||
|      // BEGIN STATIC
 | ||||
|     var split = false; | ||||
|     try { | ||||
|         split = !('0')[0]; | ||||
|     } catch (e) {         | ||||
|         split = true; // Earlier IE may not support access by string index
 | ||||
|     } | ||||
|     // END STATIC
 | ||||
|     if (split) { | ||||
|         s1 = s1.split('');        s2 = s2.split(''); | ||||
|     } | ||||
|   | ||||
|     var v0 = new Array(s1_len + 1); | ||||
|     var v1 = new Array(s1_len + 1);  | ||||
|     var s1_idx = 0, | ||||
|         s2_idx = 0, | ||||
|         cost = 0; | ||||
|     for (s1_idx = 0; s1_idx < s1_len + 1; s1_idx++) {        v0[s1_idx] = s1_idx; | ||||
|     } | ||||
|     var char_s1 = '', | ||||
|         char_s2 = ''; | ||||
|     for (s2_idx = 1; s2_idx <= s2_len; s2_idx++) {        v1[0] = s2_idx; | ||||
|         char_s2 = s2[s2_idx - 1]; | ||||
|   | ||||
|         for (s1_idx = 0; s1_idx < s1_len; s1_idx++) { | ||||
|             char_s1 = s1[s1_idx];            cost = (char_s1 == char_s2) ? 0 : 1; | ||||
|             var m_min = v0[s1_idx + 1] + 1; | ||||
|             var b = v1[s1_idx] + 1; | ||||
|             var c = v0[s1_idx] + cost; | ||||
|             if (b < m_min) {                m_min = b; | ||||
|             } | ||||
|             if (c < m_min) { | ||||
|                 m_min = c; | ||||
|             }            v1[s1_idx + 1] = m_min; | ||||
|         } | ||||
|         var v_tmp = v0; | ||||
|         v0 = v1; | ||||
|         v1 = v_tmp;    } | ||||
|     return v0[s1_len]; | ||||
| }; | ||||
| 
 | ||||
| var spelling = function(dbot) { | ||||
|     this.last = {}; | ||||
|     this.internalAPI = {}; | ||||
|     this.internalAPI.correct = function (event, correction, candidate, output_callback) { | ||||
|         var rawCandidates = allGroupings(this.last[event.channel.name][candidate].split(' ')); | ||||
| 
 | ||||
|     var correct = function (event, correction, candidate, output_callback) { | ||||
|         var rawCandidates = last[event.channel.name][candidate].split(' ').allGroupings(); | ||||
|         var candidates = []; | ||||
|         for(var i=0;i<rawCandidates.length;i++) { | ||||
|             candidates.push(rawCandidates[i].join(' ')); | ||||
| @ -11,20 +87,20 @@ var spelling = function(dbot) { | ||||
|         var winnerDistance = Infinity; | ||||
| 
 | ||||
|         for(var i=0;i<candidates.length;i++) { | ||||
|             var distance = String.prototype.distance(correction.toLowerCase(), candidates[i].toLowerCase()); | ||||
|             if((distance < winnerDistance) && (distance > 0)) { | ||||
|             var d = distance(correction.toLowerCase(), candidates[i].toLowerCase()); | ||||
|             if((d < winnerDistance) && (d > 0)) { | ||||
|                 winner = candidates[i]; | ||||
|                 winnerDistance = distance; | ||||
|                 winnerDistance = d; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(winnerDistance < Math.ceil(winner.length * 1.33)) { | ||||
|             if(winner !== correction) { | ||||
|                 var fix = last[event.channel.name][candidate].replace(winner, correction); | ||||
|                 var fix = this.last[event.channel.name][candidate].replace(winner, correction); | ||||
|                 if (/^.ACTION/.test(fix)) { | ||||
|                     fix = fix.replace(/^.ACTION/, '/me'); | ||||
|                 } | ||||
|                 last[event.channel.name][candidate] = fix; | ||||
|                 this.last[event.channel.name][candidate] = fix; | ||||
|                 var output = { | ||||
|                     'fix': fix, | ||||
|                     'correcter': event.user, | ||||
| @ -33,36 +109,31 @@ var spelling = function(dbot) { | ||||
|                 output_callback(output); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return { | ||||
|         'name': 'spelling', | ||||
|         'ignorable': true, | ||||
|     }.bind(this);  | ||||
| 
 | ||||
|         'listener': function(event) { | ||||
|             var q = event.message.valMatch(/^(?:\*\*?([\d\w\s']*)|([\d\w\s']*)\*\*?)$/, 3); | ||||
|             var otherQ = event.message.valMatch(/^([\d\w\s]*): (?:\*\*?([\d\w\s']*)|([\d\w\s']*)\*\*?)$/, 4); | ||||
|             if(q) { | ||||
|                 correct(event, q[1] || q[2], event.user, function (e) { | ||||
|                     event.reply(dbot.t('spelling_self', e)); | ||||
|                 }); | ||||
|             } else if(otherQ) { | ||||
|                 correct(event, otherQ[2] || otherQ[3], otherQ[1], function (e) { | ||||
|                     event.reply(dbot.t('spelling_other', e)); | ||||
|                 }); | ||||
|     this.listener = function(event) { | ||||
|         var q = event.message.valMatch(/^(?:\*\*?([\d\w\s']*)|([\d\w\s']*)\*\*?)$/, 3); | ||||
|         var otherQ = event.message.valMatch(/^([\d\w\s]*)[:|,] (?:\*\*?([\d\w\s']*)|([\d\w\s']*)\*\*?)$/, 4); | ||||
|         if(q) { | ||||
|             this.internalAPI.correct(event, q[1] || q[2], event.user, function (e) { | ||||
|                 event.reply(dbot.t('spelling_self', e)); | ||||
|             }); | ||||
|         } else if(otherQ) { | ||||
|             this.internalAPI.correct(event, otherQ[2] || otherQ[3], otherQ[1], function (e) { | ||||
|                 event.reply(dbot.t('spelling_other', e)); | ||||
|             }); | ||||
|         } else { | ||||
|              if(_.has(this.last, event.channel.name)) { | ||||
|                this.last[event.channel.name][event.user] = event.message;  | ||||
|             } else { | ||||
|                  if(last.hasOwnProperty(event.channel.name)) { | ||||
|                    last[event.channel.name][event.user] = event.message;  | ||||
|                 } else { | ||||
|                     last[event.channel.name] = { }; | ||||
|                     last[event.channel.name][event.user] = event.message; | ||||
|                 } | ||||
|                 this.last[event.channel.name] = { }; | ||||
|                 this.last[event.channel.name][event.user] = event.message; | ||||
|             } | ||||
|         }, | ||||
|         'on': 'PRIVMSG' | ||||
|     } | ||||
|         } | ||||
|     }.bind(this); | ||||
|     this.on = 'PRIVMSG'; | ||||
| }  | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return spelling(dbot); | ||||
|     return new spelling(dbot); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										1
									
								
								modules/stats
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								modules/stats
									
									
									
									
									
										Submodule
									
								
							| @ -0,0 +1 @@ | ||||
| Subproject commit ea795e4d17aa500923468366e73a10f6fbc94ade | ||||
							
								
								
									
										32
									
								
								modules/timers/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								modules/timers/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| ## Timers | ||||
| 
 | ||||
| Timers for fun and profit. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| This is a utility module which allows other modules to more easily use timers to | ||||
| execute functionality, as well as providing simple cron-type functionality for | ||||
| timers. | ||||
| 
 | ||||
| ### API | ||||
| 
 | ||||
| #### addTimer(interval, callback, [firstDate]) | ||||
| Execute the given callback every time *interval* (in ms) passes. | ||||
| 
 | ||||
| The firstDate parameter is a Date object used to sync a timer to a given point in  | ||||
| time, allowing for cron-type functionality. For example, if you wanted to call a  | ||||
| given function every day at 00:00, you would do the following: | ||||
| 
 | ||||
|     dbot.api.timers.addTimer(myCallback, 86400000, new Date([midnight tonight])); | ||||
| 
 | ||||
| This works like so: | ||||
| 
 | ||||
| 1. Create a one-time timeout to be executed at the given firstDate (00:00, as | ||||
| above). | ||||
| 2. Upon this timeout, your callback is executed. Then addTimer is called again | ||||
| without the firstDate parameter, thus syncing a daily timer to be executed every  | ||||
| day at 00:00. | ||||
| 
 | ||||
| The best place to create timers is in your module's onLoad function. Using this, | ||||
| you may essentially create persistent jobs to be run at regular intervals while | ||||
| your module is loaded.. | ||||
							
								
								
									
										54
									
								
								modules/timers/timers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								modules/timers/timers.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| /** | ||||
|  * Module Name: Timers | ||||
|  * Description: Persistent timers and shit | ||||
|  */ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var timers = function(dbot) { | ||||
|     this.timers = dbot.db.timers; | ||||
|     this.runningTimeouts = []; | ||||
|     this.runningIntervals = []; | ||||
| 
 | ||||
|     this.api = { | ||||
|         'addTimer': function(timeout, callback, firstDate) { | ||||
|             var now = new Date().getTime(); | ||||
|             if(firstDate) { | ||||
|                 console.log('Setting first timer to run at ' + firstDate); | ||||
|                 firstTimeout = firstDate.getTime() - now;  | ||||
|                 this.runningTimeouts.push(setTimeout(function() { | ||||
|                     console.log('Running first timer at ' + new Date().toUTCString());  | ||||
|                     this.runningIntervals.push(this.api.addTimer(timeout, callback)); | ||||
|                     try { | ||||
|                         callback(); | ||||
|                     } catch(err) { | ||||
|                         console.log('Callback failed: ' + err); | ||||
|                     } | ||||
|                 }.bind(this), firstTimeout)); | ||||
|             } else { | ||||
|                 this.runningIntervals.push(setInterval(function() { | ||||
|                     console.log('Running subsequent timer at ' + new Date().toUTCString());  | ||||
|                     try { | ||||
|                         callback(); | ||||
|                     } catch(err) { | ||||
|                         console.log('Callback failed: ' + err); | ||||
|                     } | ||||
|                 }.bind(this), timeout)); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     this.onDestroy = function() {  | ||||
|         for(var i=0;i<this.runningTimeouts.length;i++) { | ||||
|             console.log('destroying ' +this.runningTimeouts[i]); | ||||
|             clearTimeout(this.runningTimeouts[i]);  | ||||
|         } | ||||
|         for(i=0;i<this.runningIntervals.length;i++) { | ||||
|             console.log('destroying ' +this.runningIntervals[i]); | ||||
|             clearInterval(this.runningIntervals[i]);  | ||||
|         } | ||||
|     }.bind(this); | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return new timers(dbot); | ||||
| }; | ||||
							
								
								
									
										56
									
								
								modules/users/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								modules/users/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| ## Users | ||||
| 
 | ||||
| Track users. | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| This module tracks users and their aliases through nick changes and all that | ||||
| kind of thing. It's mainly a utility module for other modules to use. It's  | ||||
| also totally !insaned. | ||||
| 
 | ||||
| ### Commands | ||||
| 
 | ||||
| #### ~alias [user] | ||||
| If an alias is provided, this command will return the primary user for which | ||||
| this is an alias for. If a primary user is provided, it will return a | ||||
| confirmation of this fact and a count of how many aliases belong to the user. | ||||
| 
 | ||||
| #### ~setaliasparent [newparent] | ||||
| Set a nick which is currently serving as an alias to the primary user, while | ||||
| setting what was previously the primary user as an alias of the new primary | ||||
| user. Requires moderator level access by default. | ||||
| 
 | ||||
| #### ~mergeusers [primaryuser] [secondaryuser] | ||||
| This command merges two nicks which are recorded as primary users into one user. | ||||
| The secondary user and all of their aliases will be merged under primaryuser. | ||||
| Requires moderator level access by default. | ||||
| 
 | ||||
| ### API | ||||
| 
 | ||||
| #### resolveUser(server, nick, [useLowerCase]) | ||||
| This resolves a given nick to its primary user and returns it.  | ||||
| 
 | ||||
| Note that if the useLowerCase argument is set to true, it will do a lower-case  | ||||
| search, however it will return the username in its properly capitalised form, so | ||||
| remember to lower case the return value if you are using lower case values as | ||||
| keys. | ||||
| 
 | ||||
| #### resolveUser(server, user) | ||||
| Return whether a user is known either as an alias or a primary user. | ||||
| 
 | ||||
| #### isPrimaryUser(server, nick) | ||||
| Return whether a nick is known as a primary user. | ||||
| 
 | ||||
| #### getAliases(server, user) | ||||
| Return a list of aliases for a given primary user. | ||||
| 
 | ||||
| #### isOnline(server, user, channel, useLowerCase) | ||||
| Return whether a user is online in a given channel on the given server. | ||||
| 
 | ||||
| ### Events | ||||
| 
 | ||||
| #### nick_changed(server, newNick) | ||||
| This is executed when a new alias is added for a user. | ||||
| 
 | ||||
| #### new_user(server, nick) | ||||
| This is executed when a new primary user is added to the known users DB. | ||||
							
								
								
									
										108
									
								
								modules/users/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								modules/users/api.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var api = function(dbot) { | ||||
|     var escapeRegexen = function(str) { | ||||
|         return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); | ||||
|     }; | ||||
| 
 | ||||
|     var api = { | ||||
|         'resolveUser': function(server, nick, useLowerCase) { | ||||
|             var knownUsers = this.getServerUsers(server);  | ||||
|             var user = nick; | ||||
| 
 | ||||
|             if(!useLowerCase) { | ||||
|                 if(!_.include(knownUsers.users, nick) && _.has(knownUsers.aliases, nick)) { | ||||
|                     user = knownUsers.aliases[nick]; | ||||
|                 } | ||||
|             } else { | ||||
|                 // this is retarded
 | ||||
|                 user = user.toLowerCase(); | ||||
|                 var toMatch = new RegExp("^" + escapeRegexen(user) + "$", "i"); | ||||
| 
 | ||||
|                 var resolvedUser = _.find(knownUsers.users, function(nick) { | ||||
|                     return nick.match(toMatch) !== null;  | ||||
|                 }, this); | ||||
| 
 | ||||
|                 if(!resolvedUser) { | ||||
|                     resolvedUser = _.find(knownUsers.aliases, function(nick, alias) { | ||||
|                         if(alias.match(toMatch) !== null) return nick; | ||||
|                     }, this); | ||||
|                     if(!_.isUndefined(resolvedUser)) user = resolvedUser; | ||||
|                 } | ||||
|                 else{ | ||||
|                     user = resolvedUser; | ||||
|                 } | ||||
|             } | ||||
|             return user; | ||||
|         }, | ||||
| 
 | ||||
|         'getRandomChannelUser': function(server, channel) { | ||||
|             var channelUsers = this.getServerUsers(server).channelUsers[channel]; | ||||
|             if(!_.isUndefined(channelUsers)) { | ||||
|                 return channelUsers[_.random(0, channelUsers.length - 1)]; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         'getServerUsers': function(server) { | ||||
|             return dbot.db.knownUsers[server].users; | ||||
|         }, | ||||
| 
 | ||||
|         'getAllUsers': function() { | ||||
|             return _.reduce(dbot.db.knownUsers, function(memo, server, name) { | ||||
|                 memo[name] = server.users; | ||||
|                 return memo; | ||||
|             }, {}, this); | ||||
|         }, | ||||
| 
 | ||||
|         'isKnownUser': function(server, nick) { | ||||
|             var knownUsers = this.getServerUsers(server);  | ||||
|             return (_.include(knownUsers.users, nick) || _.has(knownUsers.aliases, nick)); | ||||
|         }, | ||||
| 
 | ||||
|         'isPrimaryUser': function(server, nick) { | ||||
|             var knownUsers = this.getServerUsers(server);  | ||||
|             return _.include(knownUsers.users, nick); | ||||
|         }, | ||||
| 
 | ||||
|         'getAliases': function(server, nick) { | ||||
|             var knownUsers = this.getServerUsers(server); | ||||
|             return _.chain(knownUsers.aliases) | ||||
|                 .keys() | ||||
|                 .filter(function(user) { | ||||
|                     return knownUsers.aliases[user] == nick; | ||||
|                 }, this) | ||||
|                 .value(); | ||||
|         }, | ||||
| 
 | ||||
|         'isOnline': function(server, user, channel, useLowerCase) { | ||||
|             var user = this.api.resolveUser(server, user, useLowerCase); | ||||
|             var possiNicks = [user].concat(this.api.getAliases(server, user)); | ||||
| 
 | ||||
|             if(!_.has(dbot.instance.connections[server].channels, channel)) return false; | ||||
|             var onlineNicks = dbot.instance.connections[server].channels[channel].nicks; | ||||
| 
 | ||||
|             return _.any(onlineNicks, function(nick) { | ||||
|                 nick = nick.name; | ||||
|                 return _.include(possiNicks, nick);  | ||||
|             }, this); | ||||
|         }, | ||||
| 
 | ||||
|         'isChannelUser': function(server, user, channel, useLowerCase) { | ||||
|             var knownUsers = this.getServerUsers(server); | ||||
|             var user = this.api.resolveUser(server, user, useLowerCase);  | ||||
| 
 | ||||
|             if(!_.has(knownUsers.channelUsers, channel)) { | ||||
|                 return false; | ||||
|             }  | ||||
|             return _.include(knownUsers.channelUsers[channel], user); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return api; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return api(dbot); | ||||
| }; | ||||
							
								
								
									
										112
									
								
								modules/users/commands.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								modules/users/commands.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var commands = function(dbot) { | ||||
|     var commands = { | ||||
|         '~alias': function(event) { | ||||
|             var knownUsers = this.getServerUsers(event.server), | ||||
|                 alias = event.params[1].trim(); | ||||
| 
 | ||||
|             if(_.include(knownUsers.users, alias)) { | ||||
|                 var aliases = this.api.getAliases(event.server, alias); | ||||
|                 var aliasCount = aliases.length; | ||||
| 
 | ||||
|                 if(aliasCount != 0) { | ||||
|                     var aliases = _.first(aliases, 10); | ||||
|                     var including = 'including: '; | ||||
|                     for(var i=0;i<aliases.length;i++) { | ||||
|                         including += aliases[i] + ', '; | ||||
|                     } | ||||
|                     including = including.slice(0, -2) + '.'; | ||||
| 
 | ||||
|                     event.reply(dbot.t('primary', {  | ||||
|                         'user': alias,  | ||||
|                         'count': aliasCount  | ||||
|                     }) + including);  | ||||
|                 } else { | ||||
|                     event.reply(dbot.t('primary', {  | ||||
|                         'user': alias,  | ||||
|                         'count': aliasCount  | ||||
|                     }));  | ||||
|                 } | ||||
|             } else if(_.has(knownUsers.aliases, alias)) { | ||||
|                 event.reply(dbot.t('alias', {  | ||||
|                     'alias': alias,  | ||||
|                     'user': knownUsers.aliases[alias]  | ||||
|                 })); | ||||
|             } else { | ||||
|                 event.reply(dbot.t('unknown_alias', { 'alias': alias })); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '~setaliasparent': function(event) { | ||||
|             var knownUsers = this.getServerUsers(event.server); | ||||
|             var newParent = event.params[1]; | ||||
| 
 | ||||
|             if(_.has(knownUsers.aliases, newParent)) { | ||||
|                 var newAlias = knownUsers.aliases[newParent];  | ||||
| 
 | ||||
|                 // Replace user entry
 | ||||
|                 knownUsers.users = _.without(knownUsers.users, newAlias); | ||||
|                 knownUsers.users.push(newParent); | ||||
| 
 | ||||
|                 // Replace channels entries with new primary user
 | ||||
|                 this.updateChannels(event, newAlias, newParent); | ||||
| 
 | ||||
|                 // Remove alias for new parent & add alias for new alias
 | ||||
|                 delete knownUsers.aliases[newParent]; | ||||
|                 knownUsers.aliases[newAlias] = newParent; | ||||
| 
 | ||||
|                 // Update aliases to point to new primary user
 | ||||
|                 this.updateAliases(event, newAlias, newParent); | ||||
| 
 | ||||
|                 event.reply(dbot.t('aliasparentset', {  | ||||
|                     'newParent': newParent,  | ||||
|                     'newAlias': newAlias  | ||||
|                 })); | ||||
| 
 | ||||
|                 return { | ||||
|                     'server': event.server, | ||||
|                     'alias': newAlias | ||||
|                 }; | ||||
|             } else { | ||||
|                 event.reply(dbot.t('unknown_alias', { 'alias': newParent })); | ||||
|             } | ||||
|             return false; | ||||
|         }, | ||||
| 
 | ||||
|         '~mergeusers': function(event) { | ||||
|             var knownUsers = this.getServerUsers(event.server); | ||||
|             var primaryUser = event.params[1]; | ||||
|             var secondaryUser = event.params[2]; | ||||
| 
 | ||||
|             if(_.include(knownUsers.users, primaryUser) && _.include(knownUsers.users, secondaryUser)) { | ||||
|                 knownUsers.users = _.without(knownUsers.users, secondaryUser); | ||||
|                 knownUsers.aliases[secondaryUser] = primaryUser; | ||||
|                 this.updateAliases(event, secondaryUser, primaryUser); | ||||
|                 this.updateChannels(event, secondaryUser, primaryUser); | ||||
| 
 | ||||
|                 event.reply(dbot.t('merged_users', {  | ||||
|                     'old_user': secondaryUser, | ||||
|                     'new_user': primaryUser | ||||
|                 })); | ||||
|                  | ||||
|                 return { | ||||
|                     'server': event.server, | ||||
|                     'secondary': secondaryUser | ||||
|                 }; | ||||
|             } else { | ||||
|                 event.reply(dbot.t('unprimary_error')); | ||||
|             } | ||||
|             return false; | ||||
|         }  | ||||
|     }; | ||||
| 
 | ||||
|     commands['~setaliasparent'].access = 'moderator'; | ||||
|     commands['~mergeusers'].access = 'moderator'; | ||||
|      | ||||
|     return commands; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return commands(dbot); | ||||
| }; | ||||
							
								
								
									
										6
									
								
								modules/users/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								modules/users/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| { | ||||
|     "ignorable": false, | ||||
|     "dependencies": [ "command", "event" ], | ||||
|     "dbKeys": [ "knownUsers" ], | ||||
|     "help": "https://github.com/reality/depressionbot/blob/master/modules/users/README.md" | ||||
| } | ||||
							
								
								
									
										102
									
								
								modules/users/pages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								modules/users/pages.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| var pages = function(dbot) { | ||||
|     var _ = require('underscore')._; | ||||
|     var connections = dbot.instance.connections; | ||||
| 
 | ||||
|     return { | ||||
|         '/connections': function(req, res) { | ||||
|             var connections = Object.keys(dbot.instance.connections); | ||||
|             res.render('connections', { 'name': dbot.config.name, 'connections': connections }); | ||||
|         }, | ||||
| 
 | ||||
|         '/channels/:connection': function(req, res) { | ||||
|             var connection = req.params.connection; | ||||
|             if(dbot.instance.connections.hasOwnProperty(connection)) { | ||||
|                 var channels = Object.keys(dbot.instance.connections[connection].channels); | ||||
|                 res.render('channels', { 'name': dbot.config.name, 'connection': connection, 'channels': channels}); | ||||
|             } else { | ||||
|                 res.render_core('error', { 'name': dbot.config.name, 'message': 'No such connection.' }); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         '/users/:connection/:channel': function(req, res) { | ||||
|             var connection = req.params.connection; | ||||
|             var channel = _.unescape(req.params.channel); | ||||
|             var connections = dbot.instance.connections; | ||||
| 
 | ||||
|             if(connections.hasOwnProperty(connection) &&  | ||||
|                 connections[connection].channels.hasOwnProperty(channel)) { | ||||
| 
 | ||||
|                 //TODO(samstudio8): Stats API Functionality
 | ||||
|                 var chanData = dbot.api.stats.getChanStats(connection, channel, ["week"]); | ||||
|                 var chanFreq = []; | ||||
|                 var chanFreqLabel = []; | ||||
| 
 | ||||
|                 if(chanData){ | ||||
|                     var cur_ptr; | ||||
|                     for(var i=0; i <= 6; i++){ | ||||
|                         cur_ptr = ((i+1)+chanData.fields.week.raw.ptr) % 7; | ||||
|                         for(var j=0; j <= 23; j++){ | ||||
|                             chanFreq.push(chanData.fields.week.raw[cur_ptr][j]); | ||||
|                         } | ||||
|                         chanFreqLabel.push("'"+chanData.fields.week.raw[cur_ptr].name+"'"); | ||||
|                     } | ||||
|                 } | ||||
|                 else{ | ||||
|                     for (var i = 0; i < 168; i++) chanFreq[i] = 0; | ||||
|                     chanFreqLabel = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; | ||||
|                 } | ||||
| 
 | ||||
|                 var userData = { "active": [], "inactive": [], "offline": []}; | ||||
|                 var reply = dbot.api.stats.getChanUsersStats(connection, channel, [ | ||||
|                         "lines", "words", "lincent", "wpl", "in_mentions" | ||||
|                 ]); | ||||
|                 _.each(reply.users, function(user, userName){ | ||||
|                     if(userName != dbot.config.name){ | ||||
|                         if(user.online){ | ||||
|                             if(user.active){ | ||||
|                                 userData.active.push(user); | ||||
|                             } | ||||
|                             else{ | ||||
|                                 userData.inactive.push(user); | ||||
|                             } | ||||
|                         } | ||||
|                         else{ | ||||
|                             userData.offline.push(user); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
|                 var userSort = function(a, b){ | ||||
|                     var x = a.display.toLowerCase(); | ||||
|                     var y = b.display.toLowerCase(); | ||||
|                     if(x > y) return 1; | ||||
|                     if(x < y) return -1; | ||||
|                     return 0; | ||||
|                 } | ||||
|                 userData.active.sort(userSort); | ||||
|                 userData.inactive.sort(userSort); | ||||
|                 userData.offline.sort(userSort); | ||||
| 
 | ||||
|                 var userDataSorted = (userData.active.concat(userData.inactive)).concat(userData.offline); | ||||
| 
 | ||||
|                 res.render('users', {  | ||||
|                     'name': dbot.config.name, | ||||
|                     'connection': connection, | ||||
|                     'channel': channel, | ||||
|                     'userStats': userDataSorted, | ||||
|                     'chanFreq': chanFreq, | ||||
|                     'chanFreqLen': chanFreq.length, | ||||
|                     "locals": { | ||||
|                       'chanFreqLabel': chanFreqLabel,   | ||||
|                     }}); | ||||
| 
 | ||||
|             } else { | ||||
|                 res.render_core('error', { 'name': dbot.config.name, 'message': 'No such connection or channel.' }); | ||||
|             } | ||||
|         }, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return pages(dbot); | ||||
| }; | ||||
							
								
								
									
										26
									
								
								modules/users/strings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								modules/users/strings.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| { | ||||
|     "alias": { | ||||
|         "english": "{alias} is an alias of {user}", | ||||
|         "na'vi": "ayfko syaw {user} {alias} nìteng" | ||||
|     },  | ||||
|     "primary": { | ||||
|         "english": "{user} is a primary user with {count} aliases, ", | ||||
|         "na'vi": "{user} lu txin ulte {count}a stxo lu poru, " | ||||
|     }, | ||||
|     "unknown_alias": { | ||||
|         "english": "{alias} does not currently exist as an alias or known user.", | ||||
|         "na'vi": "{alias} ke fkeytok nìfkrr" | ||||
|     },  | ||||
|     "aliasparentset": { | ||||
|         "english": "{newParent} is now the parent user, and {newAlias} is an alias.", | ||||
|         "na'vi": "{newParent} lu sa'sem set ulte {newAlias} lu stxo set nìteng." | ||||
|     }, | ||||
|     "unprimary_error": { | ||||
|         "english": "One of those users isn't currently recorded as a primary user.", | ||||
|         "na'vi": "fo sute txin ke lu." | ||||
|     }, | ||||
|     "merged_users": { | ||||
|         "english": "{old_user} and their aliases have been merged into {new_user}.", | ||||
|         "na'vi": "{old_user} ulte stxo alahe {new_user} lu set." | ||||
|     } | ||||
| } | ||||
							
								
								
									
										99
									
								
								modules/users/users.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								modules/users/users.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| /** | ||||
|  * Name: Users | ||||
|  * Description: Track known users | ||||
|  */ | ||||
| var _ = require('underscore')._; | ||||
| 
 | ||||
| var users = function(dbot) { | ||||
|     this.knownUsers = dbot.db.knownUsers; | ||||
|     this.getServerUsers = function(server) { | ||||
|         var knownUsers = this.knownUsers; | ||||
|         if(!_.has(knownUsers, server)) { | ||||
|             knownUsers[server] = { 'users': [], 'aliases': {}, 'channelUsers': {} }; | ||||
|         } | ||||
|         if(!_.has(knownUsers[server], 'channelUsers')) { | ||||
|             knownUsers[server].channelUsers = {}; | ||||
|         } | ||||
|         return knownUsers[server];     | ||||
|     }; | ||||
| 
 | ||||
|     this.updateAliases = function(event, oldUser, newUser) { | ||||
|         var knownUsers = this.getServerUsers(event.server); | ||||
|         _.each(knownUsers.aliases, function(user, alias) { | ||||
|             if(user == oldUser) { | ||||
|                 knownUsers.aliases[alias] = newUser; | ||||
|             } | ||||
|         }, this); | ||||
|     }; | ||||
| 
 | ||||
|     this.updateChannels = function(event, oldUser, newUser) { | ||||
|         var channelUsers = this.getServerUsers(event.server).channelUsers; | ||||
|         channelUsers = _.each(channelUsers, function(channel, channelName) { | ||||
|             channelUsers[channelName] = _.without(channel, oldUser); | ||||
|             channelUsers[channelName].push(newUser); | ||||
|         }, this); | ||||
|     }; | ||||
|          | ||||
|     this.listener = function(event) { | ||||
|         var knownUsers = this.getServerUsers(event.server);  | ||||
|         var nick = event.user; | ||||
| 
 | ||||
|         if(event.action == 'JOIN') { | ||||
|             if(!_.has(knownUsers.channelUsers, event.channel.name)) { | ||||
|                 knownUsers.channelUsers[event.channel.name] = []; | ||||
|             } | ||||
|             var channelUsers = knownUsers.channelUsers[event.channel.name]; | ||||
| 
 | ||||
|             if(this.api.isKnownUser(event.server, nick)) { | ||||
|                 nick = this.api.resolveUser(event.server, nick); | ||||
|             } else { | ||||
|                 knownUsers.users.push(nick); | ||||
|                 dbot.api.event.emit('new_user', [ event.server, nick ]); | ||||
|             } | ||||
| 
 | ||||
|             if(!_.include(channelUsers, nick)) { | ||||
|                 channelUsers.push(nick); | ||||
|             } | ||||
|         } else if(event.action == 'NICK') { | ||||
|             var newNick = event.params.substr(1); | ||||
|             if(!this.api.isKnownUser(newNick)) { | ||||
|                 knownUsers.aliases[newNick] = this.api.resolveUser(event.server, event.user); | ||||
|                 dbot.api.event.emit('nick_change', [ event.server, newNick ]); | ||||
|             } | ||||
|         } | ||||
|     }.bind(this); | ||||
|     this.on =  ['JOIN', 'NICK']; | ||||
|      | ||||
|     this.onLoad = function() { | ||||
|         // Trigger updateNickLists to stat current users in channel
 | ||||
|         dbot.instance.addListener('366', 'users', function(event) { | ||||
|             var knownUsers = this.getServerUsers(event.server); | ||||
|             if(!_.has(knownUsers.channelUsers, event.channel.name)) { | ||||
|                 knownUsers.channelUsers[event.channel.name] = []; | ||||
|             } | ||||
|             var channelUsers = knownUsers.channelUsers[event.channel.name]; | ||||
| 
 | ||||
|             _.each(event.channel.nicks, function(nick) { | ||||
|                 nick = nick.name; | ||||
|                 if(this.api.isKnownUser(event.server, nick)) { | ||||
|                     nick = this.api.resolveUser(event.server, nick); | ||||
|                 } else { | ||||
|                     knownUsers.users.push(nick); | ||||
|                     dbot.api.emit('new_user', [ event.server, nick ]); | ||||
|                 } | ||||
|                 if(!_.include(channelUsers, nick)) { | ||||
|                     channelUsers.push(nick); | ||||
|                 } | ||||
|             }, this); | ||||
|         }.bind(this)); | ||||
| 
 | ||||
|         var connections = dbot.instance.connections; | ||||
|         _.each(connections, function(connection) { | ||||
|             connection.updateNickLists();  | ||||
|         }); | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return new users(dbot); | ||||
| }; | ||||
							
								
								
									
										42
									
								
								modules/web/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								modules/web/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| # Web | ||||
| 
 | ||||
| Web interface | ||||
| 
 | ||||
| ## Description | ||||
| 
 | ||||
| It's a web interface for DBot. What of it? | ||||
| 
 | ||||
| ## Requirements | ||||
| ###Express and Jade@0.25 | ||||
| ``` | ||||
| npm install express | ||||
| npm install jade@0.25 | ||||
| ``` | ||||
| ###Express Patch | ||||
| Express currently needs to be patched, edit ~/node_modules/express/lib/express.js as thus; | ||||
| ``` | ||||
|   52 for (var key in connect.middleware) {                                            | ||||
| **53   if( !Object.prototype.hasOwnProperty(key) ) {                                  | ||||
|   54     Object.defineProperty(                                                       | ||||
|   55         exports                                                                  | ||||
|   56       , key                                                                      | ||||
|   57       , Object.getOwnPropertyDescriptor(connect.middleware, key));               | ||||
| **58   }                                                                              | ||||
|   59 }  | ||||
| ``` | ||||
| ###Twitter Bootstrap | ||||
| ``` | ||||
| cd depressionbot/public/ | ||||
| wget http://twitter.github.com/bootstrap/assets/bootstrap.zip | ||||
| unzip bootstrap.zip | ||||
| rm bootstrap.zip | ||||
| ``` | ||||
| ###d3.js | ||||
| ``` | ||||
| cd depressionbot/public/ | ||||
| mkdir d3 | ||||
| cd d3 | ||||
| wget http://d3js.org/d3.v3.zip | ||||
| unzip d3.v3.zip | ||||
| rm d3.v3.zip | ||||
| ``` | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|     "ignorable": false, | ||||
|     "webHost": "localhost", | ||||
|     "webPort": 8080 | ||||
|     "webPort": 9001 | ||||
| } | ||||
|  | ||||
| @ -1,130 +1,45 @@ | ||||
| var express = require('express'); | ||||
| var express = require('express'), | ||||
|     _ = require('underscore')._, | ||||
|     fs = require('fs'); | ||||
| 
 | ||||
| var webInterface = function(dbot) { | ||||
|     var pub = 'public'; | ||||
|     var app = express.createServer(); | ||||
|     var app = express(); | ||||
| 
 | ||||
|     app.use(express.compiler({ src: pub, enable: ['sass'] })); | ||||
|     app.use(express.static(pub)); | ||||
|     app.set('view engine', 'jade'); | ||||
| 
 | ||||
|     app.get('/', function(req, res) { | ||||
|         res.render('index', { 'name': dbot.config.name }); | ||||
|     }); | ||||
| 
 | ||||
|     app.get('/connections', function(req, res) { | ||||
|         var connections = Object.keys(dbot.instance.connections); | ||||
|         res.render('connections', { 'name': dbot.config.name, 'connections': connections }); | ||||
|     }); | ||||
| 
 | ||||
|     app.get('/channels/:connection', function(req, res) { | ||||
|         var connection = req.params.connection; | ||||
|         if(dbot.instance.connections.hasOwnProperty(connection)) { | ||||
|             var channels = Object.keys(dbot.instance.connections[connection].channels); | ||||
|             res.render('channels', { 'name': dbot.config.name, 'connection': connection, 'channels': channels}); | ||||
|         } else { | ||||
|             res.render('error', { 'name': dbot.config.name, 'message': 'No such connection.' }); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     app.get('/users/:connection/:channel', function(req, res) { | ||||
|         var connection = req.params.connection; | ||||
|         var channel = '#' + req.params.channel; | ||||
|         var connections = dbot.instance.connections; | ||||
| 
 | ||||
|         if(connections.hasOwnProperty(connection) &&  | ||||
|             connections[connection].channels.hasOwnProperty(channel)) { | ||||
|             var nicks = Object.keys(connections[connection].channels[channel].nicks); | ||||
|             res.render('users', { 'name': dbot.config.name, 'connection': connection, | ||||
|                 'channel': channel, 'nicks': nicks }); | ||||
|         } else { | ||||
|             res.render('error', { 'name': dbot.config.name, 'message': 'No such connection or channel.' }); | ||||
|         } | ||||
|     }); | ||||
|   | ||||
|     app.get('/user/:connection/:channel/:user', function(req, res) { | ||||
|         var connection = req.params.connection; | ||||
|         var channel = '#' + req.params.channel; | ||||
|         var user = dbot.cleanNick(req.params.user); | ||||
| 
 | ||||
|         var quoteCount = 'no'; | ||||
|         if(dbot.db.quoteArrs.hasOwnProperty(user)) { | ||||
|             var quoteCount = dbot.db.quoteArrs[user].length; | ||||
|         } | ||||
| 
 | ||||
|         if(!dbot.db.kicks.hasOwnProperty(req.params.user)) { | ||||
|             var kicks = '0'; | ||||
|         } else { | ||||
|             var kicks = dbot.db.kicks[req.params.user]; | ||||
|         } | ||||
| 
 | ||||
|         if(!dbot.db.kickers.hasOwnProperty(req.params.user)) { | ||||
|             var kicked = '0'; | ||||
|         } else { | ||||
|             var kicked = dbot.db.kickers[req.params.user]; | ||||
|         } | ||||
| 
 | ||||
|         res.render('user', { 'name': dbot.config.name, 'user': req.params.user, | ||||
|         'channel': channel, 'connection': connection, 'cleanUser': user,  | ||||
|         'quotecount': quoteCount, 'kicks': kicks, 'kicked': kicked }); | ||||
|     }); | ||||
|     | ||||
|     // Lists the quote categories
 | ||||
|     app.get('/quotes', function(req, res) { | ||||
|         res.render('quotelist', { 'name': dbot.config.name, 'quotelist': Object.keys(dbot.db.quoteArrs) }); | ||||
|     }); | ||||
|      | ||||
|     // Lists quotes in a category
 | ||||
|     app.get('/quotes/:key', function(req, res) { | ||||
|         var key = req.params.key.toLowerCase(); | ||||
|         if(dbot.db.quoteArrs.hasOwnProperty(key)) { | ||||
|             res.render('quotes', { 'name': dbot.config.name, 'quotes': dbot.db.quoteArrs[key], locals: { 'url_regex': RegExp.prototype.url_regex() } }); | ||||
|         } else { | ||||
|             res.render('error', { 'name': dbot.config.name, 'message': 'No quotes under that key.' }); | ||||
|         } | ||||
|     }); | ||||
|     var server = app.listen(dbot.config.web.webPort); | ||||
| 
 | ||||
|     // Load random quote category page
 | ||||
|     app.get('/rq', function(req, res) { | ||||
|         var rCategory = Object.keys(dbot.db.quoteArrs).random(); | ||||
|         res.render('quotes', { 'name': dbot.config.name, 'quotes': dbot.db.quoteArrs[rCategory], locals: { 'url_regex': RegExp.prototype.url_regex() } }); | ||||
|     }); | ||||
| 
 | ||||
|     // Lists all of the polls
 | ||||
|     app.get('/polls', function(req, res) { | ||||
|         res.render('polllist', { 'name': dbot.config.name, 'polllist': Object.keys(dbot.db.polls) }); | ||||
|     }); | ||||
| 
 | ||||
|     // Shows the results of a poll
 | ||||
|     app.get('/polls/:key', function(req, res) { | ||||
|         var key = req.params.key.toLowerCase(); | ||||
|         if(dbot.db.polls.hasOwnProperty(key) && dbot.db.polls[key].hasOwnProperty('description')) { | ||||
|             // tally the votes
 | ||||
|             var totalVotes = 0; | ||||
|             for( var v in dbot.db.polls[key].votes ) { | ||||
|                 var N = Number(dbot.db.polls[key].votes[v]); | ||||
|                 if( !isNaN(N) ) { | ||||
|                     totalVotes += N; | ||||
|                 } | ||||
|     this.reloadPages = function() { | ||||
|         var pages = dbot.pages; | ||||
|         for(var p in pages) { | ||||
|             if(_.has(pages, p)) { | ||||
|                 var func = pages[p]; | ||||
|                 var mod = func.module; | ||||
|                 app.get(p, (function(req, resp) { | ||||
|                     // Crazy shim to seperate module views.
 | ||||
|                     var shim = Object.create(resp); | ||||
|                     shim.render = (function(view, one, two) { | ||||
|                         // Render with express.js
 | ||||
|                         resp.render(this.module + '/' + view, one, two); | ||||
|                     }).bind(this); | ||||
|                     shim.render_core = resp.render; | ||||
|                     this.call(this.module, req, shim); | ||||
|                 }).bind(func)); | ||||
|             } | ||||
|             res.render('polls', { 'name': dbot.config.name, 'description': dbot.db.polls[key].description, 'votees': Object.keys(dbot.db.polls[key].votees), 'options': dbot.db.polls[key].votes, locals: { 'totalVotes': totalVotes, 'url_regex': RegExp.prototype.url_regex() } }); | ||||
|         } else { | ||||
|             res.render('error', { 'name': dbot.config.name, 'message': 'No polls under that key.' }); | ||||
|         } | ||||
|     }); | ||||
|      | ||||
|     app.listen(dbot.config.web.webPort); | ||||
|     }.bind(this); | ||||
| 
 | ||||
|     return {  | ||||
|         'name': 'web', | ||||
|         'ignorable': false, | ||||
| 
 | ||||
|         'onDestroy': function() { | ||||
|             app.close(); | ||||
|         } | ||||
|     }; | ||||
|     this.onDestroy = function() { | ||||
|         server.close(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return webInterface(dbot); | ||||
|     return new webInterface(dbot); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										10
									
								
								modules/youare/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								modules/youare/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| ## youare | ||||
| 
 | ||||
| You're a loser! | ||||
| 
 | ||||
| ### Description | ||||
| 
 | ||||
| This module occasionally comes back and says "You're a x" when somebody goes "y | ||||
| is x" or "y are x," with the intention of annoying people who are calling | ||||
| something crap. Warning: this module occasionally causes the bot to be nice to | ||||
| people. | ||||
							
								
								
									
										3
									
								
								modules/youare/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								modules/youare/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| { | ||||
|     "ignorable": true | ||||
| } | ||||
| @ -1,19 +1,14 @@ | ||||
| var youAre = function(dbot) { | ||||
|     return { | ||||
|         'name': 'youare', | ||||
|         'ignorable': false, | ||||
|     this.listener = function(event) { | ||||
|         var key = event.message.valMatch(/(\bis\b|\bare\b)\s+([\w\s\d]*?)(\s+)?(,|\.|\band\b|$)/, 5); | ||||
| 
 | ||||
|         'listener': function(event) { | ||||
|             var key = event.message.valMatch(/(\bis\b|\bare\b)\s+([\w\s\d]*?)(\s+)?(,|\.|\band\b|$)/, 5); | ||||
| 
 | ||||
|             if(key && key[2] != "" && Number.prototype.chanceIn(1, 100) && event.user != 'aisbot') { | ||||
|                 event.reply(event.user + ': You\'re ' + key[2] + '.'); | ||||
|             } | ||||
|         }, | ||||
|         'on': 'PRIVMSG' | ||||
|     }; | ||||
|         if(key && key[2] != "" && Number.prototype.chanceIn(1, 100) && event.user != 'aisbot') { | ||||
|             event.reply(event.user + ': You\'re ' + key[2] + '.'); | ||||
|         } | ||||
|     }.bind(this); | ||||
|     this.on = 'PRIVMSG'; | ||||
| }; | ||||
| 
 | ||||
| exports.fetch = function(dbot) { | ||||
|     return youAre(dbot); | ||||
|     return new youAre(dbot); | ||||
| }; | ||||
|  | ||||
| @ -6,19 +6,19 @@ | ||||
|  */ | ||||
| 
 | ||||
| html { | ||||
|     background: url("background.jpg") no-repeat center center fixed; | ||||
|     -webkit-background-size: cover; | ||||
|     -moz-background-size: cover; | ||||
|     -o-background-size: cover; | ||||
|     background-size: cover; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|     padding: 25px; | ||||
|     margin: 0; | ||||
|     font-family: "Lucida Grande"; | ||||
|     font-family: "Source Sans Pro", sans-serif; | ||||
|     color: #444; | ||||
|     text-shadow: 1px 1px 2px #2B2B2B; | ||||
|     background: url("background.jpg") no-repeat center center fixed; | ||||
|     -webkit-background-size: cover; | ||||
|     -moz-background-size: cover; | ||||
|     -o-background-size: cover; | ||||
|     background-size: cover; | ||||
| } | ||||
| 
 | ||||
| h1,h2 { | ||||
| @ -31,7 +31,6 @@ p { | ||||
| } | ||||
| 
 | ||||
| div#page { | ||||
| 	width: 90%; | ||||
| 	margin: 0 auto 0 auto; | ||||
| } | ||||
| 
 | ||||
| @ -55,9 +54,8 @@ div#title a { | ||||
| } | ||||
| 
 | ||||
| div#main { | ||||
| 	position: relative; | ||||
|     padding: 15px 5px; | ||||
|     margin: 0px;  | ||||
|     position: relative; | ||||
|     padding: 10px 5px; | ||||
|     font-size: 21px; | ||||
|     text-align:center; | ||||
|     background: #FFF; | ||||
| @ -172,3 +170,58 @@ li.option-votes { | ||||
| 
 | ||||
| 	box-shadow: inset 0px 0px 3px #444; | ||||
| } | ||||
| 
 | ||||
| /**  | ||||
|  * DataTables  | ||||
|  * Based on http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css | ||||
|  */ | ||||
| .sorting { background: url('http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/images/sort_both.png') no-repeat center right; } | ||||
| .sorting_asc { background: url('http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/images/sort_asc.png') no-repeat center right; } | ||||
| .sorting_desc { background: url('http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/images/sort_desc.png') no-repeat center right; } | ||||
| 
 | ||||
| .sorting_asc_disabled { background: url('http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/images/sort_asc_disabled.png') no-repeat center right; } | ||||
| .sorting_desc_disabled { background: url('http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/images/sort_desc_disabled.png') no-repeat center right; } | ||||
| 
 | ||||
| /** | ||||
|  * Bootstrap Overrides | ||||
|  */ | ||||
| .profile_page-header{ | ||||
|   margin: 5px 0 10px 0; | ||||
| } | ||||
| 
 | ||||
| .profile_row{ | ||||
|   text-align: left; | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * spaceinvader's thumbnails | ||||
|  */ | ||||
| span.nicks { | ||||
|   display: none; | ||||
|   color: #fff; | ||||
|   text-decoration: none; | ||||
|   position: absolute; | ||||
|   left: 0px; | ||||
|   top: 10px; | ||||
|   width: 100%; | ||||
|   word-wrap: break-word; | ||||
| } | ||||
| div.imgwrap { | ||||
|   background-color: #000; | ||||
|   position: relative; | ||||
| } | ||||
| div.imgwrap > img { | ||||
|   -webkit-transition: all 0.2s ease-in-out; | ||||
|   -moz-transition: all 0.2s ease-in-out; | ||||
|   -o-transition: all 0.2s ease-in-out; | ||||
|   transition: all 0.2s ease-in-out; | ||||
| } | ||||
| 
 | ||||
| .thumbnail:hover > div.imgwrap > img { | ||||
|   opacity: 0.5; | ||||
| } | ||||
| 
 | ||||
| .thumbnail:hover > div.imgwrap > span.nicks { | ||||
|   display: inline; | ||||
| } | ||||
|  | ||||
							
								
								
									
										279
									
								
								run.js
									
									
									
									
									
								
							
							
						
						
									
										279
									
								
								run.js
									
									
									
									
									
								
							| @ -1,47 +1,54 @@ | ||||
| var fs = require('fs'); | ||||
| var timers = require('./timer'); | ||||
| var jsbot = require('./jsbot/jsbot'); | ||||
| var fs = require('fs'), | ||||
|     _ = require('underscore')._, | ||||
|     jsbot = require('./jsbot/jsbot'); | ||||
| require('./snippets'); | ||||
| 
 | ||||
| var DBot = function(timers) { | ||||
|     // Load external files
 | ||||
|     var requiredConfigKeys = [ 'name', 'servers', 'admins', 'moduleNames', 'language' ]; | ||||
|     try { | ||||
|         this.config = JSON.parse(fs.readFileSync('config.json', 'utf-8')); | ||||
|     } catch(err) { | ||||
|         console.log('Config file is screwed up. Attempting to load defaults.'); | ||||
| var DBot = function() { | ||||
|      | ||||
|     /*** Load the DB ***/ | ||||
| 
 | ||||
|     if(fs.existsSync('db.json')) { | ||||
|         try { | ||||
|             this.config = JSON.parse(fs.readFileSync('config.json.sample', 'utf-8')); | ||||
|             this.db = JSON.parse(fs.readFileSync('db.json', 'utf-8')); | ||||
|         } catch(err) { | ||||
|             console.log('Error loading sample config. Bugger off. Stopping.'); | ||||
|             console.log('Error loading db.json. Stopping: ' + err); | ||||
|             process.exit(); | ||||
|         } | ||||
|     } | ||||
|     requiredConfigKeys.each(function(key) { | ||||
|         if(!this.config.hasOwnProperty(key)) { | ||||
|             console.log('Error: Please set a value for ' + key + ' in ' + | ||||
|                 'config.json. Stopping.'); | ||||
|             process.exit(); | ||||
|         } | ||||
|     }.bind(this)); | ||||
| 
 | ||||
|     var rawDB; | ||||
|     try { | ||||
|         var rawDB = fs.readFileSync('db.json', 'utf-8'); | ||||
|     } catch(err) { | ||||
|         this.db = {};  // If no db file, make empty one
 | ||||
|     } else { | ||||
|         this.db = {}; | ||||
|     } | ||||
| 
 | ||||
|     if(!_.has(this.db, 'config')) { | ||||
|         this.db.config = {}; | ||||
|     } | ||||
| 
 | ||||
|     /*** Load the Config ***/ | ||||
| 
 | ||||
|     if(!fs.existsSync('config.json')) { | ||||
|         console.log('Error: config.json file does not exist. Stopping'); | ||||
|         process.exit(); | ||||
|     } | ||||
|      | ||||
|     this.config = _.clone(this.db.config); | ||||
|     try { | ||||
|         if(!this.db) {  // If it wasn't empty 
 | ||||
|             this.db = JSON.parse(rawDB); | ||||
|         } | ||||
|         _.defaults(this.config, JSON.parse(fs.readFileSync('config.json', 'utf-8'))); | ||||
|     } catch(err) { | ||||
|         console.log('Syntax error in db.json. Stopping: ' + err); | ||||
|         console.log('Config file is invalid. Stopping: ' + err); | ||||
|         process.exit(); | ||||
|     } | ||||
| 
 | ||||
|     // Load Strings file
 | ||||
|     try { | ||||
|         var defaultConfig = JSON.parse(fs.readFileSync('config.json.sample', 'utf-8')); | ||||
|     } catch(err) { | ||||
|         console.log('Error loading sample config. Bugger off this should not even be edited. Stopping.'); | ||||
|         process.exit(); | ||||
|     } | ||||
| 
 | ||||
|     // Load missing config directives from sample file
 | ||||
|     _.defaults(this.config, defaultConfig); | ||||
| 
 | ||||
|     /*** Load main strings ***/ | ||||
| 
 | ||||
|     try { | ||||
|         this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8')); | ||||
|     } catch(err) { | ||||
| @ -51,24 +58,21 @@ var DBot = function(timers) { | ||||
| 
 | ||||
|     // Initialise run-time resources
 | ||||
|     this.usage = {}; | ||||
|     this.status = {}; | ||||
|     this.sessionData = {}; | ||||
|     this.timers = timers.create(); | ||||
| 
 | ||||
|     // Populate bot properties with config data
 | ||||
|     // Create JSBot and connect to each server
 | ||||
|     this.instance = jsbot.createJSBot(this.config.name); | ||||
|     for(var name in this.config.servers) { | ||||
|         if(this.config.servers.hasOwnProperty(name)) { | ||||
|             var server = this.config.servers[name]; | ||||
|             this.instance.addConnection(name, server.server, server.port, | ||||
|                     this.config.admin, function(event) { | ||||
|                 var server = this.config.servers[event.server]; | ||||
|                 for(var i=0;i<server.channels.length;i++) { | ||||
|                     this.instance.join(event, server.channels[i]); | ||||
|                 } | ||||
|             }.bind(this), server.nickserv, server.password); | ||||
|         } | ||||
|     } | ||||
|     _.each(this.config.servers, function(server, name) { | ||||
|          this.instance.addConnection(name, server.server, server.port, | ||||
|                 this.config.admin, function(event) { | ||||
|             var server = this.config.servers[event.server]; | ||||
|             for(var i=0;i<server.channels.length;i++) { | ||||
|                 this.instance.join(event, server.channels[i]); | ||||
|             } | ||||
|         }.bind(this), server.nickserv, server.password);         | ||||
|     }, this); | ||||
| 
 | ||||
|     // Load the modules and connect to the server
 | ||||
|     this.reloadModules(); | ||||
| @ -83,9 +87,9 @@ DBot.prototype.say = function(server, channel, message) { | ||||
| // Format given stored string in config language
 | ||||
| DBot.prototype.t = function(string, formatData) { | ||||
|     var formattedString; | ||||
|     if(this.strings.hasOwnProperty(string)) { | ||||
|     if(_.has(this.strings, string)) { | ||||
|         var lang = this.config.language; | ||||
|         if(!this.strings[string].hasOwnProperty(lang)) { | ||||
|         if(!_.has(this.strings[string], lang)) { | ||||
|             lang = "english";  | ||||
|         } | ||||
| 
 | ||||
| @ -103,7 +107,7 @@ DBot.prototype.t = function(string, formatData) { | ||||
| 
 | ||||
| // Save the database file
 | ||||
| DBot.prototype.save = function() { | ||||
|     fs.writeFile('db.json', JSON.stringify(this.db, null, '    ')); | ||||
|     fs.writeFileSync('db.json', JSON.stringify(this.db, null, '    ')); | ||||
| }; | ||||
| 
 | ||||
| // Hot-reload module files.
 | ||||
| @ -117,11 +121,16 @@ DBot.prototype.reloadModules = function() { | ||||
|     } | ||||
| 
 | ||||
|     this.rawModules = []; | ||||
|     this.modules = []; | ||||
|     this.pages = {}; | ||||
|     this.status = {}; | ||||
|     this.modules = {}; | ||||
|     this.commands = {}; | ||||
|     this.api = {}; | ||||
|     this.commandMap = {}; // Map of which commands belong to which modules
 | ||||
|     this.usage = {}; | ||||
|     this.timers.clearTimers(); | ||||
|      | ||||
|     // Load config changes
 | ||||
|     _.extend(this.config, this.db.config); | ||||
| 
 | ||||
|     try { | ||||
|         this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8')); | ||||
| @ -149,89 +158,151 @@ DBot.prototype.reloadModules = function() { | ||||
|         var cacheKey = require.resolve(moduleDir + name); | ||||
|         delete require.cache[cacheKey]; | ||||
| 
 | ||||
|         try { | ||||
|             var webKey = require.resolve(moduleDir + 'web'); | ||||
|         } catch(err) { | ||||
|         } | ||||
|         if(webKey) { | ||||
|             delete require.cache[webKey]; | ||||
|         } | ||||
| 
 | ||||
|         this.status[name] = true; | ||||
| 
 | ||||
|         try { | ||||
|             // Load the module config data
 | ||||
|             var config = {}; | ||||
|              | ||||
|             if(_.has(this.db.config, name)) { | ||||
|                 config = _.clone(this.db.config[name]);  | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 var config = JSON.parse(fs.readFileSync(moduleDir + 'config.json', 'utf-8')) | ||||
|                 this.config[name] = config; | ||||
|                 for(var i=0;i<config.dbKeys.length;i++) { | ||||
|                     if(!this.db.hasOwnProperty(config.dbKeys[i])) { | ||||
|                         this.db[config.dbKeys[i]] = {}; | ||||
|                     } | ||||
|                 var defaultConfig = fs.readFileSync(moduleDir + 'config.json', 'utf-8'); | ||||
|                 try { | ||||
|                     defaultConfig = JSON.parse(defaultConfig); | ||||
|                 } catch(err) { // syntax error
 | ||||
|                     this.status[name] = 'Error parsing config: ' + err + ' ' + err.stack.split('\n')[2].trim(); | ||||
|                     return; | ||||
|                 } | ||||
|                 config = _.defaults(config, defaultConfig); | ||||
|             } catch(err) { | ||||
|                 // Invalid or no config data
 | ||||
|             } | ||||
| 
 | ||||
|             // Don't shit out if dependencies not met
 | ||||
|             if(_.has(config, 'dependencies')) { | ||||
|                 _.each(config.dependencies, function(dependency) { | ||||
|                     if(!_.include(moduleNames, dependency)) { | ||||
|                         console.log('Warning: Automatically loading ' + dependency); | ||||
|                         moduleNames.push(dependency); | ||||
|                     } | ||||
|                 }, this); | ||||
|             } | ||||
| 
 | ||||
|             // Generate missing DB keys
 | ||||
|             this.config[name] = config; | ||||
|             _.each(config.dbKeys, function(dbKey) { | ||||
|                 if(!_.has(this.db, dbKey)) { | ||||
|                     this.db[dbKey] = {}; | ||||
|                 } | ||||
|             }, this); | ||||
| 
 | ||||
|             // Load the module itself
 | ||||
|             var rawModule = require(moduleDir + name); | ||||
|             var module = rawModule.fetch(this); | ||||
|             module.name = name; | ||||
|             this.rawModules.push(rawModule); | ||||
| 
 | ||||
|             module.config = this.config[name]; | ||||
| 
 | ||||
|             // Load the module data
 | ||||
|             _.each([ 'commands', 'pages', 'api' ], function(property) { | ||||
|                 var propertyObj = {}; | ||||
| 
 | ||||
|                 if(fs.existsSync(moduleDir + property + '.js')) { | ||||
|                     try { | ||||
|                         var propertyKey = require.resolve(moduleDir + property); | ||||
|                         if(propertyKey) delete require.cache[propertyKey]; | ||||
|                         propertyObj = require(moduleDir + property).fetch(this); | ||||
|                     } catch(err) { | ||||
|                         this.status[name] = 'Error loading ' + propertyKey + ': ' + err + ' - ' + err.stack.split('\n')[1].trim(); | ||||
|                         console.log('Module error (' + module.name + ') in ' + property + ': ' + err); | ||||
|                     }  | ||||
|                 } | ||||
| 
 | ||||
|                 if(!_.has(module, property)) module[property] = {}; | ||||
|                 _.extend(module[property], propertyObj); | ||||
|                 _.each(module[property], function(item, itemName) { | ||||
|                     item.module = name;  | ||||
|                     if(_.has(config, property) && _.has(config[property], itemName)) { | ||||
|                         _.extend(item, config[property][itemName]); | ||||
|                     } | ||||
|                     module[property][itemName] = _.bind(item, module); | ||||
|                     _.extend(module[property][itemName], item); | ||||
|                 }, this); | ||||
| 
 | ||||
|                 if(property == 'api') { | ||||
|                     this[property][name] = module[property]; | ||||
|                 } else { | ||||
|                     _.extend(this[property], module[property]); | ||||
|                 } | ||||
|             }, this); | ||||
| 
 | ||||
|             // Load the module listener
 | ||||
|             if(module.listener) { | ||||
|                 this.instance.addListener(module.on, module.name, module.listener); | ||||
|             } | ||||
| 
 | ||||
|             if(module.onLoad) { | ||||
|                 module.onLoad(); | ||||
|             } | ||||
| 
 | ||||
|             // Load module commands
 | ||||
|             if(module.commands) { | ||||
|                 var newCommands = module.commands; | ||||
|                 for(key in newCommands) { | ||||
|                     if(newCommands.hasOwnProperty(key) && Object.prototype.isFunction(newCommands[key])) { | ||||
|                         this.commands[key] = newCommands[key]; | ||||
|                         this.commandMap[key] = name; | ||||
|                     } | ||||
|                 if(!_.isArray(module.on)) { | ||||
|                     module.on = [ module.on ]; | ||||
|                 } | ||||
|                 _.each(module.on, function(on) { | ||||
|                     this.instance.addListener(on, module.name, module.listener); | ||||
|                 }, this); | ||||
|             } | ||||
| 
 | ||||
|             // Load the module usage data
 | ||||
|             try { | ||||
|                 var usage = JSON.parse(fs.readFileSync(moduleDir + 'usage.json', 'utf-8')); | ||||
|                 for(key in usage) { | ||||
|                     if(usage.hasOwnProperty(key)) { | ||||
|                         if(this.usage.hasOwnProperty(key)) { | ||||
|                             console.log('Usage key clash for ' + key + ' in ' + name); | ||||
|                         } else { | ||||
|                             this.usage[key] = usage[key]; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } catch(err) { | ||||
|                 // Invalid or no usage info
 | ||||
|             // Load string data for the module
 | ||||
|             _.each([ 'usage', 'strings' ], function(property) { | ||||
|                 var propertyData = {}; | ||||
|                 try { | ||||
|                     propertyData = JSON.parse(fs.readFileSync(moduleDir + property + '.json', 'utf-8')); | ||||
|                 } catch(err) {}; | ||||
|                 _.extend(this[property], propertyData); | ||||
|             }, this); | ||||
| 
 | ||||
|             // Provide toString for module name
 | ||||
|             module.toString = function() { | ||||
|                 return this.name; | ||||
|             } | ||||
| 
 | ||||
|             // Load the module string data
 | ||||
|             try { | ||||
|                 var strings = JSON.parse(fs.readFileSync(moduleDir + 'strings.json', 'utf-8')); | ||||
|                 for(key in strings) { | ||||
|                     if(strings.hasOwnProperty(key)) { | ||||
|                         if(this.strings.hasOwnProperty(key)) { | ||||
|                             console.log('Strings key clash for ' + key + ' in ' + name); | ||||
|                         } else { | ||||
|                             this.strings[key] = strings[key]; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } catch(err) { | ||||
|                 // Invalid or no string info
 | ||||
|             } | ||||
| 
 | ||||
|             this.modules.push(module); | ||||
|             this.modules[module.name] = module; | ||||
|         } catch(err) { | ||||
|             console.log(this.t('module_load_error', {'moduleName': name})); | ||||
|             console.log('MODULE ERROR: ' + name + ' ' + err); | ||||
|             this.status[name] = err + ' - ' + err.stack.split('\n')[1].trim(); | ||||
|             if(this.config.debugMode) { | ||||
|                 console.log('MODULE ERROR (' + name + '): ' + err.stack ); | ||||
|             } else { | ||||
|                 console.log('MODULE ERROR (' + name + '): ' + err ); | ||||
|             } | ||||
|         } | ||||
|     }.bind(this)); | ||||
| 
 | ||||
|     if(_.has(this.modules, 'web')) this.modules.web.reloadPages(); | ||||
|      | ||||
|     _.each(this.modules, function(module, name) { | ||||
|         if(module.onLoad) { | ||||
|             try { | ||||
|                 module.onLoad(); | ||||
|             } catch(err) { | ||||
|                 this.status[name] = 'Error in onLoad: ' + err + ' ' + err.stack.split('\n')[1].trim(); | ||||
|             } | ||||
|         } | ||||
|     }, this); | ||||
| 
 | ||||
|     this.save(); | ||||
| }; | ||||
| 
 | ||||
| DBot.prototype.cleanNick = function(key) { | ||||
|     key = key.toLowerCase(); | ||||
|     while(key.endsWith("_")) { | ||||
|         if(this.db.quoteArrs.hasOwnProperty(key)) { | ||||
|         if(_.has(this.db.quoteArrs, key)) { | ||||
|             return key; | ||||
|         } | ||||
|         key = key.substring(0, key.length-1); | ||||
| @ -239,4 +310,4 @@ DBot.prototype.cleanNick = function(key) { | ||||
|     return key; | ||||
| } | ||||
| 
 | ||||
| new DBot(timers); | ||||
| new DBot(); | ||||
|  | ||||
							
								
								
									
										98
									
								
								snippets.js
									
									
									
									
									
								
							
							
						
						
									
										98
									
								
								snippets.js
									
									
									
									
									
								
							| @ -35,19 +35,6 @@ Array.prototype.sum = function() { | ||||
|     return sum; | ||||
| }; | ||||
| 
 | ||||
| Array.prototype.allGroupings = function() { | ||||
|     if (this.length == 0) { | ||||
|         return [];  /* short-circuit the empty-array case */ | ||||
|     } | ||||
|     var groupings = []; | ||||
|     for(var n=1;n<=this.length;n++) { | ||||
|         for(var i=0;i<(this.length-(n-1));i++) { | ||||
|             groupings.push(this.slice(i, i+n)); | ||||
|         } | ||||
|     } | ||||
|     return groupings; | ||||
| } | ||||
| 
 | ||||
| Array.prototype.uniq = function() { | ||||
|     var hash = {} | ||||
|     var result = []; | ||||
| @ -79,66 +66,6 @@ String.prototype.startsWith = function(needle) { | ||||
|     return needle === this.slice(0, needle.length); | ||||
| }; | ||||
| 
 | ||||
| String.prototype.distance = function(s1, s2) { | ||||
|     // Calculate Levenshtein distance between two strings  
 | ||||
|     // 
 | ||||
|     // version: 1109.2015
 | ||||
|     // discuss at: http://phpjs.org/functions/levenshtein    // +            original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
 | ||||
|     // +            bugfixed by: Onno Marsman
 | ||||
|     // +             revised by: Andrea Giammarchi (http://webreflection.blogspot.com)
 | ||||
|     // + reimplemented by: Brett Zamir (http://brett-zamir.me)
 | ||||
|     // + reimplemented by: Alexander M Beedie    // *                example 1: levenshtein('Kevin van Zonneveld', 'Kevin van Sommeveld');
 | ||||
|     // *                returns 1: 3
 | ||||
|     if (s1 == s2) { | ||||
|         return 0; | ||||
|     }  | ||||
|     var s1_len = s1.length; | ||||
|     var s2_len = s2.length; | ||||
|     if (s1_len === 0) { | ||||
|         return s2_len;    } | ||||
|     if (s2_len === 0) { | ||||
|         return s1_len; | ||||
|     } | ||||
|      // BEGIN STATIC
 | ||||
|     var split = false; | ||||
|     try { | ||||
|         split = !('0')[0]; | ||||
|     } catch (e) {        split = true; // Earlier IE may not support access by string index
 | ||||
|     } | ||||
|     // END STATIC
 | ||||
|     if (split) { | ||||
|         s1 = s1.split('');        s2 = s2.split(''); | ||||
|     } | ||||
|   | ||||
|     var v0 = new Array(s1_len + 1); | ||||
|     var v1 = new Array(s1_len + 1);  | ||||
|     var s1_idx = 0, | ||||
|         s2_idx = 0, | ||||
|         cost = 0; | ||||
|     for (s1_idx = 0; s1_idx < s1_len + 1; s1_idx++) {        v0[s1_idx] = s1_idx; | ||||
|     } | ||||
|     var char_s1 = '', | ||||
|         char_s2 = ''; | ||||
|     for (s2_idx = 1; s2_idx <= s2_len; s2_idx++) {        v1[0] = s2_idx; | ||||
|         char_s2 = s2[s2_idx - 1]; | ||||
|   | ||||
|         for (s1_idx = 0; s1_idx < s1_len; s1_idx++) { | ||||
|             char_s1 = s1[s1_idx];            cost = (char_s1 == char_s2) ? 0 : 1; | ||||
|             var m_min = v0[s1_idx + 1] + 1; | ||||
|             var b = v1[s1_idx] + 1; | ||||
|             var c = v0[s1_idx] + cost; | ||||
|             if (b < m_min) {                m_min = b; | ||||
|             } | ||||
|             if (c < m_min) { | ||||
|                 m_min = c; | ||||
|             }            v1[s1_idx + 1] = m_min; | ||||
|         } | ||||
|         var v_tmp = v0; | ||||
|         v0 = v1; | ||||
|         v1 = v_tmp;    } | ||||
|     return v0[s1_len]; | ||||
| } | ||||
| 
 | ||||
| String.prototype.format = function() { // format takes either multiple indexed arguments, or a single object, whose keys/values will be used
 | ||||
|     var targetStr = this; | ||||
|     var replacements = [].splice.call(arguments, 0); | ||||
| @ -204,7 +131,15 @@ Object.prototype.filter = function(fun) { | ||||
|         } | ||||
|     } | ||||
|     return filtered; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| Object.prototype.each = function(fun) { | ||||
|     for(var key in this) { | ||||
|         if(this.hasOwnProperty(key)) { | ||||
|             fun(this[key]); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /*** Integer ***/ | ||||
| 
 | ||||
| @ -253,3 +188,18 @@ RegExp.prototype.url_regex = function() { | ||||
|     ); | ||||
|     return reg; | ||||
| } | ||||
| 
 | ||||
| Number.prototype.numberFormat = function(dec_places){ | ||||
|     //TODO Possibly abstract this to some sort of localisation module in future?
 | ||||
|     var dec_point = '.'; | ||||
|     var sep = ','; | ||||
| 
 | ||||
|     var parts = this.toFixed(dec_places).toString().split(dec_point); | ||||
|     parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, sep); | ||||
|     return parts.join(dec_point); | ||||
| } | ||||
| 
 | ||||
| // http://simonwillison.net/2006/Jan/20/escape/#p-6
 | ||||
| String.prototype.escape = function() { | ||||
|     return this.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); | ||||
| } | ||||
|  | ||||
							
								
								
									
										27
									
								
								timer.js
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								timer.js
									
									
									
									
									
								
							| @ -1,27 +0,0 @@ | ||||
| var timers = function() { | ||||
|     var timers = []; | ||||
|     var timeouts = []; | ||||
|      | ||||
|     return { | ||||
|         'addTimer': function(interval, callback) { // Because who puts the callback first. Really.
 | ||||
|             timers.push(setInterval(callback, interval));  | ||||
|         }, | ||||
|          | ||||
|         'addOnceTimer': function(delay, callback) { // Because who seriously puts the callback first here too?
 | ||||
|             timeouts.push(setTimeout(callback, delay)); | ||||
|         }, | ||||
| 
 | ||||
|         'clearTimers': function() { | ||||
|             for(var i;i<timers.length;i++) { | ||||
|                 clearInterval(timers[i]); | ||||
|             } | ||||
|             for(var i;i<timeouts.length;i++) { | ||||
|                 clearTimeout(timeouts[i]); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| exports.create = function() { | ||||
|     return timers(); | ||||
| } | ||||
| @ -1,7 +0,0 @@ | ||||
| h3 Channels on #{connection} | ||||
| div#backlink  | ||||
|     a(href='/connections') « Connection List | ||||
| ul#quotelist | ||||
|   -each channel in channels | ||||
|     a(href='/users/'+connection+'/'+channel.substr(1,channel.length)) | ||||
|       li.quotes #{channel} | ||||
| @ -1,6 +0,0 @@ | ||||
| h3 Current Connections | ||||
| div#backlink  | ||||
|     a(href='/') « Home | ||||
| #modulelinks | ||||
|   -each connection in connections | ||||
|     a.module(href='/channels/'+connection) #{connection} | ||||
| @ -1,4 +1,7 @@ | ||||
| #modulelinks | ||||
| extends layout | ||||
| 
 | ||||
| block content | ||||
|   #modulelinks | ||||
|     a.module(href='/quotes') Quotes   | ||||
|     a.module(href='/polls') Polls | ||||
|     a.module(href='/connections') Users | ||||
|  | ||||
| @ -2,12 +2,18 @@ | ||||
| html(lang='en') | ||||
|   head  | ||||
|     meta(charset='utf-8') | ||||
|     script(type="text/javascript", src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js") | ||||
|     link(rel="stylesheet", type="text/css", href="http://fonts.googleapis.com/css?family=Source+Sans+Pro") | ||||
|     link(rel="stylesheet", type="text/css", href="/bootstrap/css/bootstrap.min.css") | ||||
|     link(rel='stylesheet', type='text/css', href='/styles.css') | ||||
|     title #{name} web interface | ||||
|   body | ||||
|     div#page | ||||
|         div#title  | ||||
|             a(href='/') #{name} web interface | ||||
|         div#main | ||||
|             !{body} | ||||
|     div.container | ||||
|         div#page | ||||
|             div#title  | ||||
|                 a(href='/') #{name} web interface | ||||
|             div.container#main | ||||
|                 block content | ||||
|     script(type="text/javascript", src="/bootstrap/js/bootstrap.min.js") | ||||
|     script(type="text/javascript", src="/d3/d3.v3.min.js") | ||||
|     script(type="text/javascript", src="/script.js") | ||||
|  | ||||
							
								
								
									
										12
									
								
								views/poll/polllist.jade
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								views/poll/polllist.jade
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| extends ../layout | ||||
| 
 | ||||
| block content | ||||
|     div#backlink  | ||||
|         a(href='/') « Home | ||||
|     div#controls | ||||
|         input(type="text", name="search", id="search-text", oninput="search(this.value)") | ||||
|     ul#quotelist | ||||
|         -each poll in polllist | ||||
|             a(href='/polls/'+poll) | ||||
|                 li.quotes #{poll} | ||||
| 
 | ||||
							
								
								
									
										39
									
								
								views/poll/polls.jade
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								views/poll/polls.jade
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| extends ../layout | ||||
| 
 | ||||
| block content | ||||
|     div#backlink  | ||||
|         a(href='/polls/') « Poll list | ||||
|     h2 #{description} | ||||
|     p Voters (#{locals.totalVotes}):  | ||||
|         -each voter in votees | ||||
|              |  #{voter} | ||||
|     ul#votelist | ||||
|         -var hasYouTubeVids=false | ||||
|         -each votes,option in options | ||||
|             -var percentage = votes/locals.totalVotes*100 | ||||
|             -if(options.hasOwnProperty(option)) | ||||
|                 -if(option.match(locals.url_regex)) | ||||
|                     li.option  | ||||
|                         -if(option.match(/(jpg|png|gif|jpeg|tiff)$/)) | ||||
|                             a(href=option) | ||||
|                                 img(src=option) | ||||
|                         -else if(option.match(/youtube.com\/watch/)) | ||||
|                             -hasYouTubeVids = true | ||||
|                             span(class='ytplaceholder') | ||||
|                             =option | ||||
|                         -else | ||||
|                             a(href=option) | ||||
|                                 =option | ||||
|                 -else | ||||
|                     li.option #{option} | ||||
|                 li.option-votes | ||||
|                     .vote-track | ||||
|                         -if(!isNaN(percentage)) | ||||
|                             .vote-percentage(style="width: #{percentage}%") | ||||
|                     case votes | ||||
|                         when 1: #{votes} vote  | ||||
|                         default: #{votes} votes  | ||||
|                     -if(!isNaN(percentage)) | ||||
|                         |(#{percentage.toFixed(2)}%)  | ||||
|         -if(hasYouTubeVids) | ||||
|             script(src='/ytembed.js') | ||||
| @ -1,9 +0,0 @@ | ||||
| div#backlink  | ||||
|     a(href='/') « Home | ||||
| div#controls | ||||
|     input(type="text", name="search", id="search-text", oninput="search(this.value)") | ||||
| ul#quotelist | ||||
|     -each poll in polllist | ||||
|         a(href='/polls/'+poll) | ||||
|             li.quotes #{poll} | ||||
| 
 | ||||
| @ -1,36 +0,0 @@ | ||||
| div#backlink  | ||||
|     a(href='/polls/') « Poll list | ||||
| h2 #{description} | ||||
| p Voters (#{locals.totalVotes}):  | ||||
|     -each voter in votees | ||||
|          |  #{voter} | ||||
| ul#votelist | ||||
|     -var hasYouTubeVids=false | ||||
|     -each votes,option in options | ||||
|         -var percentage = votes/locals.totalVotes*100 | ||||
|         -if(options.hasOwnProperty(option)) | ||||
|             -if(option.match(locals.url_regex)) | ||||
|                 li.option  | ||||
|                     -if(option.match(/(jpg|png|gif|jpeg|tiff)$/)) | ||||
|                         a(href=option) | ||||
|                             img(src=option) | ||||
|                     -else if(option.match(/youtube.com\/watch/)) | ||||
|                         -hasYouTubeVids = true | ||||
|                         span(class='ytplaceholder') | ||||
|                         =option | ||||
|                     -else | ||||
|                         a(href=option) | ||||
|                             =option | ||||
|             -else | ||||
|                 li.option #{option} | ||||
|             li.option-votes | ||||
|                 .vote-track | ||||
|                     -if(!isNaN(percentage)) | ||||
|                         .vote-percentage(style="width: #{percentage}%") | ||||
|                 case votes | ||||
|                     when 1: #{votes} vote  | ||||
|                     default: #{votes} votes  | ||||
|                 -if(!isNaN(percentage)) | ||||
|                     |(#{percentage.toFixed(2)}%)  | ||||
|     -if(hasYouTubeVids) | ||||
|         script(src='/ytembed.js') | ||||
							
								
								
									
										98
									
								
								views/profile/profile.jade
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								views/profile/profile.jade
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | ||||
| extends ../layout | ||||
| 
 | ||||
| block content | ||||
|     script(type="text/javascript", src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js") | ||||
| 
 | ||||
|     script | ||||
|         $(document).ready(function(){ | ||||
|             // Allowing forcing of string stats data to sort as numeric | ||||
|             jQuery.extend( jQuery.fn.dataTableExt.oSort, { | ||||
|                 "forcenum-pre": function ( a ) { | ||||
|                     a = a.replace("\,", ""); | ||||
|                     return parseFloat( a ); | ||||
|                 }, | ||||
|               | ||||
|                 "forcenum-asc": function ( a, b ) { | ||||
|                     return a - b; | ||||
|                 }, | ||||
|               | ||||
|                 "forcenum-desc": function ( a, b ) { | ||||
|                     return b - a; | ||||
|                 } | ||||
|             } ); | ||||
| 
 | ||||
|             $('.tip').tooltip(); | ||||
|             $('.data').dataTable({ | ||||
|               "aoColumnDefs": [ | ||||
|                 { "sType": "forcenum", | ||||
|                   "asSorting": [ "desc", "asc" ], | ||||
|                   "aTargets": [ 1, 2, 3, 4, 5 ] } | ||||
|               ], | ||||
|               "bPaginate": false, | ||||
|               "bFilter": false, | ||||
|               "bLengthChange": false, | ||||
|               "oLanguage": { | ||||
|                 "sInfo":  "", | ||||
|                 "sInfoEmpty":  "", | ||||
|                 "sInfoFiltered": "" | ||||
|               }, | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|     div.page-header.profile_page-header | ||||
|         h1  | ||||
|           #{primary} | ||||
|         small | ||||
|           if(profile.tagline) | ||||
|             "#{profile.tagline}" | ||||
|     div#backlink  | ||||
|         a(href='/profile/'+connection) « Profiles | ||||
|     | ||||
|     div.row.profile_row#profile_data | ||||
|       div.span3 | ||||
|         if profile.avatar | ||||
|           img.profile_avatar.img-polaroid(src="#{profile.avatar}") | ||||
|         else | ||||
|           img.profile_avatar.img-polaroid(src="http://placehold.it/270x180&text=Hello,%20World") | ||||
|       div.span9 | ||||
|         h4 Bio | ||||
|         p #{profile.bio} | ||||
|      | ||||
|     hr | ||||
|     h3 Channel Statistics | ||||
|     div#profile_datatable | ||||
|         table.table.table-hover.data | ||||
|             thead | ||||
|                 tr | ||||
|                   th Channel | ||||
|                   th Lines | ||||
|                   th Words | ||||
|                   th Lincent | ||||
|                   th Verbosity | ||||
|                   th Mentions | ||||
|             tbody | ||||
|               if stats | ||||
|                   for chan, key in stats | ||||
|                     if stats.hasOwnProperty(key) | ||||
|                         tr | ||||
|                           td | ||||
|                               a(href='/users/'+connection+'/'+encodeURIComponent(key)) | ||||
|                                 #{key} | ||||
|                               span  | ||||
|                               if chan.online | ||||
|                                 if chan.active.active | ||||
|                                   span.label.label-success.tip(data-original-title="#{chan.active.ago}", data-placement="right") Active | ||||
|                                 else | ||||
|                                   span.label.label-important.tip(data-original-title="#{chan.active.ago}", data-placement="right") Inactive | ||||
|                               else | ||||
|                                 span.label.tip(data-original-title="#{chan.active.ago}", data-placement="right") Offline | ||||
|                           td | ||||
|                               #{chan.fields.lines.data} | ||||
|                           td | ||||
|                               #{chan.fields.words.data} | ||||
|                           td | ||||
|                               #{chan.fields.lincent.data} | ||||
|                           td | ||||
|                               #{chan.fields.wpl.data} | ||||
|                           td | ||||
|                               #{chan.fields.in_mentions.data} | ||||
							
								
								
									
										26
									
								
								views/profile/profile_grid.jade
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								views/profile/profile_grid.jade
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| extends ../layout | ||||
| 
 | ||||
| block content | ||||
|   script(src='http://masonry.desandro.com/jquery.masonry.min.js') | ||||
|   script | ||||
|     $(function() { | ||||
|       var $container = $('.thumbnails'); | ||||
|       $container.imagesLoaded(function(){ | ||||
|         $container.masonry({ | ||||
|           itemSelector : '.thumbnails > li', | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   div.page-header.profile_page-header | ||||
|     h1  | ||||
|       #{connection} | ||||
|   div#backlink  | ||||
|       a(href='../connections') « Connections | ||||
| 
 | ||||
|   ul.thumbnails | ||||
|     each nick in nicks | ||||
|       li.span2 | ||||
|         a.thumbnail(href='/profile/'+connection+'/'+encodeURIComponent(nick)) | ||||
|           div.imgwrap | ||||
|             img(src="#{profiles[nick].profile.avatar}", alt="#{profiles[nick].profile.primary}'s photo") | ||||
|             span.nicks #{profiles[nick].profile.primary} | ||||
| @ -1,9 +0,0 @@ | ||||
| div#backlink  | ||||
|     a(href='/') « Home | ||||
| div#controls | ||||
|     input(type="text", name="search", id="search-text", oninput="search(this.value)") | ||||
| ul#quotelist | ||||
|     -each quote in quotelist | ||||
|         a(href='/quotes/'+quote) | ||||
|             li.quotes #{quote} | ||||
| 
 | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dafydd Francis
						Dafydd Francis