3
0
mirror of https://github.com/reality/dbot.git synced 2024-11-30 16:09:27 +01:00

Merge branch 'master' of git://github.com/reality/depressionbot into weboverhaul

Conflicts:
	modules/quotes/quotes.js
	modules/users/users.js
	run.js
This commit is contained in:
Daniel Evans 2013-01-04 20:15:12 +00:00
commit f91fc99d35
41 changed files with 698 additions and 253 deletions

18
LICENCE Normal file
View File

@ -0,0 +1,18 @@
Copyright (c) 2012 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.

108
README.md
View File

@ -3,8 +3,8 @@
## Introduction ## Introduction
Depressionbot is an IRC bot which aims to be the fanciest IRC bot around - On 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 the general standard of software fanciness, dbot is statistically rated as being
as bathing in fine, fine grape juice.' '82% the same as bathing in fine, fine grape juice.'
Please note that this documentation is not complete and is a work in progress, 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. given I started it rather a long time after I began development of the project.
@ -18,108 +18,10 @@ Requirements:
handles the IRC protocol. handles the IRC protocol.
- Various modules have their own requirements also. - 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 init
git submodule update 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.

57
modules/admin/README.md Normal file
View 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.

View File

@ -13,7 +13,7 @@ var admin = function(dbot) {
'join': function(event) { 'join': function(event) {
var channel = event.params[1]; var channel = event.params[1];
if(event.allChannels.hasOwnProperty(channel)) { if(event.allChannels.hasOwnProperty(channel)) {
event.reply("I'm already in that channel."); event.reply(dbot.t('already_in_channel', {'channel': channel}));
} else { } else {
dbot.instance.join(event, channel); dbot.instance.join(event, channel);
event.reply(dbot.t('join', {'channel': channel})); event.reply(dbot.t('join', {'channel': channel}));
@ -24,7 +24,7 @@ var admin = function(dbot) {
'part': function(event) { 'part': function(event) {
var channel = event.params[1]; var channel = event.params[1];
if(!event.allChannels.hasOwnProperty(channel)) { if(!event.allChannels.hasOwnProperty(channel)) {
event.reply("I'm not in that channel."); event.reply(dbot.t('not_in_channel', {'channel': channel}));
} else { } else {
event.instance.part(event, channel); event.instance.part(event, channel);
event.reply(dbot.t('part', {'channel': channel})); event.reply(dbot.t('part', {'channel': channel}));
@ -44,10 +44,12 @@ var admin = function(dbot) {
// Do a git pull and reload // Do a git pull and reload
'greload': function(event) { 'greload': function(event) {
var child = exec("git pull", function (error, stdout, stderr) { exec("git pull", function (error, stdout, stderr) {
exec("git submodule update", function (error, stdout, stderr) {
event.reply(dbot.t('gpull')); event.reply(dbot.t('gpull'));
commands.reload(event); commands.reload(event);
}.bind(this)); }.bind(this));
}.bind(this));
}, },
// Reload DB, translations and modules. // Reload DB, translations and modules.
@ -57,7 +59,7 @@ var admin = function(dbot) {
event.reply(dbot.t('reload')); event.reply(dbot.t('reload'));
}, },
// Say something in a channel (TODO probably doesn't work.) // Say something in a channel
'say': function(event) { 'say': function(event) {
var channel = event.params[1]; var channel = event.params[1];
if(event.params[1] === "@") { if(event.params[1] === "@") {
@ -116,13 +118,6 @@ var admin = function(dbot) {
} else { } else {
event.reply(dbot.t('unban_error', {'user': username})); 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}));
} }
}; };

View File

@ -1,3 +1,4 @@
{ {
"dbKeys": [ "bans", "locks" ] "dbKeys": [ "bans" ],
"help": "http://github.com/reality/depressionbot/blob/master/modules/admin/README.md"
} }

View File

@ -64,5 +64,11 @@
"spanish": "Cerrado la categoría: {category}", "spanish": "Cerrado la categoría: {category}",
"na'vi": "{category}ìri oel 'upxareti fmoli", "na'vi": "{category}ìri oel 'upxareti fmoli",
"welsh": "Categori wedi cloi: {category}" "welsh": "Categori wedi cloi: {category}"
},
"already_in_channel": {
"english": "I'm already in {channel}"
},
"not_in_channel": {
"english": "I'm not in {channel}"
} }
} }

14
modules/command/README.md Normal file
View File

@ -0,0 +1,14 @@
## Command
Handles the command execution logic for DBot.
### Description
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 specified in
the configuration file.

View File

@ -57,10 +57,34 @@ var command = function(dbot) {
'~usage': function(event) { '~usage': function(event) {
var commandName = event.params[1]; var commandName = event.params[1];
if(dbot.usage.hasOwnProperty(commandName)) { if(dbot.usage.hasOwnProperty(commandName)) {
event.reply('Usage for ' + commandName + ': ' + event.reply(dbot.t('usage', {
dbot.usage[commandName]); 'command': commandName,
'usage': dbot.usage[commandName]
}));
} else { } else {
event.reply('No usage information for ' + commandName); event.reply(dbot.t('no_usage_info', {
'command': commandName
}));
}
},
'~help': function(event) {
var moduleName = event.params[1];
if(!dbot.modules.hasOwnProperty(moduleName)) {
var moduleName = dbot.commandMap[moduleName];
}
if(moduleName && dbot.config[moduleName].hasOwnProperty('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 }))
} }
} }
}, },

View File

@ -0,0 +1,3 @@
{
"help": "http://github.com/reality/depressionbot/blob/master/modules/command/README.md"
}

View File

@ -10,5 +10,17 @@
"spanish": "Sintaxis no válida. Iniciar incineración.", "spanish": "Sintaxis no válida. Iniciar incineración.",
"na'vi": "Ngeyä pamrel keyawr lu. Nga skxawng lu.", "na'vi": "Ngeyä pamrel keyawr lu. Nga skxawng lu.",
"welsh": "Cystrawen annilys. Cychwyn orfflosgiad" "welsh": "Cystrawen annilys. Cychwyn orfflosgiad"
},
"usage": {
"english": "Usage for {command}: {usage}."
},
"no_usage_info": {
"english": "No usage information found for {command}."
},
"help_link": {
"english": "Help for {module}: {link}"
},
"no_help": {
"english": "No help found for {module}."
} }
} }

26
modules/ignore/README.md Normal file
View 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.

View File

@ -1,3 +1,4 @@
{ {
"dbKeys": [ "ignores" ] "dbKeys": [ "ignores" ],
"help": "http://github.com/reality/depressionbot/blob/master/modules/ignore/README.md"
} }

View File

@ -7,12 +7,11 @@
var ignore = function(dbot) { var ignore = function(dbot) {
var commands = { var commands = {
'~ignore': function(event) { '~ignore': function(event) {
var ignorableModules = []; var ignorableModules = dbot.modules.filter(function(module) {
for(var i=0;i<dbot.modules.length;i++) { if(module.ignorable != null && module.ignorable == true) {
if(dbot.modules[i].ignorable != null && dbot.modules[i].ignorable == true) { return true;
ignorableModules.push(dbot.modules[i].name);
}
} }
});
var module = event.params[1]; var module = event.params[1];
if(module === undefined) { if(module === undefined) {

40
modules/js/README.md Normal file
View 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.

3
modules/js/config.json Normal file
View File

@ -0,0 +1,3 @@
{
"help": "http://github.com/reality/depressionbot/blob/master/modules/js/README.md"
}

17
modules/kick/README.md Normal file
View 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.

View File

@ -1,3 +1,4 @@
{ {
"dbKeys": [ "kicks", "kickers" ] "dbKeys": [ "kicks", "kickers" ],
"help": "http://github.com/reality/depressionbot/blob/master/modules/kick/README.md"
} }

21
modules/link/README.md Normal file
View File

@ -0,0 +1,21 @@
## 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.

4
modules/link/config.json Normal file
View File

@ -0,0 +1,4 @@
{
"autoTitle": false,
"help": "http://github.com/reality/depressionbot/blob/master/modules/link/README.md"
}

View File

@ -7,6 +7,19 @@ var request = require('request');
var link = function(dbot) { var link = function(dbot) {
var urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; var urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
var links = {}; var links = {};
var 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]);
} else {
event.reply(dbot.t('title_not_found'));
}
}
});
};
var commands = { var commands = {
'~title': function(event) { '~title': function(event) {
@ -17,18 +30,7 @@ var link = function(dbot) {
link = urlMatches[0]; link = urlMatches[0];
} }
} }
fetchTitle(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]);
} else {
event.reply('no title found');
}
}
});
} }
}; };
@ -41,6 +43,10 @@ var link = function(dbot) {
var urlMatches = event.message.match(urlRegex); var urlMatches = event.message.match(urlRegex);
if(urlMatches !== null) { if(urlMatches !== null) {
links[event.channel.name] = urlMatches[0]; links[event.channel.name] = urlMatches[0];
if(dbot.config.link.autoTitle == true) {
fetchTitle(event, urlMatches[0]);
}
} }
}, },
'on': 'PRIVMSG' 'on': 'PRIVMSG'

View File

@ -0,0 +1,5 @@
{
"title_not_found": {
"english": "No page title found."
}
}

35
modules/poll/README.md Normal file
View 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.

3
modules/poll/config.json Normal file
View File

@ -0,0 +1,3 @@
{
"help": "http://github.com/reality/depressionbot/blob/master/modules/poll/README.md"
}

View File

@ -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);
};

68
modules/quotes/README.md Normal file
View File

@ -0,0 +1,68 @@
## 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.
### 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.

View File

@ -1,4 +1,5 @@
{ {
"dbKeys": [ "quoteArrs" ], "dbKeys": [ "quoteArrs" ],
"rmLimit": 10 "rmLimit": 10,
"help": "http://github.com/reality/depressionbot/blob/master/modules/quotes/README.md"
} }

View File

@ -55,6 +55,26 @@ var quotes = function(dbot) {
} }
}; };
var api = {
'getQuote': function(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)) {
return interpolatedQuote(key);
} else if(quotes.hasOwnProperty(altKey)) {
return interpolatedQuote(altKey);
} else {
return false;
}
}
}
};
var commands = { var commands = {
// Alternative syntax to ~q // Alternative syntax to ~q
'~': function(event) { '~': function(event) {
@ -101,20 +121,12 @@ var quotes = function(dbot) {
// Retrieve quote from a category in the database. // Retrieve quote from a category in the database.
'~q': function(event) { '~q': function(event) {
var key = event.input[1].trim().toLowerCase(); var key = event.input[1].trim().toLowerCase();
var altKey; var quote = api.getQuote(event.input[1]);
if(key.split(' ').length > 0) { if(quote) {
altKey = key.replace(/ /g, '_'); event.reply(key + ': ' + quote);
}
if(key.charAt(0) !== '_') { // lol
if(quotes.hasOwnProperty(key)) {
event.reply(key + ': ' + interpolatedQuote(key));
} else if(quotes.hasOwnProperty(altKey)) {
event.reply(altKey + ': ' + interpolatedQuote(altKey));
} else { } else {
event.reply(dbot.t('category_not_found', {'category': key})); event.reply(dbot.t('category_not_found', {'category': key}));
} }
}
}, },
// Shows a list of the biggest categories // Shows a list of the biggest categories
@ -157,7 +169,6 @@ var quotes = function(dbot) {
if(rmAllowed == true || dbot.config.admins.include(event.user)) { if(rmAllowed == true || dbot.config.admins.include(event.user)) {
var key = event.input[1].trim().toLowerCase(); var key = event.input[1].trim().toLowerCase();
if(quotes.hasOwnProperty(key)) { if(quotes.hasOwnProperty(key)) {
if(!dbot.db.locks.include(key) || dbot.config.admins.include(event.user)) {
var quote = quotes[key].pop(); var quote = quotes[key].pop();
if(quotes[key].length === 0) { if(quotes[key].length === 0) {
delete quotes[key]; delete quotes[key];
@ -165,9 +176,6 @@ var quotes = function(dbot) {
resetRemoveTimer(event, key, quote); resetRemoveTimer(event, key, quote);
event.reply(dbot.t('removed_from', {'quote': quote, 'category': key})); event.reply(dbot.t('removed_from', {'quote': quote, 'category': key}));
} else {
event.reply(dbot.t('locked_category', {'category': q[1]}));
}
} else { } else {
event.reply(dbot.t('no_quotes', {'category': q[1]})); event.reply(dbot.t('no_quotes', {'category': q[1]}));
} }
@ -182,7 +190,6 @@ var quotes = function(dbot) {
var quote = event.input[2]; var quote = event.input[2];
if(quotes.hasOwnProperty(key)) { if(quotes.hasOwnProperty(key)) {
if(!dbot.db.locks.include(key)) {
var category = quotes[key]; var category = quotes[key];
var index = category.indexOf(quote); var index = category.indexOf(quote);
if(index !== -1) { if(index !== -1) {
@ -196,9 +203,6 @@ var quotes = function(dbot) {
} else { } else {
event.reply(dbot.t('q_not_exist_under', {'category': key, 'quote': quote})); event.reply(dbot.t('q_not_exist_under', {'category': key, 'quote': quote}));
} }
} else {
event.reply(dbot.t('locked_category', {'category': key}));
}
} else { } else {
event.reply(dbot.t('category_not_found', {'category': key})); event.reply(dbot.t('category_not_found', {'category': key}));
} }
@ -295,11 +299,10 @@ var quotes = function(dbot) {
'ignorable': true, 'ignorable': true,
'commands': commands, 'commands': commands,
'pages': pages, 'pages': pages,
'api': api,
'listener': function(event) { 'listener': function(event) {
// Reality Once listener if(event.action == 'PRIVMSG') {
if((dbot.db.ignores.hasOwnProperty(event) &&
dbot.db.ignores[event.user].include(name)) == false) {
if(event.user == 'reality') { if(event.user == 'reality') {
var once = event.message.valMatch(/^I ([\d\w\s,'-]* once)/, 2); var once = event.message.valMatch(/^I ([\d\w\s,'-]* once)/, 2);
} else { } else {
@ -307,28 +310,19 @@ var quotes = function(dbot) {
} }
if(once) { if(once) {
if((dbot.db.bans.hasOwnProperty('~qadd') && event.message = '~qadd realityonce=reality ' + once[1];
dbot.db.bans['~qadd'].include(event.user)) || event.action = 'PRIVMSG';
dbot.db.bans['*'].include(event.user)) { event.params = event.message.split(' ');
event.reply(dbot.t('command_ban', {'user': event.user})); dbot.instance.emit(event);
} 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.');
}
} }
} else if(event.action == 'JOIN') {
var userQuote = api.getQuote(event.user)
if(userQuote) {
event.reply(event.user + ': ' + api.getQuote(event.user));
} }
} }
}, },
'on': ['PRIVMSG', 'JOIN']
'on': 'PRIVMSG'
}; };
}; };

19
modules/report/README.md Normal file
View 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.

View File

@ -0,0 +1,3 @@
{
"help": "http://github.com/reality/depressionbot/blob/master/modules/report/README.md"
}

View File

@ -21,18 +21,21 @@ var report = function(dbot) {
} }
for(var i=0;i<ops.length;i++) { for(var i=0;i<ops.length;i++) {
dbot.say(event.server, ops[i], dbot.say(event.server, ops[i], dbot.t('report', {
'Attention: ' + event.user + ' has reported ' + 'reporter': event.user,
nick + ' in ' + channelName + '. The reason ' + 'reported': nick,
'given was: "' + reason + '."'); 'channel': channelName,
'reason': reason
}));
} }
event.reply('Thank you, ' + nick + ' has been reported the channel administrators.'); event.reply(dbot.t('reported', { 'reported': nick }));
} else { } else {
event.reply('User is not in that channel.'); event.reply(dbot.t('user_not_found', { 'reported': nick,
'channel': channelName }));
} }
} else { } else {
event.reply('I am not in that channel.'); event.reply(dbot.t('not_in_channel', { 'channel': channelName }));
} }
} }

View File

@ -0,0 +1,14 @@
{
"report": {
"english": "Attention: {reporter} has reported {reported} in {channel}. The reason given was: \"{reason}.\""
},
"reported": {
"english": "Thank you, {reported} has been reported to the channel administrators."
},
"user_not_found": {
"english": "{reported} does not appear to be in {channel}."
},
"not_in_channel": {
"english": "I am not present in {channel}."
}
}

View 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.

View File

@ -0,0 +1,3 @@
{
"help": "http://github.com/reality/depressionbot/blob/master/modules/spelling/README.md"
}

@ -1 +1 @@
Subproject commit 8867e3592ea03b85093a5ab79017efb26c9156be Subproject commit cedfdb09ed857476298ba16eb90e3131f033ba60

View File

@ -0,0 +1,20 @@
{
"alias": {
"english": "{alias} is an alias of {user}"
},
"primary": {
"english": "{user} is a primary user with {count} aliases."
},
"unknown_alias": {
"english": "{alias} does not currently exist as an alias or known user."
},
"aliasparentset": {
"english": "{newParent} is now the parent user, and {newAlias} is an alias."
},
"unprimary_error": {
"english": "One of those users isn't currently recorded as a primary user."
},
"merged_users": {
"english": "{old_user} and their aliases have been merged into {new_user}."
}
}

View File

@ -4,15 +4,26 @@
*/ */
var users = function(dbot) { var users = function(dbot) {
var knownUsers = dbot.db.knownUsers; var knownUsers = dbot.db.knownUsers;
var getServerUsers = function(event) { var getServerUsers = function(server) {
if(!knownUsers.hasOwnProperty(event.server)) { if(!knownUsers.hasOwnProperty(server)) {
knownUsers[event.server] = { 'users': [], 'aliases': {} }; knownUsers[server] = { 'users': [], 'aliases': {} };
} }
return knownUsers[event.server]; return knownUsers[server];
}; };
var updateAliases = function(event, oldUser, newUser) {
var knownUsers = getServerUsers(event.server);
for(var alias in knownUsers.aliases) {
if(knownUsers.aliases.hasOwnProperty(alias)) {
if(knownUsers.aliases[alias] === oldUser) {
knownUsers.aliases[alias] = newUser;
}
}
}
}
dbot.instance.addListener('366', 'users', function(event) { dbot.instance.addListener('366', 'users', function(event) {
var knownUsers = getServerUsers(event); var knownUsers = getServerUsers(event.server);
for(var nick in event.channel.nicks) { for(var nick in event.channel.nicks) {
if(!knownUsers.users.include(nick) && !knownUsers.aliases.hasOwnProperty(nick) && if(!knownUsers.users.include(nick) && !knownUsers.aliases.hasOwnProperty(nick) &&
event.channel.nicks.hasOwnProperty(nick)) { event.channel.nicks.hasOwnProperty(nick)) {
@ -80,34 +91,114 @@ var users = function(dbot) {
}, },
}; };
return { var api = {
'name': 'users', 'resolveUser': function(server, nick, useLowercase) {
'ignorable': false, var knownUsers = getServerUsers(server);
var user = nick;
if(!knownUsers.users.include(nick) && knownUsers.aliases.hasOwnProperty(nick)) {
user = knownUsers.aliases[nick];
}
'commands': { if(useLowercase) user = user.toLowerCase();
return user;
}
};
var commands = {
'~alias': function(event) { '~alias': function(event) {
var knownUsers = getServerUsers(event); var knownUsers = getServerUsers(event.server);
var alias = event.params[1].trim(); var alias = event.params[1].trim();
if(knownUsers.aliases.hasOwnProperty(alias)) { if(knownUsers.users.include(alias)) {
event.reply(alias + ' is an alias of ' + knownUsers.aliases[alias]); var aliasCount = 0;
knownUsers.aliases.each(function(primaryUser) {
if(primaryUser == alias) aliasCount += 1;
}.bind(this));
event.reply(dbot.t('primary', { 'user': alias, 'count': aliasCount }));
} else if(knownUsers.aliases.hasOwnProperty(alias)) {
event.reply(dbot.t('alias', { 'alias': alias,
'user': knownUsers.aliases[alias] }));
} else { } else {
event.reply(alias + ' is not known as an alias to me.'); event.reply(dbot.t('unknown_alias', { 'alias': alias }));
}
},
'~setaliasparent': function(event) {
if(dbot.config.admins.include(event.user)) {
var knownUsers = getServerUsers(event.server);
var newParent = event.params[1];
if(knownUsers.aliases.hasOwnProperty(newParent)) {
var newAlias = knownUsers.aliases[newParent];
// Replace users entry with new primary user
var usersIndex = knownUsers.users.indexOf(newAlias);
knownUsers.users.splice(usersIndex, 1);
knownUsers.users.push(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
updateAliases(event, newAlias, newParent);
event.reply(dbot.t('aliasparentset', { 'newParent': newParent,
'newAlias': newAlias }));
dbot.api.stats.fixStats(event.server, newAlias);
} else {
event.reply(dbot.t('unknown_alias', { 'alias': newParent}));
} }
} }
}, },
'~mergeusers': function(event) {
if(dbot.config.admins.include(event.user)) {
var knownUsers = getServerUsers(event.server);
var primaryUser = event.params[1];
var secondaryUser = event.params[2];
if(knownUsers.users.include(primaryUser) && knownUsers.users.include(secondaryUser)) {
knownUsers.users.splice(knownUsers.users.indexOf(secondaryUser), 1);
knownUsers.aliases[secondaryUser] = primaryUser;
updateAliases(event, secondaryUser, primaryUser);
event.reply(dbot.t('merged_users', {
'old_user': secondaryUser,
'new_user': primaryUser
}));
dbot.api.stats.fixStats(event.server, secondaryUser);
} else {
event.reply(dbot.t('unprimary_error'));
}
}
}
};
return {
'name': 'users',
'ignorable': false,
'commands': commands,
'api': api,
'pages': pages, 'pages': pages,
'listener': function(event) { 'listener': function(event) {
var knownUsers = getServerUsers(event); var knownUsers = getServerUsers(event.server);
if(event.action == 'JOIN') { if(event.action == 'JOIN') {
if(!knownUsers.users.include(event.user)) { if(!knownUsers.users.include(event.user)) {
knownUsers.users.push(event.user); knownUsers.users.push(event.user);
} }
} else if(event.action == 'NICK') { } else if(event.action == 'NICK') {
var newNick = event.params.substr(1); var newNick = event.params.substr(1);
if(knownUsers.aliases.hasOwnProperty(event.user)) {
knownUsers.aliases[newNick] = knownUsers.aliases[event.user];
} else {
if(!knownUsers.users.include(newNick)) {
knownUsers.aliases[newNick] = event.user; knownUsers.aliases[newNick] = event.user;
} }
}
}
}, },
'on': ['JOIN', 'NICK'], 'on': ['JOIN', 'NICK'],

7
modules/web/README.md Normal file
View File

@ -0,0 +1,7 @@
## Web
Web interface
### Description
It's a web interface for DBot. What of it?

View File

@ -1,4 +1,4 @@
{ {
"webHost": "localhost", "webHost": "localhost",
"webPort": 8080 "webPort": 8090
} }

10
modules/youare/README.md Normal file
View 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.

13
run.js
View File

@ -117,9 +117,10 @@ DBot.prototype.reloadModules = function() {
} }
this.rawModules = []; this.rawModules = [];
this.modules = [];
this.pages = {}; this.pages = {};
this.modules = {};
this.commands = {}; this.commands = {};
this.api = {};
this.commandMap = {}; // Map of which commands belong to which modules this.commandMap = {}; // Map of which commands belong to which modules
this.usage = {}; this.usage = {};
this.timers.clearTimers(); this.timers.clearTimers();
@ -209,6 +210,11 @@ DBot.prototype.reloadModules = function() {
} }
} }
// Load module API
if(module.api) {
this.api[module.name] = module.api;
}
// Load the module usage data // Load the module usage data
try { try {
var usage = JSON.parse(fs.readFileSync(moduleDir + 'usage.json', 'utf-8')); var usage = JSON.parse(fs.readFileSync(moduleDir + 'usage.json', 'utf-8'));
@ -241,7 +247,10 @@ DBot.prototype.reloadModules = function() {
// Invalid or no string info // Invalid or no string info
} }
this.modules.push(module); module.toString = function() {
return this.name;
}
this.modules[module.name] = module;
} catch(err) { } catch(err) {
console.log(this.t('module_load_error', {'moduleName': name})); console.log(this.t('module_load_error', {'moduleName': name}));
if(this.config.debugMode) { if(this.config.debugMode) {

View File

@ -204,7 +204,15 @@ Object.prototype.filter = function(fun) {
} }
} }
return filtered; return filtered;
} };
Object.prototype.each = function(fun) {
for(var key in this) {
if(this.hasOwnProperty(key)) {
fun(this[key]);
}
}
};
/*** Integer ***/ /*** Integer ***/
@ -263,3 +271,8 @@ Number.prototype.numberFormat = function(dec_places){
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, sep); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, sep);
return parts.join(dec_point); return parts.join(dec_point);
} }
// http://simonwillison.net/2006/Jan/20/escape/#p-6
String.prototype.escape = function() {
return this.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}