mirror of
https://github.com/reality/dbot.git
synced 2024-12-25 20:22:39 +01:00
Merge reality's stuff
This commit is contained in:
commit
feb51aaceb
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -1,3 +1,9 @@
|
|||||||
[submodule "jsbot"]
|
[submodule "jsbot"]
|
||||||
path = jsbot
|
path = jsbot
|
||||||
url = git://github.com/reality/jsbot.git
|
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
|
## 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.
|
||||||
@ -15,111 +15,14 @@ Requirements:
|
|||||||
|
|
||||||
- Node JS
|
- Node JS
|
||||||
- [JSBot](http://github.com/reality/JSBot "JSBot"), a Javascript library which
|
- [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.
|
- 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.
|
|
||||||
|
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" ],
|
"admins": [ "batman" ],
|
||||||
"moduleNames": [ "ignore", "admin", "command", "dice", "js", "kick", "puns", "quotes", "spelling", "youare" ],
|
"moderators": [ "whatever" ],
|
||||||
"language": "english"
|
"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
|
* Description: Set of commands which only one who is a DepressionBot
|
||||||
* administrator can run - as such, it has its own command execution listener.
|
* administrator can run - as such, it has its own command execution listener.
|
||||||
*/
|
*/
|
||||||
var fs = require('fs');
|
var fs = require('fs'),
|
||||||
var sys = require('sys')
|
_ = require('underscore')._;
|
||||||
var exec = require('child_process').exec;
|
|
||||||
|
|
||||||
var admin = function(dbot) {
|
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) {
|
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}",
|
"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}",
|
||||||
|
"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,86 +4,50 @@
|
|||||||
* command and then runs that command, given the user isn't banned from or
|
* command and then runs that command, given the user isn't banned from or
|
||||||
* ignoring that command.
|
* ignoring that command.
|
||||||
*/
|
*/
|
||||||
|
var _ = require('underscore')._;
|
||||||
var command = function(dbot) {
|
var command = function(dbot) {
|
||||||
/**
|
this.dbot = dbot;
|
||||||
* Is user banned from using command?
|
|
||||||
*/
|
|
||||||
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.
|
* Run the appropriate command given the input.
|
||||||
*/
|
*/
|
||||||
'listener': function(event) {
|
this.listener = function(event) {
|
||||||
var commandName = event.params[0];
|
var commandName = event.params[0];
|
||||||
if(!dbot.commands.hasOwnProperty(commandName)) {
|
if(!_.has(dbot.commands, commandName)) {
|
||||||
|
if(_.has(dbot.modules, 'quotes')) {
|
||||||
commandName = '~';
|
commandName = '~';
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isBanned(event.user, commandName)) {
|
if(this.api.isBanned(event.user, commandName)) {
|
||||||
event.reply(dbot.t('command_ban', {'user': event.user}));
|
event.reply(dbot.t('command_ban', {'user': event.user}));
|
||||||
} else {
|
} else {
|
||||||
if(!isIgnoring(event.user, commandName)) {
|
if(!this.api.isIgnoring(event.user, commandName) &&
|
||||||
if(applyRegex(commandName, event)) {
|
!this.api.isIgnoring(event.channel, commandName) &&
|
||||||
dbot.commands[commandName](event);
|
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();
|
dbot.save();
|
||||||
} else {
|
} else {
|
||||||
if(commandName !== '~') {
|
if(commandName !== '~') {
|
||||||
if(dbot.usage.hasOwnProperty(commandName)){
|
if(_.has(dbot.usage, commandName)) {
|
||||||
event.reply('Usage: ' + dbot.usage[commandName]);
|
event.reply('Usage: ' + dbot.usage[commandName]);
|
||||||
} else {
|
} else {
|
||||||
event.reply(dbot.t('syntax_error'));
|
event.reply(dbot.t('syntax_error'));
|
||||||
@ -92,12 +56,10 @@ var command = function(dbot) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}.bind(this);
|
||||||
'on': 'PRIVMSG'
|
this.on = 'PRIVMSG';
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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.",
|
"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}.",
|
||||||
|
"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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.commands = commands;
|
||||||
return {
|
|
||||||
'name': 'dice',
|
|
||||||
'commands': commands,
|
|
||||||
'ignorable': true
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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,34 +2,43 @@
|
|||||||
* Module Name: Ignore
|
* Module Name: Ignore
|
||||||
* Description: Handles commands in which users can choose to ignore listeners
|
* Description: Handles commands in which users can choose to ignore listeners
|
||||||
* and commands from certain modules. It also populates the JSBot instance with
|
* 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 ignore = function(dbot) {
|
||||||
var commands = {
|
var commands = {
|
||||||
'~ignore': function(event) {
|
'~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 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) {
|
if(_.isUndefined(module)) {
|
||||||
event.reply(dbot.t('ignore_usage', {'user': event.user, 'modules': ignorableModules.join(', ')}));
|
event.reply(dbot.t('ignore_usage', {
|
||||||
|
'user': event.user,
|
||||||
|
'modules': ignorableModules.join(', ')
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
if(ignorableModules.include(module)) {
|
if(_.include(ignorableModules, module)) {
|
||||||
if(dbot.db.ignores.hasOwnProperty(event.user) && dbot.db.ignores[event.user].include(module)) {
|
if(_.has(dbot.db.ignores, event.user) && _.include(dbot.db.ignores[event.user], module)) {
|
||||||
event.reply(dbot.t('already_ignoring', { 'user': event.user }));
|
event.reply(dbot.t('already_ignoring', { 'user': event.user }));
|
||||||
} else {
|
} else {
|
||||||
if(dbot.db.ignores.hasOwnProperty(module)) {
|
if(_.has(dbot.db.ignores, module)) {
|
||||||
dbot.db.ignores[event.user].push(module);
|
dbot.db.ignores[event.user].push(module);
|
||||||
} else {
|
} else {
|
||||||
dbot.db.ignores[event.user] = [module];
|
dbot.db.ignores[event.user] = [module];
|
||||||
}
|
}
|
||||||
|
|
||||||
dbot.instance.ignoreTag(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 {
|
} else {
|
||||||
event.reply(dbot.t('invalid_ignore', { 'user': event.user }));
|
event.reply(dbot.t('invalid_ignore', { 'user': event.user }));
|
||||||
@ -39,43 +48,91 @@ var ignore = function(dbot) {
|
|||||||
|
|
||||||
'~unignore': function(event) {
|
'~unignore': function(event) {
|
||||||
var ignoredModules = [];
|
var ignoredModules = [];
|
||||||
if(dbot.db.ignores.hasOwnProperty(event.user)) {
|
if(_.has(dbot.db.ignores, event.user)) {
|
||||||
ignoredModules = dbot.db.ignores[event.user];
|
ignoredModules = dbot.db.ignores[event.user];
|
||||||
}
|
}
|
||||||
var module = event.params[1];
|
var module = event.params[1];
|
||||||
|
|
||||||
if(module === undefined) {
|
if(_.isUndefined(module)) {
|
||||||
event.reply(dbot.t('unignore_usage', {'user': event.user, 'modules': ignoredModules.join(', ')}));
|
event.reply(dbot.t('unignore_usage', {
|
||||||
} else {
|
'user': event.user,
|
||||||
if(ignoredModules.include(module) == false) {
|
'modules': ignoredModules.join(', ')
|
||||||
event.reply(dbot.t('invalid_unignore', {'user': event.user}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
|
if(_.include(ignoredModules, module)) {
|
||||||
dbot.db.ignores[event.user].splice(dbot.db.ignores[event.user].indexOf(module), 1);
|
dbot.db.ignores[event.user].splice(dbot.db.ignores[event.user].indexOf(module), 1);
|
||||||
dbot.instance.removeIgnore(event.user, module)
|
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 {
|
commands['~ignorechannel'].access = 'moderator';
|
||||||
'name': 'ignore',
|
commands['~unignorechannel'].access = 'moderator';
|
||||||
'ignorable': false,
|
|
||||||
'commands': commands,
|
|
||||||
|
|
||||||
'onLoad': function() {
|
this.commands = commands;
|
||||||
|
|
||||||
|
this.onLoad = function() {
|
||||||
dbot.instance.clearIgnores();
|
dbot.instance.clearIgnores();
|
||||||
for(var user in dbot.db.ignores) {
|
_.each(dbot.db.ignores, function(ignores, item) {
|
||||||
if(dbot.db.ignores.hasOwnProperty(user)) {
|
_.each(ignores, function(ignore) {
|
||||||
for(var i=0;i<dbot.db.ignores[user].length;i++) {
|
dbot.instance.ignoreTag(item, ignore);
|
||||||
dbot.instance.ignoreTag(user, dbot.db.ignores[user][i]);
|
}, this);
|
||||||
}
|
}, this);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
exports.fetch = function(dbot) {
|
||||||
return ignore(dbot);
|
return new ignore(dbot);
|
||||||
};
|
};
|
||||||
|
@ -40,5 +40,25 @@
|
|||||||
"spanish": "{user}: Ya no ignoras {module}.",
|
"spanish": "{user}: Ya no ignoras {module}.",
|
||||||
"na'vi": "{user}: Nga terìng mikyun {module}ne set",
|
"na'vi": "{user}: Nga terìng mikyun {module}ne set",
|
||||||
"welsh": "{user}: Ddim yn anwybyddu {module} bellach"
|
"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 sbox = require('sandbox');
|
||||||
|
|
||||||
var js = function(dbot) {
|
var js = function(dbot) {
|
||||||
var s = new sbox();
|
|
||||||
|
|
||||||
var commands = {
|
var commands = {
|
||||||
// Run JS code sandboxed, return result to channel.
|
// Run JS code sandboxed, return result to channel.
|
||||||
'~js': function(event) {
|
'~js': function(event) {
|
||||||
try {
|
try {
|
||||||
|
var s = new sbox();
|
||||||
s.run(event.input[1], function(output) {
|
s.run(event.input[1], function(output) {
|
||||||
event.reply(output.result);
|
event.reply(output.result);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@ -22,12 +21,10 @@ var js = function(dbot) {
|
|||||||
|
|
||||||
// Run JS code un-sandboxed, with access to DBot memory (admin-only).
|
// Run JS code un-sandboxed, with access to DBot memory (admin-only).
|
||||||
'~ajs': function(event) {
|
'~ajs': function(event) {
|
||||||
if(dbot.config.admins.include(event.user) ) {
|
|
||||||
var ret = eval(event.input[1]);
|
var ret = eval(event.input[1]);
|
||||||
if(ret !== undefined) {
|
if(ret !== undefined) {
|
||||||
event.reply(ret);
|
event.reply(ret);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'jesus': function (event) {
|
'jesus': function (event) {
|
||||||
event.reply(event.user + ": s/(.)(.)(.)(..)/\4\2 \1\3/")
|
event.reply(event.user + ": s/(.)(.)(.)(..)/\4\2 \1\3/")
|
||||||
@ -36,14 +33,13 @@ var js = function(dbot) {
|
|||||||
commands['~js'].regex = [/^~js (.*)/, 2];
|
commands['~js'].regex = [/^~js (.*)/, 2];
|
||||||
commands['~ajs'].regex = [/^~ajs (.*)/, 2];
|
commands['~ajs'].regex = [/^~ajs (.*)/, 2];
|
||||||
commands['jesus'].regex = [/^jesus$/, 2];
|
commands['jesus'].regex = [/^jesus$/, 2];
|
||||||
|
commands['~ajs'].access = 'admin';
|
||||||
|
|
||||||
return {
|
this.name = 'js';
|
||||||
'name': 'js',
|
this.ignorable = true;
|
||||||
'ignorable': true,
|
this.commands = commands;
|
||||||
'commands': commands
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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 kick = function(dbot) {
|
||||||
var commands = {
|
var commands = {
|
||||||
// Give the number of times a given user has been kicked and has kicked
|
// 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) {
|
'~kickcount': function(event) {
|
||||||
var username = event.params[1];
|
var username = event.params[1];
|
||||||
|
|
||||||
if(!dbot.db.kicks.hasOwnProperty(username)) {
|
if(!_.has(dbot.db.kicks, username)) {
|
||||||
var kicks = '0';
|
var kicks = '0';
|
||||||
} else {
|
} else {
|
||||||
var kicks = dbot.db.kicks[username];
|
var kicks = dbot.db.kicks[username];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!dbot.db.kickers.hasOwnProperty(username)) {
|
if(!_.has(dbot.db.kickers, username)) {
|
||||||
var kicked = '0';
|
var kicked = '0';
|
||||||
} else {
|
} else {
|
||||||
var kicked = dbot.db.kickers[username];
|
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
|
// Output a list of the people who have been kicked the most and those
|
||||||
// who have kicked other people the most.
|
// who have kicked other people the most.
|
||||||
'~kickstats': function(event) {
|
'~kickstats': function(event) {
|
||||||
var orderedKickLeague = function(list, topWhat) {
|
var orderedKickLeague = function(list, topWhat) {
|
||||||
var kickArr = Object.prototype.sort(list, function(key, obj) { return obj[key]; });
|
var kickArr = _.chain(list)
|
||||||
kickArr = kickArr.slice(kickArr.length - 10).reverse();
|
.pairs()
|
||||||
|
.sortBy(function(kick) { return kick[1] })
|
||||||
|
.reverse()
|
||||||
|
.first(10)
|
||||||
|
.value();
|
||||||
|
|
||||||
var kickString = "Top " + topWhat + ": ";
|
var kickString = "Top " + topWhat + ": ";
|
||||||
for(var i=0;i<kickArr.length;i++) {
|
for(var i=0;i<kickArr.length;i++) {
|
||||||
@ -39,38 +49,36 @@ var kick = function(dbot) {
|
|||||||
event.reply(orderedKickLeague(dbot.db.kickers, 'Kickers'));
|
event.reply(orderedKickLeague(dbot.db.kickers, 'Kickers'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.commands = commands;
|
||||||
|
|
||||||
return {
|
this.listener = function(event) {
|
||||||
'name': 'kick',
|
|
||||||
'ignorable': false,
|
|
||||||
'commands': commands,
|
|
||||||
|
|
||||||
'listener': function(event) {
|
|
||||||
if(event.kickee == dbot.config.name) {
|
if(event.kickee == dbot.config.name) {
|
||||||
dbot.instance.join(event, event.channel);
|
dbot.instance.join(event, event.channel);
|
||||||
event.reply(dbot.t('kicked_dbot', { 'botname': dbot.config.name }));
|
event.reply(dbot.t('kicked_dbot', { 'botname': dbot.config.name }));
|
||||||
dbot.db.kicks[dbot.config.name] += 1;
|
dbot.db.kicks[dbot.config.name] += 1;
|
||||||
} else {
|
} else {
|
||||||
if(!dbot.db.kicks.hasOwnProperty(event.kickee)) {
|
if(!_.has(dbot.db.kicks, event.kickee)) {
|
||||||
dbot.db.kicks[event.kickee] = 1;
|
dbot.db.kicks[event.kickee] = 1;
|
||||||
} else {
|
} else {
|
||||||
dbot.db.kicks[event.kickee] += 1;
|
dbot.db.kicks[event.kickee] += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!dbot.db.kickers.hasOwnProperty(event.user)) {
|
if(!_.has(dbot.db.kickers, event.user)) {
|
||||||
dbot.db.kickers[event.user] = 1;
|
dbot.db.kickers[event.user] = 1;
|
||||||
} else {
|
} else {
|
||||||
dbot.db.kickers[event.user] += 1;
|
dbot.db.kickers[event.user] += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.reply(event.kickee + '-- (' + dbot.t('user_kicks',
|
event.reply(event.kickee + '-- (' + dbot.t('user_kicks', {
|
||||||
{'user': event.kickee, 'kicks': dbot.db.kicks[event.kickee], 'kicked': dbot.db.kickers[event.kickee]}) + ')');
|
'user': event.kickee,
|
||||||
|
'kicks': dbot.db.kicks[event.kickee],
|
||||||
|
'kicked': dbot.db.kickers[event.kickee]
|
||||||
|
}) + ')');
|
||||||
}
|
}
|
||||||
},
|
|
||||||
on: 'KICK'
|
|
||||||
};
|
};
|
||||||
|
this.on = 'KICK';
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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
|
* Description: Stores recent channel links, with commands to retrieve
|
||||||
* information about links.
|
* information about links.
|
||||||
*/
|
*/
|
||||||
var request = require('request');
|
var request = require('request'),
|
||||||
|
_ = require('underscore')._;
|
||||||
|
|
||||||
var link = function(dbot) {
|
var link = function(dbot) {
|
||||||
var urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
this.urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||||
var links = {};
|
this.links = {};
|
||||||
|
this.fetchTitle = function(event, link) {
|
||||||
var commands = {
|
|
||||||
'~title': function(event) {
|
|
||||||
var link = links[event.channel.name];
|
|
||||||
if(event.params[1] !== undefined) {
|
|
||||||
var urlMatches = event.params[1].match(urlRegex);
|
|
||||||
if(urlMatches !== null) {
|
|
||||||
link = urlMatches[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request(link, function(error, response, body) {
|
request(link, function(error, response, body) {
|
||||||
if(!error && response.statusCode == 200) {
|
if(!error && response.statusCode == 200) {
|
||||||
body = body.replace(/(\r\n|\n\r|\n)/gm, " ");
|
body = body.replace(/(\r\n|\n\r|\n)/gm, " ");
|
||||||
var title = body.valMatch(/<title>(.*)<\/title>/, 2);
|
var title = body.valMatch(/<title>(.*)<\/title>/, 2);
|
||||||
if(title) {
|
if(title) {
|
||||||
event.reply(title[1]);
|
event.reply(title[1]);
|
||||||
} else {
|
|
||||||
event.reply('no title found');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
var commands = {
|
||||||
'name': 'link',
|
'~title': function(event) {
|
||||||
'ignorable': true,
|
var link = this.links[event.channel.name];
|
||||||
'commands': commands,
|
if(!_.isUndefined(event.params[1])) {
|
||||||
|
var urlMatches = event.params[1].match(this.urlRegex);
|
||||||
'listener': function(event) {
|
|
||||||
var urlMatches = event.message.match(urlRegex);
|
|
||||||
if(urlMatches !== null) {
|
if(urlMatches !== null) {
|
||||||
links[event.channel.name] = urlMatches[0];
|
link = urlMatches[0];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
this.fetchTitle(event, link);
|
||||||
},
|
},
|
||||||
'on': 'PRIVMSG'
|
|
||||||
|
'~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;
|
||||||
|
|
||||||
|
this.listener = function(event) {
|
||||||
|
var urlMatches = event.message.match(this.urlRegex);
|
||||||
|
if(urlMatches !== null) {
|
||||||
|
this.links[event.channel.name] = urlMatches[0];
|
||||||
|
|
||||||
|
if(dbot.config.link.autoTitle == true) {
|
||||||
|
this.fetchTitle(event, urlMatches[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
this.on = 'PRIVMSG';
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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 poll = function(dbot) {
|
||||||
var polls = dbot.db.polls;
|
this.internalAPI = {
|
||||||
var commands = {
|
'updatePollNicks': function(server, oldNick) {
|
||||||
'~newpoll': function(event) {
|
var newNick = dbot.api.users.resolveUser(server, oldNick);
|
||||||
var av = event.input[1] != undefined;
|
_.each(dbot.db.polls, function(poll) {
|
||||||
var name = event.input[2];
|
if(poll.owner === oldNick) {
|
||||||
var options = event.input[3].split(',');
|
poll.owner = newNick;
|
||||||
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 {
|
if(_.has(poll.votees, oldNick)) {
|
||||||
polls[name] = {
|
poll.votees[newNick] = poll.votees[oldNick];
|
||||||
'av': av,
|
delete poll.votees[oldNick];
|
||||||
'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})}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'~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}));
|
|
||||||
}
|
|
||||||
} 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 {
|
this.onLoad = function() {
|
||||||
'name': 'poll',
|
dbot.api.command.addHook('~setaliasparent', this.internalAPI.updatePollNicks);
|
||||||
'ignorable': true,
|
dbot.api.command.addHook('~mergeusers', this.internalAPI.updatePollNicks);
|
||||||
'commands': commands
|
}.bind(this);
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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}"
|
"na'vi": "sìpawm sna'o '{name}' ngìyop ({description}). Nga tìpe'unit Pe'eiun - {url}"
|
||||||
},
|
},
|
||||||
"poll_describe": {
|
"poll_describe": {
|
||||||
"english": "{name}: {description} - {url}"
|
"english": "{name}: {description} - {url}."
|
||||||
},
|
},
|
||||||
"changed_vote": {
|
"changed_vote": {
|
||||||
"english": "{user} changed their vote in {poll} to '{vote}' ({count}).",
|
"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,19 +1,25 @@
|
|||||||
var quotes = function(dbot) {
|
var _ = require('underscore')._;
|
||||||
var name = 'quotes';
|
|
||||||
var quotes = dbot.db.quoteArrs;
|
|
||||||
var addStack = [];
|
|
||||||
var rmAllowed = true;
|
|
||||||
|
|
||||||
|
var quotes = function(dbot) {
|
||||||
|
dbot.sessionData.rmCache = [];
|
||||||
|
this.quotes = dbot.db.quoteArrs,
|
||||||
|
this.addStack = [],
|
||||||
|
this.rmAllowed = true,
|
||||||
|
this.rmCache = dbot.sessionData.rmCache,
|
||||||
|
this.rmTimer;
|
||||||
|
|
||||||
|
this.internalAPI = {
|
||||||
// Retrieve a random quote from a given category, interpolating any quote
|
// Retrieve a random quote from a given category, interpolating any quote
|
||||||
// references (~~QUOTE CATEGORY~~) within it
|
// references (~~QUOTE CATEGORY~~) within it
|
||||||
var interpolatedQuote = function(key, quoteTree) {
|
'interpolatedQuote': function(server, channel, key, quoteTree) {
|
||||||
if(quoteTree !== undefined && quoteTree.indexOf(key) != -1) {
|
if(!_.isUndefined(quoteTree) && quoteTree.indexOf(key) != -1) {
|
||||||
return '';
|
return '';
|
||||||
} else if(quoteTree === undefined) {
|
} else if(_.isUndefined(quoteTree)) {
|
||||||
quoteTree = [];
|
quoteTree = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var quoteString = quotes[key].random();
|
var index = _.random(0, this.quotes[key].length - 1);
|
||||||
|
var quoteString = this.quotes[key][index];
|
||||||
|
|
||||||
// Parse quote interpolations
|
// Parse quote interpolations
|
||||||
var quoteRefs = quoteString.match(/~~([\d\w\s-]*)~~/g);
|
var quoteRefs = quoteString.match(/~~([\d\w\s-]*)~~/g);
|
||||||
@ -21,204 +27,67 @@ var quotes = function(dbot) {
|
|||||||
|
|
||||||
while(quoteRefs && (thisRef = quoteRefs.shift()) !== undefined) {
|
while(quoteRefs && (thisRef = quoteRefs.shift()) !== undefined) {
|
||||||
var cleanRef = dbot.cleanNick(thisRef.replace(/^~~/,'').replace(/~~$/,'').trim());
|
var cleanRef = dbot.cleanNick(thisRef.replace(/^~~/,'').replace(/~~$/,'').trim());
|
||||||
if (quotes.hasOwnProperty(cleanRef)) {
|
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);
|
quoteTree.push(key);
|
||||||
quoteString = quoteString.replace("~~" + cleanRef + "~~",
|
quoteString = quoteString.replace("~~" + cleanRef + "~~",
|
||||||
interpolatedQuote(cleanRef, quoteTree.slice()));
|
this.internalAPI.interpolatedQuote(server, channel, cleanRef, quoteTree.slice()));
|
||||||
quoteTree.pop();
|
quoteTree.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return quoteString;
|
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 = {
|
this.api = {
|
||||||
// Alternative syntax to ~q
|
'getQuote': function(event, category) {
|
||||||
'~': function(event) {
|
var key = category.trim().toLowerCase();
|
||||||
commands['~q'](event);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Retrieve quote from a category in the database.
|
|
||||||
'~q': function(event) {
|
|
||||||
var key = event.input[1].trim().toLowerCase();
|
|
||||||
var altKey;
|
var altKey;
|
||||||
if(key.split(' ').length > 0) {
|
if(key.split(' ').length > 0) {
|
||||||
altKey = key.replace(/ /g, '_');
|
altKey = key.replace(/ /g, '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.charAt(0) !== '_') { // lol
|
if(key.charAt(0) !== '_') { // lol
|
||||||
if(quotes.hasOwnProperty(key)) {
|
if(_.has(this.quotes, key)) {
|
||||||
event.reply(key + ': ' + interpolatedQuote(key));
|
return this.internalAPI.interpolatedQuote(event.server, event.channel.name, key);
|
||||||
} else if(quotes.hasOwnProperty(altKey)) {
|
} else if(_.has(this.quotes, altKey)) {
|
||||||
event.reply(altKey + ': ' + interpolatedQuote(altKey));
|
return this.internalAPI.interpolatedQuote(event.server, event.channel.name, altKey);
|
||||||
} else {
|
} 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];
|
this.listener = function(event) {
|
||||||
commands['~q'].regex = [/^~q ([\d\w\s-]*)/, 2];
|
if(event.action == 'PRIVMSG') {
|
||||||
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') {
|
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 {
|
||||||
@ -226,31 +95,21 @@ 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] + '.')) {
|
} else if(event.action == 'JOIN') {
|
||||||
event.reply(event.user + ': reality has already done that once.');
|
var userQuote = this.api.getQuote(event, event.user)
|
||||||
} else {
|
if(userQuote) {
|
||||||
dbot.db.quoteArrs['realityonce'].push('reality ' + once[1] + '.');
|
event.reply(event.user + ': ' + this.api.getQuote(event, event.user));
|
||||||
addStack.push('realityonce');
|
|
||||||
rmAllowed = true;
|
|
||||||
event.reply('\'reality ' + once[1] + '.\' saved.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.bind(this);
|
||||||
}
|
this.on = ['PRIVMSG', 'JOIN'];
|
||||||
},
|
|
||||||
|
|
||||||
'on': 'PRIVMSG'
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
exports.fetch = function(dbot) {
|
||||||
return quotes(dbot);
|
return new quotes(dbot);
|
||||||
};
|
};
|
||||||
|
@ -106,5 +106,25 @@
|
|||||||
"spanish" : "{category} ({needle}): '{quote}' [{matches} resultados]",
|
"spanish" : "{category} ({needle}): '{quote}' [{matches} resultados]",
|
||||||
"na'vi": "{category} ({needle}): '{quote}' [kum a{matches}]",
|
"na'vi": "{category} ({needle}): '{quote}' [kum a{matches}]",
|
||||||
"welsh": "{category} ({needle}): '{quote}' [{matches} canlyniad]"
|
"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 report = function(dbot) {
|
||||||
var commands = {
|
var commands = {
|
||||||
'~report': function(event) {
|
'~report': function(event) {
|
||||||
@ -5,47 +7,37 @@ var report = function(dbot) {
|
|||||||
var nick = event.input[2];
|
var nick = event.input[2];
|
||||||
var reason = event.input[3];
|
var reason = event.input[3];
|
||||||
|
|
||||||
if(event.allChannels.hasOwnProperty(channelName)) {
|
if(_.has(event.allChannels, channelName)) {
|
||||||
var channel = event.allChannels[channelName];
|
var channel = event.allChannels[channelName];
|
||||||
if(channel.nicks.hasOwnProperty(nick)) {
|
if(dbot.api.users.isChannelUser(event.server, nick, channelName, true)) {
|
||||||
var ops = [];
|
var nick = dbot.api.users.resolveUser(event.server, nick, true);
|
||||||
for(var possibOps in channel.nicks) {
|
var ops = _.filter(channel.nicks, function(user) {
|
||||||
if(channel.nicks[possibOps].op == true) {
|
return user.op;
|
||||||
ops.push(possibOps);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does the channel have an admin channel?
|
_.each(ops, function(user) {
|
||||||
if(event.allChannels.hasOwnProperty('#' + channelName)) {
|
dbot.say(event.server, user.name, dbot.t('report', {
|
||||||
ops.push('#' + channelName);
|
'reporter': event.user,
|
||||||
}
|
'reported': nick,
|
||||||
|
'channel': channelName,
|
||||||
|
'reason': reason
|
||||||
|
}));
|
||||||
|
}, this);
|
||||||
|
|
||||||
for(var i=0;i<ops.length;i++) {
|
event.reply(dbot.t('reported', { 'reported': nick }));
|
||||||
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.');
|
|
||||||
} 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 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
commands['~report'].regex = [/^~report ([^ ]+) ([^ ]+) (.+)$/, 4];
|
commands['~report'].regex = [/^~report ([^ ]+) ([^ ]+) (.+)$/, 4];
|
||||||
|
this.commands = commands;
|
||||||
return {
|
|
||||||
'name': 'report',
|
|
||||||
'ignorable': true,
|
|
||||||
'commands': commands
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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 _ = require('underscore')._;
|
||||||
var last = {};
|
|
||||||
|
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 = [];
|
var candidates = [];
|
||||||
for(var i=0;i<rawCandidates.length;i++) {
|
for(var i=0;i<rawCandidates.length;i++) {
|
||||||
candidates.push(rawCandidates[i].join(' '));
|
candidates.push(rawCandidates[i].join(' '));
|
||||||
@ -11,20 +87,20 @@ var spelling = function(dbot) {
|
|||||||
var winnerDistance = Infinity;
|
var winnerDistance = Infinity;
|
||||||
|
|
||||||
for(var i=0;i<candidates.length;i++) {
|
for(var i=0;i<candidates.length;i++) {
|
||||||
var distance = String.prototype.distance(correction.toLowerCase(), candidates[i].toLowerCase());
|
var d = distance(correction.toLowerCase(), candidates[i].toLowerCase());
|
||||||
if((distance < winnerDistance) && (distance > 0)) {
|
if((d < winnerDistance) && (d > 0)) {
|
||||||
winner = candidates[i];
|
winner = candidates[i];
|
||||||
winnerDistance = distance;
|
winnerDistance = d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(winnerDistance < Math.ceil(winner.length * 1.33)) {
|
if(winnerDistance < Math.ceil(winner.length * 1.33)) {
|
||||||
if(winner !== correction) {
|
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)) {
|
if (/^.ACTION/.test(fix)) {
|
||||||
fix = fix.replace(/^.ACTION/, '/me');
|
fix = fix.replace(/^.ACTION/, '/me');
|
||||||
}
|
}
|
||||||
last[event.channel.name][candidate] = fix;
|
this.last[event.channel.name][candidate] = fix;
|
||||||
var output = {
|
var output = {
|
||||||
'fix': fix,
|
'fix': fix,
|
||||||
'correcter': event.user,
|
'correcter': event.user,
|
||||||
@ -33,36 +109,31 @@ var spelling = function(dbot) {
|
|||||||
output_callback(output);
|
output_callback(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.bind(this);
|
||||||
|
|
||||||
return {
|
this.listener = function(event) {
|
||||||
'name': 'spelling',
|
|
||||||
'ignorable': true,
|
|
||||||
|
|
||||||
'listener': function(event) {
|
|
||||||
var q = event.message.valMatch(/^(?:\*\*?([\d\w\s']*)|([\d\w\s']*)\*\*?)$/, 3);
|
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);
|
var otherQ = event.message.valMatch(/^([\d\w\s]*)[:|,] (?:\*\*?([\d\w\s']*)|([\d\w\s']*)\*\*?)$/, 4);
|
||||||
if(q) {
|
if(q) {
|
||||||
correct(event, q[1] || q[2], event.user, function (e) {
|
this.internalAPI.correct(event, q[1] || q[2], event.user, function (e) {
|
||||||
event.reply(dbot.t('spelling_self', e));
|
event.reply(dbot.t('spelling_self', e));
|
||||||
});
|
});
|
||||||
} else if(otherQ) {
|
} else if(otherQ) {
|
||||||
correct(event, otherQ[2] || otherQ[3], otherQ[1], function (e) {
|
this.internalAPI.correct(event, otherQ[2] || otherQ[3], otherQ[1], function (e) {
|
||||||
event.reply(dbot.t('spelling_other', e));
|
event.reply(dbot.t('spelling_other', e));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if(last.hasOwnProperty(event.channel.name)) {
|
if(_.has(this.last, event.channel.name)) {
|
||||||
last[event.channel.name][event.user] = event.message;
|
this.last[event.channel.name][event.user] = event.message;
|
||||||
} else {
|
} else {
|
||||||
last[event.channel.name] = { };
|
this.last[event.channel.name] = { };
|
||||||
last[event.channel.name][event.user] = event.message;
|
this.last[event.channel.name][event.user] = event.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}.bind(this);
|
||||||
'on': 'PRIVMSG'
|
this.on = 'PRIVMSG';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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",
|
"webHost": "localhost",
|
||||||
"webPort": 8080
|
"webPort": 9001
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
var express = require('express');
|
var express = require('express'),
|
||||||
|
_ = require('underscore')._,
|
||||||
|
fs = require('fs');
|
||||||
|
|
||||||
var webInterface = function(dbot) {
|
var webInterface = function(dbot) {
|
||||||
var pub = 'public';
|
var pub = 'public';
|
||||||
var app = express.createServer();
|
var app = express();
|
||||||
|
|
||||||
app.use(express.compiler({ src: pub, enable: ['sass'] }));
|
|
||||||
app.use(express.static(pub));
|
app.use(express.static(pub));
|
||||||
app.set('view engine', 'jade');
|
app.set('view engine', 'jade');
|
||||||
|
|
||||||
@ -12,119 +13,33 @@ var webInterface = function(dbot) {
|
|||||||
res.render('index', { 'name': dbot.config.name });
|
res.render('index', { 'name': dbot.config.name });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/connections', function(req, res) {
|
var server = app.listen(dbot.config.web.webPort);
|
||||||
var connections = Object.keys(dbot.instance.connections);
|
|
||||||
res.render('connections', { 'name': dbot.config.name, 'connections': connections });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/channels/:connection', function(req, res) {
|
this.reloadPages = function() {
|
||||||
var connection = req.params.connection;
|
var pages = dbot.pages;
|
||||||
if(dbot.instance.connections.hasOwnProperty(connection)) {
|
for(var p in pages) {
|
||||||
var channels = Object.keys(dbot.instance.connections[connection].channels);
|
if(_.has(pages, p)) {
|
||||||
res.render('channels', { 'name': dbot.config.name, 'connection': connection, 'channels': channels});
|
var func = pages[p];
|
||||||
} else {
|
var mod = func.module;
|
||||||
res.render('error', { 'name': dbot.config.name, 'message': 'No such connection.' });
|
app.get(p, (function(req, resp) {
|
||||||
}
|
// Crazy shim to seperate module views.
|
||||||
});
|
var shim = Object.create(resp);
|
||||||
|
shim.render = (function(view, one, two) {
|
||||||
app.get('/users/:connection/:channel', function(req, res) {
|
// Render with express.js
|
||||||
var connection = req.params.connection;
|
resp.render(this.module + '/' + view, one, two);
|
||||||
var channel = '#' + req.params.channel;
|
}).bind(this);
|
||||||
var connections = dbot.instance.connections;
|
shim.render_core = resp.render;
|
||||||
|
this.call(this.module, req, shim);
|
||||||
if(connections.hasOwnProperty(connection) &&
|
}).bind(func));
|
||||||
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.' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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() } });
|
}.bind(this);
|
||||||
} else {
|
|
||||||
res.render('error', { 'name': dbot.config.name, 'message': 'No polls under that key.' });
|
this.onDestroy = function() {
|
||||||
|
server.close();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(dbot.config.web.webPort);
|
|
||||||
|
|
||||||
return {
|
|
||||||
'name': 'web',
|
|
||||||
'ignorable': false,
|
|
||||||
|
|
||||||
'onDestroy': function() {
|
|
||||||
app.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
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) {
|
var youAre = function(dbot) {
|
||||||
return {
|
this.listener = function(event) {
|
||||||
'name': 'youare',
|
|
||||||
'ignorable': false,
|
|
||||||
|
|
||||||
'listener': function(event) {
|
|
||||||
var key = event.message.valMatch(/(\bis\b|\bare\b)\s+([\w\s\d]*?)(\s+)?(,|\.|\band\b|$)/, 5);
|
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') {
|
if(key && key[2] != "" && Number.prototype.chanceIn(1, 100) && event.user != 'aisbot') {
|
||||||
event.reply(event.user + ': You\'re ' + key[2] + '.');
|
event.reply(event.user + ': You\'re ' + key[2] + '.');
|
||||||
}
|
}
|
||||||
},
|
}.bind(this);
|
||||||
'on': 'PRIVMSG'
|
this.on = 'PRIVMSG';
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.fetch = function(dbot) {
|
exports.fetch = function(dbot) {
|
||||||
return youAre(dbot);
|
return new youAre(dbot);
|
||||||
};
|
};
|
||||||
|
@ -6,19 +6,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
html {
|
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 {
|
body {
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: "Lucida Grande";
|
font-family: "Source Sans Pro", sans-serif;
|
||||||
color: #444;
|
color: #444;
|
||||||
text-shadow: 1px 1px 2px #2B2B2B;
|
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 {
|
h1,h2 {
|
||||||
@ -31,7 +31,6 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div#page {
|
div#page {
|
||||||
width: 90%;
|
|
||||||
margin: 0 auto 0 auto;
|
margin: 0 auto 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,8 +55,7 @@ div#title a {
|
|||||||
|
|
||||||
div#main {
|
div#main {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 15px 5px;
|
padding: 10px 5px;
|
||||||
margin: 0px;
|
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
background: #FFF;
|
background: #FFF;
|
||||||
@ -172,3 +170,58 @@ li.option-votes {
|
|||||||
|
|
||||||
box-shadow: inset 0px 0px 3px #444;
|
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;
|
||||||
|
}
|
||||||
|
267
run.js
267
run.js
@ -1,47 +1,54 @@
|
|||||||
var fs = require('fs');
|
var fs = require('fs'),
|
||||||
var timers = require('./timer');
|
_ = require('underscore')._,
|
||||||
var jsbot = require('./jsbot/jsbot');
|
jsbot = require('./jsbot/jsbot');
|
||||||
require('./snippets');
|
require('./snippets');
|
||||||
|
|
||||||
var DBot = function(timers) {
|
var DBot = function() {
|
||||||
// Load external files
|
|
||||||
var requiredConfigKeys = [ 'name', 'servers', 'admins', 'moduleNames', 'language' ];
|
/*** Load the DB ***/
|
||||||
|
|
||||||
|
if(fs.existsSync('db.json')) {
|
||||||
try {
|
try {
|
||||||
this.config = JSON.parse(fs.readFileSync('config.json', 'utf-8'));
|
this.db = JSON.parse(fs.readFileSync('db.json', 'utf-8'));
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log('Config file is screwed up. Attempting to load defaults.');
|
console.log('Error loading db.json. Stopping: ' + err);
|
||||||
try {
|
|
||||||
this.config = JSON.parse(fs.readFileSync('config.json.sample', 'utf-8'));
|
|
||||||
} catch(err) {
|
|
||||||
console.log('Error loading sample config. Bugger off. Stopping.');
|
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
requiredConfigKeys.each(function(key) {
|
this.db = {};
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if(!_.has(this.db, 'config')) {
|
||||||
if(!this.db) { // If it wasn't empty
|
this.db.config = {};
|
||||||
this.db = JSON.parse(rawDB);
|
|
||||||
}
|
}
|
||||||
} catch(err) {
|
|
||||||
console.log('Syntax error in db.json. Stopping: ' + err);
|
/*** Load the Config ***/
|
||||||
|
|
||||||
|
if(!fs.existsSync('config.json')) {
|
||||||
|
console.log('Error: config.json file does not exist. Stopping');
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Strings file
|
this.config = _.clone(this.db.config);
|
||||||
|
try {
|
||||||
|
_.defaults(this.config, JSON.parse(fs.readFileSync('config.json', 'utf-8')));
|
||||||
|
} catch(err) {
|
||||||
|
console.log('Config file is invalid. Stopping: ' + err);
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8'));
|
this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8'));
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
@ -51,15 +58,13 @@ var DBot = function(timers) {
|
|||||||
|
|
||||||
// Initialise run-time resources
|
// Initialise run-time resources
|
||||||
this.usage = {};
|
this.usage = {};
|
||||||
|
this.status = {};
|
||||||
this.sessionData = {};
|
this.sessionData = {};
|
||||||
this.timers = timers.create();
|
|
||||||
|
|
||||||
// Populate bot properties with config data
|
// Populate bot properties with config data
|
||||||
// Create JSBot and connect to each server
|
// Create JSBot and connect to each server
|
||||||
this.instance = jsbot.createJSBot(this.config.name);
|
this.instance = jsbot.createJSBot(this.config.name);
|
||||||
for(var name in this.config.servers) {
|
_.each(this.config.servers, function(server, name) {
|
||||||
if(this.config.servers.hasOwnProperty(name)) {
|
|
||||||
var server = this.config.servers[name];
|
|
||||||
this.instance.addConnection(name, server.server, server.port,
|
this.instance.addConnection(name, server.server, server.port,
|
||||||
this.config.admin, function(event) {
|
this.config.admin, function(event) {
|
||||||
var server = this.config.servers[event.server];
|
var server = this.config.servers[event.server];
|
||||||
@ -67,8 +72,7 @@ var DBot = function(timers) {
|
|||||||
this.instance.join(event, server.channels[i]);
|
this.instance.join(event, server.channels[i]);
|
||||||
}
|
}
|
||||||
}.bind(this), server.nickserv, server.password);
|
}.bind(this), server.nickserv, server.password);
|
||||||
}
|
}, this);
|
||||||
}
|
|
||||||
|
|
||||||
// Load the modules and connect to the server
|
// Load the modules and connect to the server
|
||||||
this.reloadModules();
|
this.reloadModules();
|
||||||
@ -83,9 +87,9 @@ DBot.prototype.say = function(server, channel, message) {
|
|||||||
// Format given stored string in config language
|
// Format given stored string in config language
|
||||||
DBot.prototype.t = function(string, formatData) {
|
DBot.prototype.t = function(string, formatData) {
|
||||||
var formattedString;
|
var formattedString;
|
||||||
if(this.strings.hasOwnProperty(string)) {
|
if(_.has(this.strings, string)) {
|
||||||
var lang = this.config.language;
|
var lang = this.config.language;
|
||||||
if(!this.strings[string].hasOwnProperty(lang)) {
|
if(!_.has(this.strings[string], lang)) {
|
||||||
lang = "english";
|
lang = "english";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +107,7 @@ DBot.prototype.t = function(string, formatData) {
|
|||||||
|
|
||||||
// Save the database file
|
// Save the database file
|
||||||
DBot.prototype.save = function() {
|
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.
|
// Hot-reload module files.
|
||||||
@ -117,11 +121,16 @@ DBot.prototype.reloadModules = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.rawModules = [];
|
this.rawModules = [];
|
||||||
this.modules = [];
|
this.pages = {};
|
||||||
|
this.status = {};
|
||||||
|
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();
|
|
||||||
|
// Load config changes
|
||||||
|
_.extend(this.config, this.db.config);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8'));
|
this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8'));
|
||||||
@ -150,88 +159,150 @@ DBot.prototype.reloadModules = function() {
|
|||||||
delete require.cache[cacheKey];
|
delete require.cache[cacheKey];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Load the module config data
|
var webKey = require.resolve(moduleDir + 'web');
|
||||||
|
} catch(err) {
|
||||||
|
}
|
||||||
|
if(webKey) {
|
||||||
|
delete require.cache[webKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status[name] = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var config = JSON.parse(fs.readFileSync(moduleDir + 'config.json', 'utf-8'))
|
// Load the module config data
|
||||||
this.config[name] = config;
|
var config = {};
|
||||||
for(var i=0;i<config.dbKeys.length;i++) {
|
|
||||||
if(!this.db.hasOwnProperty(config.dbKeys[i])) {
|
if(_.has(this.db.config, name)) {
|
||||||
this.db[config.dbKeys[i]] = {};
|
config = _.clone(this.db.config[name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
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) {
|
} catch(err) {
|
||||||
// Invalid or no config data
|
// 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
|
// Load the module itself
|
||||||
var rawModule = require(moduleDir + name);
|
var rawModule = require(moduleDir + name);
|
||||||
var module = rawModule.fetch(this);
|
var module = rawModule.fetch(this);
|
||||||
|
module.name = name;
|
||||||
this.rawModules.push(rawModule);
|
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) {
|
if(module.listener) {
|
||||||
this.instance.addListener(module.on, module.name, module.listener);
|
if(!_.isArray(module.on)) {
|
||||||
|
module.on = [ module.on ];
|
||||||
|
}
|
||||||
|
_.each(module.on, function(on) {
|
||||||
|
this.instance.addListener(on, module.name, module.listener);
|
||||||
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(module.onLoad) {
|
// Load string data for the module
|
||||||
module.onLoad();
|
_.each([ 'usage', 'strings' ], function(property) {
|
||||||
}
|
var propertyData = {};
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the module usage data
|
|
||||||
try {
|
try {
|
||||||
var usage = JSON.parse(fs.readFileSync(moduleDir + 'usage.json', 'utf-8'));
|
propertyData = JSON.parse(fs.readFileSync(moduleDir + property + '.json', 'utf-8'));
|
||||||
for(key in usage) {
|
} catch(err) {};
|
||||||
if(usage.hasOwnProperty(key)) {
|
_.extend(this[property], propertyData);
|
||||||
if(this.usage.hasOwnProperty(key)) {
|
}, this);
|
||||||
console.log('Usage key clash for ' + key + ' in ' + name);
|
|
||||||
} else {
|
// Provide toString for module name
|
||||||
this.usage[key] = usage[key];
|
module.toString = function() {
|
||||||
}
|
return this.name;
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
// Invalid or no usage info
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the module string data
|
this.modules[module.name] = module;
|
||||||
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);
|
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log(this.t('module_load_error', {'moduleName': name}));
|
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));
|
}.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();
|
this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
DBot.prototype.cleanNick = function(key) {
|
DBot.prototype.cleanNick = function(key) {
|
||||||
key = key.toLowerCase();
|
key = key.toLowerCase();
|
||||||
while(key.endsWith("_")) {
|
while(key.endsWith("_")) {
|
||||||
if(this.db.quoteArrs.hasOwnProperty(key)) {
|
if(_.has(this.db.quoteArrs, key)) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
key = key.substring(0, key.length-1);
|
key = key.substring(0, key.length-1);
|
||||||
@ -239,4 +310,4 @@ DBot.prototype.cleanNick = function(key) {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
new DBot(timers);
|
new DBot();
|
||||||
|
96
snippets.js
96
snippets.js
@ -35,19 +35,6 @@ Array.prototype.sum = function() {
|
|||||||
return sum;
|
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() {
|
Array.prototype.uniq = function() {
|
||||||
var hash = {}
|
var hash = {}
|
||||||
var result = [];
|
var result = [];
|
||||||
@ -79,66 +66,6 @@ String.prototype.startsWith = function(needle) {
|
|||||||
return needle === this.slice(0, needle.length);
|
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
|
String.prototype.format = function() { // format takes either multiple indexed arguments, or a single object, whose keys/values will be used
|
||||||
var targetStr = this;
|
var targetStr = this;
|
||||||
var replacements = [].splice.call(arguments, 0);
|
var replacements = [].splice.call(arguments, 0);
|
||||||
@ -204,7 +131,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 ***/
|
||||||
|
|
||||||
@ -253,3 +188,18 @@ RegExp.prototype.url_regex = function() {
|
|||||||
);
|
);
|
||||||
return reg;
|
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,3 +1,6 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
#modulelinks
|
#modulelinks
|
||||||
a.module(href='/quotes') Quotes
|
a.module(href='/quotes') Quotes
|
||||||
a.module(href='/polls') Polls
|
a.module(href='/polls') Polls
|
||||||
|
@ -2,12 +2,18 @@
|
|||||||
html(lang='en')
|
html(lang='en')
|
||||||
head
|
head
|
||||||
meta(charset='utf-8')
|
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')
|
link(rel='stylesheet', type='text/css', href='/styles.css')
|
||||||
title #{name} web interface
|
title #{name} web interface
|
||||||
body
|
body
|
||||||
|
div.container
|
||||||
div#page
|
div#page
|
||||||
div#title
|
div#title
|
||||||
a(href='/') #{name} web interface
|
a(href='/') #{name} web interface
|
||||||
div#main
|
div.container#main
|
||||||
!{body}
|
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")
|
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…
Reference in New Issue
Block a user