diff --git a/.gitmodules b/.gitmodules index d47e162..cf2f023 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "modules/github"] path = modules/github url = git://github.com/zuzak/dbot-github.git -[submodule "modules/stats"] - path = modules/stats - url = git@github.com:SamStudio8/dbot-stats.git diff --git a/README.md b/README.md index 99b28be..ac54895 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,11 @@ given I started it rather a long time after I began development of the project. Please don't judge me too harshly for this as I am, in fact, mildly allergic to writing documentation. -Requirements: +## Getting Started -- Node JS -- [JSBot](http://github.com/reality/JSBot "JSBot"), a Javascript library which - handles the IRC protocol -- Underscore JS library -- Various modules have their own requirements also. +You can install DBot's dependencies, configure and run the bot for the first +time with the following command: -### External Modules - -JSBot and externally developed modules can be imported by running the following -commands in the cloned repository: - - git submodule init - git submodule update +``` +sh install.sh +``` diff --git a/database.js b/database.js new file mode 100644 index 0000000..3010140 --- /dev/null +++ b/database.js @@ -0,0 +1,33 @@ +var databank = require('databank'), + Databank = databank.Databank, + DatabankObject = databank.DatabankObject, + _ = require('underscore')._; + +/** + * Multiplex databank objects + */ +var DatabaseDriver = function(config) { + this.config = config; + this.databanks = {}; +}; + +/** + * Connect to or create a new DataBank + */ +DatabaseDriver.prototype.createDB = function(name, driver, schema, callback) { + var params = { 'schema': schema }; + + if(driver == 'redis' && _.has(this.config, 'redisPort')) params.port = this.config.redisPort; + if(driver == 'disk') params.dir = 'db'; + + this.databanks[name] = Databank.get(driver, params); + this.databanks[name].connect({}, function(err) { + if(err) { + console.log('Didn\'t manage to connect to the data source - ' + err); + } else { + callback(this.databanks[name]); + } + }.bind(this)); +}; + +exports.DatabaseDriver = DatabaseDriver; diff --git a/install.sh b/install.sh index 9c1ae68..d9cb7fc 100755 --- a/install.sh +++ b/install.sh @@ -14,7 +14,7 @@ then exit 1 fi -npm install wordnik ent underscore request sandbox express moment jade@0.25 +npm install wordnik node-uuid underscore request sandbox express moment jade databank databank-redis ent cd public/ wget http://twitter.github.com/bootstrap/assets/bootstrap.zip diff --git a/jsbot b/jsbot index 11cbb75..bb1e72b 160000 --- a/jsbot +++ b/jsbot @@ -1 +1 @@ -Subproject commit 11cbb75b504fbb703450a2f8c938d5a81680109c +Subproject commit bb1e72b173a136816287bc8d90509cc38a0986e3 diff --git a/modules/admin/README.md b/modules/admin/README.md index 0a0583c..6aff5a4 100644 --- a/modules/admin/README.md +++ b/modules/admin/README.md @@ -26,6 +26,15 @@ channel, it will attempt to give the caller ops in the current channel. Perform a git pull, and then execute the 'reload' command. Saves a lot of time updating! +#### version [module] +Shows the git version of the currently loaded revision of DBot. If module is +provided, it will attempt to get the revision of the module (this is only useful +for submodules). + +#### status [module] +Show the recorded status for a given module, this is helpful for debugging +issues when loading or for checking if a module is loaded. + #### 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 @@ -46,12 +55,18 @@ loaded by the standard DBot process. 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. +#### setconfig [path] [value] +Set a config value at path to be a certain value persistently. For example, if +you want to change the web module to listen on port 9001, you can run _setconfig +web.webPort 9001_. -#### 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. +#### pushconfig [path] [value] +Push a new value to an existing config array. For example, if you want to add +the user 'batman62' to the DBot moderators, you can run _pushconfig moderators +batman62_. + +#### showconfig [path] +Use this to explore and view the DBot configuration. If called without a path, +it will display the config keys in the root; if the path is a subkey, it will +show all config keys under that key. If you give it an actual key, it'll show you +the currently effective config value. diff --git a/modules/admin/admin.js b/modules/admin/admin.js index f1edaf3..74e4af7 100644 --- a/modules/admin/admin.js +++ b/modules/admin/admin.js @@ -1,12 +1,56 @@ /** * Module Name: Admin * 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. */ var fs = require('fs'), _ = require('underscore')._; var admin = function(dbot) { + this.internalAPI = { + 'getCurrentConfig': function(configKey, callback) { + var configPath = dbot.config; + configKey = configKey.split('.'); + + for(var i=0;i " + newOption); - configPath['user'][configKey] = newOption; - dbot.reloadModules(); + this.internalAPI.setConfig(configPath, newOption, function(err) { + event.reply(configPath + ": " + config + " -> " + newOption); + }); + }.bind(this)); } else { event.reply(dbot.t("config_lock")); } }, '~pushconfig': function(event) { - var configPathString = event.params[1], - configKey = _.last(configPathString.split('.')), - newOption = event.params[2]; + var configPath = event.input[1], + newOption = event.input[2]; - if(!_.include(noChangeConfig, configKey)) { - var configPath = getCurrentConfig(configPathString); - if(configPath == false || _.isUndefined(configPath.value)) { - event.reply(dbot.t("no_config_key")); - return; - } - var currentArray = configPath.value; - - if(!_.isArray(currentArray)) { - event.reply(dbot.t("config_array",{"alternate": "setconfig"})); - return; - } - - event.reply(configPathString + ": " + currentArray + " << " + newOption); - currentArray.push(newOption); - dbot.reloadModules(); + if(!_.include(noChangeConfig, configPath)) { + this.internalAPI.getCurrentConfig(configPath, function(config) { + if(config !== null) { + if(_.isArray(config)) { + event.reply(configPath + ": " + config + " << " + newOption); + config.push(newOption); + this.internalAPI.setConfig(configPath, config, function(err) {}); + } else { + event.reply(dbot.t("config_array", { "alternate": "setconfig" })); + } + } else { + event.reply(dbot.t("no_config_key", { 'path': configPath })); + } + }.bind(this)); + } else { + event.reply(dbot.t("config_lock")); } }, '~showconfig': function(event) { - var configPathString = event.params[1]; - var configPath = getCurrentConfig(configPathString); - - if(configPathString) { - var configKey = _.last(configPathString.split('.')); - if(!configKey) { - event.reply(dbot.t("no_config_path")); - return; - } + var configPath = event.params[1]; + if(configPath) { + this.internalAPI.getCurrentConfig(configPath, function(config) { + if(config !== null) { + if(_.isArray(config)) { + event.reply(dbot.t("config_keys_location", { + "path": configPath, + "value": config + })); + } else if(_.isObject(config)) { + event.reply(dbot.t("config_keys_location", { + "path": configPath, + "value": _.keys(config) + })); + } else { + event.reply(dbot.t("config_keys_location", { + "path": configPath, + "value": config + })); + } + } else { + event.reply(dbot.t("no_config_key", {'path': configPath})); - if(_.isArray(configPath.value)) { - event.reply(configKey + ': ' + configPath.value); - } else if(_.isObject(configPath.value)) { - event.reply(dbot.t("config_keys_location",{"path":configPathString,"value":Object.keys(configPath.value)})); - } else { - event.reply(configKey + ': ' + configPath.value); - } + configPath = configPath.split('.'); + if(_.has(dbot.config.modules, configPath[0])) { + configPath.splice(0, 0, 'modules'); + } else { + configPath.pop(); + } + + event.params[1] = configPath.join('.'); + this.commands['~showconfig'](event); + } + }.bind(this)); } else { - event.reply(dbot.t("config_keys_location",{"path":"root","value":Object.keys(configPath['default'])})); + event.reply(dbot.t("config_keys_location", { + "path": "root", + "value": _.keys(dbot.config) + })); } + }, + + '~savemodules': function(event) { + fs.readFile('config.json', 'utf-8', function(err, config) { + config = JSON.parse(config); + config.moduleNames = _.keys(dbot.modules); + fs.writeFile('config.json', JSON.stringify(config, null, ' '), function() { + event.reply(dbot.t('modules_saved', { 'modules': _.keys(dbot.modules) })); + }); + }); } }; @@ -281,6 +292,9 @@ var commands = function(dbot) { commands['~opme'].access = 'moderator'; commands['~say'].access = 'moderator'; + commands['~pushconfig'].regex = [/~pushconfig ([^ ]+) ([^ ]+)/, 3]; + commands['~setconfig'].regex = [/~setconfig ([^ ]+) ([^ ]+)/, 3]; + return commands; }; diff --git a/modules/admin/config.json b/modules/admin/config.json index f87d4b9..3a85186 100644 --- a/modules/admin/config.json +++ b/modules/admin/config.json @@ -1,5 +1,5 @@ { "ignorable": false, - "dependencies": [ "command" ], - "help": "http://github.com/reality/depressionbot/blob/master/modules/admin/README.md" + "dbType": "redis", + "dependencies": [ "command" ] } diff --git a/modules/admin/strings.json b/modules/admin/strings.json index 73241d1..d130c0c 100644 --- a/modules/admin/strings.json +++ b/modules/admin/strings.json @@ -5,7 +5,8 @@ "na'vi": "fpxäkìm {channel}(nemfa)", "cy": "Wedi ymuno {channel}", "nl": "{channel} binnengekomen", - "de": "{channel} beigetreten" + "de": "{channel} beigetreten", + "fr": "{channel} rejoint" }, "part": { "en": "Left {channel}", @@ -13,7 +14,8 @@ "na'vi": "Hum {channel}", "cy": "Wedi gadael {channel}", "nl": "{channel} verlaten", - "de": "{channel} verlassen" + "de": "{channel} verlassen", + "fr": "{channel} quitté" }, "gpull": { "en": "Git pulled that shit.", @@ -21,7 +23,8 @@ "na'vi": "Gìtìl fì'uti stamarsìm.", "cy": "Wedi tynnu git yr cach na i gyd", "nl": "Git heeft die zooi binnengehaald.", - "de": "Git hat es gezogen" + "de": "Git hat es gezogen", + "fr": "Git a pullé cette merde" }, "reload": { "en": "Reloaded that shit.", @@ -29,7 +32,8 @@ "na'vi": "Oel fìuti stìyeftxaw.", "cy": "Ail-lwytho'r cach na", "nl": "Die zooi opnieuw geladen.", - "de": "Neu geladen" + "de": "Neu geladen", + "fr": "Bordel rechargé" }, "load_module": { "en": "Loaded new module: {moduleName}", @@ -37,7 +41,8 @@ "na'vi": "Oel {moduleName}it amip stìyeftxaw.", "cy": "Wedi llwytho modiwl newydd: {moduleName}", "nl": "Nieuwe module geladen: {moduleName}", - "de": "Neues Modul geladen: {moduleName}" + "de": "Neues Modul geladen: {moduleName}", + "fr": "Nouveau module chargé : {moduleName}" }, "unload_module": { "en": "Turned off module: {moduleName}", @@ -45,7 +50,8 @@ "na'vi": "Oel {moduleName} tswìya'.", "cy": "Wedi troi ffwrdd y modiwl: {moduleName}", "nl": "Module uitgeschakeld: {moduleName}", - "de": "Modul ausgeschaltet: {moduleName}" + "de": "Modul ausgeschaltet: {moduleName}", + "fr": "Module déchargé : {moduleName}" }, "unload_error": { "en": "{moduleName} isn't loaded. Idiot.", @@ -53,7 +59,8 @@ "na'vi": "Oel {moduleName}it omum. Nga skxawng lu.", "cy": "Di {moduleName} ddim wedi llwytho. Twpsyn", "nl": "{moduleName} is niet geladen. Idioot.", - "de": "{moduleName} ist nicht geladen, du Idiot." + "de": "{moduleName} ist nicht geladen, du Idiot.", + "fr": "{moduleName} n'est pas chargé. Idiot." }, "banned": { "en": "{user} banned from {command}", @@ -61,7 +68,8 @@ "na'vi": "{command}ìri {user} ke tung.", "cy": "{user} wedi ei gohurio o {command}", "nl": "{user} mag {command} niet meer gebruiken", - "de": "{user} wurde von {command} gebannt" + "de": "{user} wurde von {command} gebannt", + "fr": "{user} a été interdit d'utiliser {command}" }, "unbanned": { "en": "{user} unbanned from {command}", @@ -69,7 +77,8 @@ "na'vi": "{command}ìri {user} tung set.", "cy": "{user} wedi ei dad-wahardd o {command}", "nl": "{user} mag {command} weer gebruiken", - "de": "{user} wurde von {command} entbannt" + "de": "{user} wurde von {command} entbannt", + "fr": "{user} peut de nouveau utiliser {command}" }, "unban_error": { "en": "{user} wasn't banned from that command, fool.", @@ -77,7 +86,8 @@ "na'vi": "{user} fìtsu'oti tamung srekrr, nga skxawng lu.", "cy": "Nid oedd {user} wedi ei wahardd o'r gorchymyn yna, twpsyn", "nl": "{user} mag dat commando sowieso al gebruiken, mafketel.", - "de": "{user} wurde nicht von {command} gebannt, du Trottel" + "de": "{user} wurde nicht von {command} gebannt, du Trottel", + "fr": "{user} n'a pas été interdit d'utiliser cette commande, imbécile." }, "qlock": { "en": "Locked quote category: {category}", @@ -85,84 +95,107 @@ "na'vi": "{category}ìri oel 'upxareti fmoli", "cy": "Categori wedi cloi: {category}", "nl": "Quote categorie vergrendeld: {category}", - "de": "Zitat-Kategorie geschlossen: {category}" + "de": "Zitat-Kategorie geschlossen: {category}", + "fr": "Catégorie de citations verrouillée : {category}" }, "already_in_channel": { "en": "I'm already in {channel}", "na'vi": "Oel {channel}it tok li", "cy": "Rydw i eisoes yn {channel}", "nl": "Ik ben al in {channel}", - "de": "Ich bin schon in {channel}" + "de": "Ich bin schon in {channel}", + "fr": "Je suis déjà dans {channel}" }, "not_in_channel": { "en": "I'm not in {channel}", "na'vi": "Oel {channel}it ke tok", "cy": "Rydw i ddim yn {channel}", "nl": "Ik ben niet aanwezig in {channel}", - "de": "Ich bin noch nicht in {channel}" + "de": "Ich bin noch nicht in {channel}", + "fr": "Je ne suis pas dans {channel}" }, "already_loaded_web": { "en": "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)", "cy": "PAM ALLA I DDIM YN LWYTHO POB Y WE? (We eisoes yn lwytho)", "nl": "AL DIT WEB WORDT ME TOCH EEN BEETJE TE VEEL! (web is al geladen)", - "de": "WARUM KANN DAS NICHT GELADEN WERDEN? (bereits geladen)" + "de": "WARUM KANN DAS NICHT GELADEN WERDEN? (bereits geladen)", + "fr": "POURQUOI EST-CE QUE JE PEUX PAS CHARGER TOUT CE WEB? (web déjà chargé)" }, "already_loaded": { "en": "{moduleName} is already loaded.", "na'vi": "Oel omum teri {moduleName}it li.", "cy": "{moduleName} eisoes yn lwytho", "nl": "{moduleName} is al geladen.", - "de": "{moduleName} ist bereits geladen." + "de": "{moduleName} ist bereits geladen.", + "fr": "{moduleName} est déjà chargé." }, "no_version": { "en": "No version information or queried module not loaded.", "cy": "Dim gwybodaeth fersiwn neu modiwl holodd dim yn lwytho", - "de": "Keine Informationen verfügbar oder gewünschtes Modul wurde noch nicht geladen." + "de": "Keine Informationen verfügbar oder gewünschtes Modul wurde noch nicht geladen.", + "fr": "Aucune information de version ou module demandé non chargé." }, "status_good": { "en": "{module} status: Shit looks good", "cy": "{module} statws: Cachu yn edrych yn dda", - "de": "Sieht gut aus" + "de": "Sieht gut aus", + "fr": "Statut de {module} : Cette merde tourne bien" }, "status_bad": { "en": "{module} status: Failed to load: {reason}", "cy": "{module} statws: Methu i lwytho: {reason}", - "de": "{module} Status: Konnte nicht geladen werden: {reason}" + "de": "{module} Status: Konnte nicht geladen werden: {reason}", + "fr": "Statut de {module} : échec de chargement : {reason}" }, "status_unloaded": { "en": "Either that module wasn't on the roster or shit is totally fucked.", "cy": "Naill ai heb fod modiwl oedd ar y rhestr, neu cachu yn gwbl torrodd", - "de": "Entweder ist das Modul nicht auf der Liste oder die Kacke ist am dampfen" + "de": "Entweder ist das Modul nicht auf der Liste oder die Kacke ist am dampfen", + "fr": "Soit ce module n'est pas sur la liste, soit tout est complètement niqué" }, "load_failed": { "en": "Failed to load {module}. See 'status {module}'.", "cy": "Methu i lwytho {module}. Gwelwch 'status {module}'.", - "de": "Konnte {module} nicht laden. Siehe 'status {module}'." + "de": "Konnte {module} nicht laden. Siehe 'status {module}'.", + "fr": "Echec du chargement de {module}. Voir 'status {module}'." }, "no_config_key": { - "en": "Config key doesn't exist bro", - "cy": "Nid yw allwedd cyfluniad yn bodoli, fy mrawd", - "de": "Einstellung existiert nicht, Bruder" + "en": "{path} doesn't exist bro", + "cy": "{path} cyfluniad yn bodoli, fy mrawd", + "de": "{path} existiert nicht, Bruder", + "fr": "{path} n'existe pas, fréro" }, "config_array": { "en": "Config option is an array. Try '{alternate}'.", "cy": "Opsiwn cyfluniad ydy'r amrywiaeth. Rhowch gynnig ar '{alternate}'.", - "de": "Einstellung ist ein Array, probiere '{alternate}' aus." + "de": "Einstellung ist ein Array, probiere '{alternate}' aus.", + "fr": "L'option de configuration est un array. Essaye '{alternate}'." }, "config_lock": { "en": "This config option cannot be altered while the bot is running.", "cy": "Ni all yr opsiwn cyfluniad yn cael ei newid tra bod y bot yn rhedeg.", - "de": "Diese Option kann während der Benutzung des Bots nicht verändert werden" + "de": "Diese Option kann während der Benutzung des Bots nicht verändert werden", + "fr": "Cette option de configuration ne peut pas être changée pendant que le bot est activé." }, "no_config_path": { "en": "Config path doesn't exist bro", "cy": "Nid yw llwybr cyfluniad yn bodoli, fy mrawd", - "de": "Konfigurationspfad nicht vorhanden, Bruder" + "de": "Konfigurationspfad nicht vorhanden, Bruder", + "fr": "Le chemin de configuration n'existe pas, fréro" + }, + "new_config_key": { + "en": "Warning: Creating new config key: {key}.", + "fr": "Attention : création d'une nouvelle clé de configuration : {key}." }, "config_keys_location": { "en": "Config keys in {path}: {value}", "cy": "Allweddi cyfluniad yn {path}: {value}", - "de": "Die Konfiguration in {path}: {value}" + "de": "Die Konfiguration in {path}: {value}", + "fr": "Clés de configuration dans {path}: {value}" + }, + "modules_saved": { + "en": "Currently loaded modules now default: {modules}", + "fr": "Les modules actuellement chargés sont maintenant chargés par défaut : {modules}" } } diff --git a/modules/command/README.md b/modules/command/README.md index 619fa04..e0115bc 100644 --- a/modules/command/README.md +++ b/modules/command/README.md @@ -21,6 +21,17 @@ Command flow: This is the only module which is force loaded, even if it's not specified in the configuration file. +### Config + +#### useNickserv: false +Use the nickserv module to ensure a user is logged into their account before +running any elevated commands. Note you will still have to load and configure +the nickserv module yourself. + +#### accessOutput: false +Show a message to a user if they attempt to run a command they don't have the +access level for. + ### Commands #### ~usage [command] diff --git a/modules/command/api.js b/modules/command/api.js index 6af230b..c68d716 100644 --- a/modules/command/api.js +++ b/modules/command/api.js @@ -2,22 +2,10 @@ var _ = require('underscore')._; var api = function(dbot) { return { - 'isBanned': function(user, command) { - var banned = false; - if(_.has(dbot.db.bans, user)) { - if(_.include(dbot.db.bans[user], command) || - _.include(dbot.db.bans[user], dbot.commands[command].module) || - _.include(dbot.db.bans[user], '*')) { - banned = true; - } - } - return banned; - }, - /** * Does the user have the correct access level to use the command? */ - 'hasAccess': function(server, user, command, callback) { + 'hasAccess': function(user, command, callback) { var accessNeeded = dbot.commands[command].access; if(accessNeeded == 'admin' || accessNeeded == 'moderator' || accessNeeded == 'power_user') { @@ -25,11 +13,11 @@ var api = function(dbot) { if(accessNeeded == 'moderator') allowedNicks = _.union(allowedNicks, dbot.config.moderators); if(accessNeeded == 'power_user') allowedNicks = _.union(allowedNicks, dbot.config.power_users); - if(!_.include(allowedNicks, user)) { + if(!_.include(allowedNicks, user.primaryNick)) { callback(false); } else { if(_.has(dbot.modules, 'nickserv') && this.config.useNickserv == true) { - dbot.api.nickserv.auth(server, user, function(result) { + dbot.api.nickserv.auth(user.server, user.currentNick, function(result) { callback(result); }); } else { @@ -41,16 +29,6 @@ var api = function(dbot) { } }, - /** - * 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) || - _.include(dbot.db.ignores[item], '*'))); - }, - /** * Apply Regex to event message, store result. Return false if it doesn't * apply. @@ -76,16 +54,6 @@ var api = function(dbot) { 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); - } } }; }; diff --git a/modules/command/command.js b/modules/command/command.js index cab3439..13b3654 100644 --- a/modules/command/command.js +++ b/modules/command/command.js @@ -5,9 +5,8 @@ * ignoring that command. */ var _ = require('underscore')._; + var command = function(dbot) { - this.dbot = dbot; - /** * Run the appropriate command given the input. */ @@ -20,24 +19,23 @@ var command = function(dbot) { return; } } - - if(this.api.isBanned(event.user, commandName)) { - event.reply(dbot.t('command_ban', {'user': event.user})); - } else { - this.api.hasAccess(event.server, event.user, commandName, function(result) { - if(result) { - if(!this.api.isIgnoring(event.user, commandName) && - !this.api.isIgnoring(event.channel, commandName) && - dbot.commands[commandName].disabled !== true) { + + this.api.hasAccess(event.rUser, commandName, function(hasAccess) { + dbot.api.ignore.isUserIgnoring(event.rUser, commandName, function(isIgnoring) { + dbot.api.ignore.isUserBanned(event.rUser, commandName, function(isBanned) { + if(isBanned) { + if(this.config.banOutput && commandName != '~') { + event.reply(dbot.t('command_ban', {'user': event.user})); + } + } else if(!hasAccess) { + if(this.config.accessOutput) { + event.reply(dbot.t('access_denied', { 'user': event.user })); + } + } else if(!isIgnoring && !dbot.commands[commandName].disabled) { 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) { var stack = err.stack.split('\n').slice(1, dbot.config.debugLevel + 1); @@ -51,7 +49,7 @@ var command = function(dbot) { }); } } - dbot.api.event.emit('command', [ event ]); + if(!_.include(['~reload', '~load', '~unload'], commandName)) dbot.api.event.emit('command', [ event ]); dbot.save(); } else { if(commandName !== '~') { @@ -63,9 +61,9 @@ var command = function(dbot) { } } } - } + }.bind(this)); }.bind(this)); - } + }.bind(this)); }.bind(this); this.on = 'PRIVMSG'; }; diff --git a/modules/command/commands.js b/modules/command/commands.js index 5bf355b..6fe31f8 100644 --- a/modules/command/commands.js +++ b/modules/command/commands.js @@ -48,8 +48,8 @@ var commands = function(dbot) { } else { var helpLink = dbot.config.repoRoot + 'blob/master/modules/' + moduleName + '/README.md'; - if(dbot.config[moduleName].help) { - helpLink = dbot.config[moduleName].help; + if(dbot.config.modules[moduleName].help) { + helpLink = dbot.config.modules[moduleName].help; } // TODO: Check it exists diff --git a/modules/command/config.json b/modules/command/config.json index b27be88..9374a66 100644 --- a/modules/command/config.json +++ b/modules/command/config.json @@ -1,6 +1,7 @@ { "ignorable": false, - "help": "http://github.com/reality/depressionbot/blob/master/modules/command/README.md", + "dependencies": [ "event", "ignore", "users" ], "useNickserv": false, - "dbKeys": [ "ignores", "bans" ] + "accessOutput": false, + "banOutput": false } diff --git a/modules/command/strings.json b/modules/command/strings.json index 3e6d5a0..d314848 100644 --- a/modules/command/strings.json +++ b/modules/command/strings.json @@ -1,11 +1,12 @@ { "command_ban": { "en": "{user} is banned from using this command. Commence incineration.", - "es": "{user} está prohibido de usar esta instrucción. Comenzar incineración.", + "es": "{user} está prohibido de usar esta instrucción. urrently loaded modules now default: {modules}.", "na'vi": "Tsu'ori {user} ke tung. Nga skxawng lu.", "cy": "Mae {user} wedi ei gohurio gan ddefnyddio'r gorchymun yma. Cychwyn orfflosgiad", "nl": "{user} mag dit commando niet meer gebruiken. Bereid het verbrandingsritueel voor.", - "de": "{user} wurde von diesem Befehl gebannt. Verbrennung einleiten" + "de": "{user} wurde von diesem Befehl gebannt. Verbrennung einleiten", + "fr": "{user} est interdit d'utiliser cette commande. Commencer l'incinération." }, "syntax_error": { "en": "Invalid syntax. Initiate incineration.", @@ -13,43 +14,54 @@ "na'vi": "Ngeyä pamrel keyawr lu. Nga skxawng lu.", "cy": "Cystrawen annilys. Cychwyn orfflosgiad", "nl": "Ongeldige syntax. Bereid het verbrandingsritueel voor.", - "de": "Syntax ungültig. Verbrennung einleiten" + "de": "Syntax ungültig. Verbrennung einleiten", + "fr": "Syntaxe invalide. Initier l'incinération." }, "usage": { "en": "Usage for {command}: {usage}.", "na'vi": "Nga tsun sivar ìlä {command}: {usage}.", "cy": "Defnydd o {command}: {usage}.", "nl": "{command} wordt op de volgende manier gebruikt: {usage}.", - "de": "Benutzung von {command}: [usage}." + "de": "Benutzung von {command}: [usage}.", + "fr": "Utilisation de {command}: {usage}." }, "no_usage_info": { "en": "No usage information found for {command}.", "na'vi": "Oel ke tsun sivar {comamnd}it", "cy": "Ni chanfuwyd gwybodaeth am ddefnydd o {command}", "nl": "Geen gebruiksinformatie gevonden voor {command}.", - "de": "Keine Gebrauchsanweisung gefunden für {command}." + "de": "Keine Gebrauchsanweisung gefunden für {command}.", + "fr": "Aucune information d'utilisation trouvée pour {command}" }, "help_link": { "en": "Help for {module}: {link}", "na'vi": "{module}ä srungìl {link} it tok", "cy": "Cymorth am {module}: {link}", "nl": "Hulp voor {module}: {link}", - "de": "Hilfe für {module}: {link}" + "de": "Hilfe für {module}: {link}", + "fr": "Aide pour {module}: {link}" }, "no_help": { "en": "No help found for {module}.", "na'vi": "Fì{module}ìri oel ke tsun run srungit", "cy": "Ni chanfuwyd cymorth am {module}", "nl": "Geen hulp gevonden voor {module}.", - "de": "Keine Hilfe gefunden für {module}." + "de": "Keine Hilfe gefunden für {module}.", + "fr": "Aucune aide trouvée pour {module}." }, "loaded_modules": { "en": "Loaded modules: {modules}.", "cy": "Modiwlau sy'n lwythodd: {modules}.", "nl": "Geladen modules: {modules}.", - "de": "Module geladen: {modules}." + "de": "Module geladen: {modules}.", + "fr": "Modules chargés: {modules}." + }, + "access_denied": { + "en": "{user}: You don't have the access level to run this command.", + "fr": "{user}: Vous n'avez pas le niveau d'accès requis pour utiliser cette commande." }, "module_commands": { - "en": "Commands in {module}: {commands}." + "en": "Commands in {module}: {commands}.", + "fr": "Commandes de {module}: {commands}." } } diff --git a/modules/ctcp/config.json b/modules/ctcp/config.json index b300d9f..c945e96 100644 --- a/modules/ctcp/config.json +++ b/modules/ctcp/config.json @@ -1,4 +1,3 @@ { - "ignorable": true, - "help": "https://github.com/reality/depressionbot/blob/master/modules/ctcp/README.md" + "ignorable": true } diff --git a/modules/dent/config.json b/modules/dent/config.json index 8b0fd90..e5715fe 100644 --- a/modules/dent/config.json +++ b/modules/dent/config.json @@ -1,8 +1,7 @@ { "username": "youruserhere", "password": "yourpasswordhere", - "dependencies": [ "command" ], + "dependencies": [ "link" ], "ignorable": true, - "help": "https://github.com/reality/depressionbot/blob/master/modules/dent/README.md", - "dentQuotes": false + "dentQuotes": true } diff --git a/modules/dent/dent.js b/modules/dent/dent.js index a990984..9b03b45 100644 --- a/modules/dent/dent.js +++ b/modules/dent/dent.js @@ -2,7 +2,6 @@ var request = require('request'); _ = require('underscore')._; var dent = function(dbot) { - this.dbot = dbot; this.StatusRegex = { identica: /\bhttps?:\/\/identi\.ca\/notice\/(\d+)\b/ig, twitter: /\bhttps?:\/\/twitter\.com\/\w+\/status\/(\d+)\b/ig @@ -15,8 +14,8 @@ var dent = function(dbot) { this.api = { 'post': function(content) { - var username = dbot.config.dent.username, - password = dbot.config.dent.password, + var username = this.config.username, + password = this.config.password, info, auth = "Basic " + new Buffer(username + ":" + password).toString("base64"); @@ -57,8 +56,8 @@ var dent = function(dbot) { 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) { + if(this.config.dentQuotes === true && _.has(dbot.modules, 'quotes')) { + dbot.api.event.addHook('~qadd', function(key, text) { if(text.indexOf('~~') == -1) { this.api.post(key + ': ' + text); } diff --git a/modules/dns/config.json b/modules/dns/config.json deleted file mode 100644 index 644956e..0000000 --- a/modules/dns/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "help": "https://github.com/reality/depressionbot/blob/master/modules/dns/README.md" -} diff --git a/modules/dns/dns.js b/modules/dns/dns.js index e061d83..fbc38cb 100644 --- a/modules/dns/dns.js +++ b/modules/dns/dns.js @@ -16,6 +16,7 @@ var dns = function(dbot) { } }); }, + '~rdns': function(event) { ip = event.params[1]; dnsmod.reverse(ip, function (error, domain) { diff --git a/modules/dns/strings.json b/modules/dns/strings.json index e77a224..4a1fe02 100644 --- a/modules/dns/strings.json +++ b/modules/dns/strings.json @@ -3,21 +3,25 @@ "en": "{domain} is \u000303AVAILABLE! \u000314({code})", "cy": "{domain} \u000303AR GAEL! \u000314({code})", "nl": "{domain} is \u000303BESCHIKBAAR! \u000314({code})", - "de": "{domain} ist \u000303VERFÜGBAR! \u000314({code})" + "de": "{domain} ist \u000303VERFÜGBAR! \u000314({code})", + "fr": "{domain} est \u000303DISPONIBLE! \u000314({code})" }, "lookup": { "en": "{domain} is \u000305TAKEN! \u000314({address})", "cy": "Dydy {domain} \u000305DDIM AR GAEL! \u000314({address})", "nl": "{domain} is \u000305BEZET! \u000314({address})", - "de": "{domain} ist \u000305BELEGT! \u000314({address})" + "de": "{domain} ist \u000305BELEGT! \u000314({address})", + "fr": "{domain} est \u000305PRIS! \u000314({address})" }, "rdns": { - "en": "{ip} \u2192 {domain}" + "en": "{ip} \u2192 {domain}", + "fr": "{ip} \u2192 {domain}" }, "rdns-error": { "en": "Unable to lookup {ip}. \u000314({error})", "cy": "Methu am-edrych {ip}. \u000314({error})", "nl": "{ip} kan niet worden opgezocht. \u000314({error})", - "de": "Kann {ip} nicht auflösen. \u000314({error})" + "de": "Kann {ip} nicht auflösen. \u000314({error})", + "fr": "Impossible de rechercher {ip}. \u000314({error})" } } diff --git a/modules/event/config.json b/modules/event/config.json deleted file mode 100644 index 55c9c57..0000000 --- a/modules/event/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "help": "https://github.com/reality/depressionbot/blob/master/modules/event/README.md" -} diff --git a/modules/flashy/config.json b/modules/flashy/config.json deleted file mode 100644 index 39287b4..0000000 --- a/modules/flashy/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "help": "https://github.com/reality/depressionbot/blob/master/modules/flashy/README.md" -} diff --git a/modules/github b/modules/github deleted file mode 160000 index 5f4e3dc..0000000 --- a/modules/github +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5f4e3dc8335000e97af528fee289b880a3c99e81 diff --git a/modules/github/LICENSE b/modules/github/LICENSE new file mode 100644 index 0000000..b52fb8d --- /dev/null +++ b/modules/github/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2013 Douglas Gardner + +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. diff --git a/modules/github/README.md b/modules/github/README.md new file mode 100644 index 0000000..12d7a9d --- /dev/null +++ b/modules/github/README.md @@ -0,0 +1,28 @@ +## Github + +Grabs interesting data from the GitHub API. + +### Description + +This module for [depressionbot](https://github.com/reality/depressionbot) takes some interesting information about Github and parses it in a pleasing manner. + +### Configuration +#### defaultrepo +When repository information is lacking from the command, this repository will be used. +#### sortorder +Defines the behaviour of ~issue when no arguments are given. Options are ``created``, ``updated``, or ``comments``. +### Commands +#### ~commits +Returns the number of commits in the repository of the current depressionbot instance. +#### ~gstatus +Returns the [current status of Github](https://status.github.com), and a message explaining the current state of affairs. +#### ~issue (user/repo) [id] +Gives information about the isse pecified, from the default repository if one is not explicitly stated. +#### ~milestone [milestone name] +Returns milestone progress for any given milestone, with a link to the milestone in question. +#### ~repo (repo name) +Returns information about the repo given as a parameter. The repo should be specified as ``user/name``; for example, ``twitter/snowflake``. +#### ~repocount [user] +Returns the number of public Github repositories for the specified user. +### Dependencies +* [request](https://github.com/mikeal/request/):``$ npm install request`` diff --git a/modules/github/config.json b/modules/github/config.json new file mode 100644 index 0000000..2721da9 --- /dev/null +++ b/modules/github/config.json @@ -0,0 +1,8 @@ +{ + "dependencies": [ "command" ], + "ignorable": true, + "help": "http://github.com/zuzak/dbot-github/blob/master/README.md", + "defaultrepo": "reality/dbot", + "sortorder": "updated", + "useragent": "reality/depressionbot github module" +} diff --git a/modules/github/github.js b/modules/github/github.js new file mode 100644 index 0000000..2ca7aa3 --- /dev/null +++ b/modules/github/github.js @@ -0,0 +1,200 @@ +/** + * Module Name: Github + * Description: Retrieves interesting Github information + */ +var request = require('request'), + exec = require('child_process').exec; + +var github = function(dbot) { + this.api = { + "githubStatus": function(callback){ + var reqUrl = "https://status.github.com/api/last-message.json"; + request({"url": reqUrl, "headers": {"User-Agent": this.config.useragent}}, function(error, response, body) { + callback(JSON.parse(body)); + }); + } + }; + + + var commands = { + '~repocount': function(event) { + var reqUrl = "https://api.github.com/users/" + event.params[1] + "/repos"; + request({"url": reqUrl, "headers": {"User-Agent": this.config.useragent}}, function(error, response, body) { + if(response.statusCode == "200") { + var result = JSON.parse(body); + event.reply(dbot.t("repocount",{"user": event.params[1], "count": result.length})); + } else { + event.reply(dbot.t("usernotfound")); + } + }); + }, + '~repo': function(event) { + var repo = event.params[1]; + if (typeof repo == 'undefined') { + repo = this.config.defaultrepo; + } + + var reqUrl = "https://api.github.com/"; + reqUrl += "repos/" + repo; + request({"url": reqUrl, "headers": {"User-Agent": this.config.useragent}}, function(error, response, body) { + + var data = JSON.parse(body); + if (data["fork"]) { + event.reply(dbot.t("forkedrepo",data)); + } else { + event.reply(dbot.t("unforkedrepo",data)); + } + // TODO: move this shizz into an api call + var longurl = "http://github.com/" + repo; + request({method: 'POST', uri: 'http://git.io', form:{url: longurl}}, function(error, response, body){ + event.reply(dbot.t('location')+" "+response.headers["location"]); + }); + }); + }, + '~gstatus': function(event) { + data = this.api.githubStatus(function(data){ + console.log(data); + event.reply(dbot.t("status"+data["status"])); + event.reply(data["body"]); + }.bind(this)); + }, + '~milestone': function(event) { + var repo = this.config.defaultrepo; + var name = event.params[1]; + if (event.params[2]){ + repo = name; + name = event.params[2]; + } + var reqUrl = "https://api.github.com/repos/"; + reqUrl += repo + "/milestones"; + + request({"url": reqUrl, "headers":{"User-Agent": this.config.useragent}}, function(error, response, body) { + var data = JSON.parse(body); + for (var section in data) { + var milestone = data[section]; + if (milestone["title"] == name){ + var str = "Milestone " + milestone["title"]; + var progress = milestone["closed_issues"] / (milestone["open_issues"] + milestone["closed_issues"]); + progress = Math.round(progress*100); + var bar = "["; + for (var i = 10; i < 100; i += 10) { + if ((progress/i) > 1) { + bar += "█"; + } else { + bar += " "; + } + } + bar += "]"; + str += " is " + bar + progress + "% complete"; + + var longurl = "http://github.com/" + repo + "/issues?milestone=" + milestone["number"]; + request({method: 'POST', uri: 'http://git.io', form:{url: longurl}}, function(error, response, body){ + event.reply(response.headers["location"]); + }); + event.reply(str); + break; + } + } + }); + }, + '~repocount': function(event) { + // TODO: add handling for non existent user + var reqUrl = "https://api.github.com/users/" + event.params[1] + "/repos"; + request({"url": reqUrl,"headers": { "User-Agent": this.config.useragent}}, function(error, response, body) { + var result = JSON.parse(body); + event.reply(event.params[1] + " has " + result.length + " public repositories."); + }); + }, + '~grate': function(event) { + request.get({"url":"https://api.github.com/rate_limit", "headers":{"User-Agent": this.config.useragent}}, function(error, response, body) { + var data = JSON.parse(body); + if (data.message){ + event.reply(data.message); + } else { + event.reply(data.rate.remaining + " requests of " + data.rate.limit + " remaining."); + } + }); + }, + '~issue': function(event) { + var repo, issue, randflag; + if (isNaN(event.params[1]) && event.params[1]){ // if ~issue foo/bar + repo = event.params[1]; + issue = event.params[2]; + } else { + repo = this.config.defaultrepo; + issue = event.params[1]; + } + + if (issue == "*" || issue == "random" || issue == "0") { + issue = ""; + randflag = true; + } else if (!issue) { // issue is undefined + issue = ""; + } else { + issue = "/" + issue; // got to be a better way + } + + var reqUrl = "https://api.github.com/repos/" + repo + "/issues" + + issue + "?sort=" + this.config.sortorder; + request.get({"url": reqUrl, headers: { "User-Agent": this.config.useragent}}, function(error,response, body) { + if (response.statusCode == "200") { + var data = JSON.parse(body); + if (!issue){ + if (randflag) { + data = data[Math.floor(Math.random() * data.length)]; + } else { + data = data[0]; + } + } + if (data["pull_request"]["html_url"]){ + console.log(data["pull_request"]["html_url"]); + data["pull_request"] = " with code"; + } else { + data["pull_request"] = ""; + } + if (data["state"]=="open") { + data["state"] = "\u000303" + data["state"]; + } else { + data["state"] = "\u000304" + data["state"]; + } + var labels = ""; + for (var i=0; i < data["labels"].length; i++) { // for-in doesn't like me + var color = "\u0003" + (parseInt(data["labels"][i]["color"],16) % 15); + labels += " " + color + data["labels"][i]["name"]; + } + data["label"] = labels; + event.reply(dbot.t("issue",data)); + event.reply(data["html_url"]); + } else { + event.reply(dbot.t("issuenotfound")); + } + }); + }, + '~commits': function(event) { + exec("git rev-list --all | wc -l", function(error, stdout, stderr) { + stdout = stdout.trim(); + request({"url":"http://numbersapi.com/" + stdout + "?fragment&default=XXX"}, function(error, response, body){ + if (body != "XXX"){ + event.reply(dbot.t("commitcountfun",{"fact": body, "count": stdout})); + } else { + // nothing fun about the number, let's try the year + request({"url":"http://numbersapi.com/" + stdout + "/year?fragment&default=XXX"}, function(error, response, body){ + if (body != "XXX"){ + event.reply(dbot.t("commitcountyear",{"fact": body, "count": stdout})); + } else { + event.reply(dbot.t("commitcountboring",{"count": stdout})); + } + }); + } + }); + }); + } + }; + this.commands = commands; + + this.on = 'PRIVMSG'; +}; + +exports.fetch = function(dbot) { + return new github(dbot); +}; diff --git a/modules/github/strings.json b/modules/github/strings.json new file mode 100644 index 0000000..4b0fb29 --- /dev/null +++ b/modules/github/strings.json @@ -0,0 +1,81 @@ +{ + "repocount": { + "en": "{user} has {count} public repos.", + "cy": "Mae {count} archifdai cyhoeddus gan {user}.", + "de": "{user} hat {count} öffnetliche Repos.", + "fr": "{user} a {count} dépôt(s) public(s)." + }, + "statusgood": { + "en": "\u000309Shit's fine", + "cy": "\u000309Cachu'n ddirwy", + "de": "\u000309Alles in Ordnung", + "fr": "\u000309Cette merde tourne bien" + }, + "statusminor": { + "en": "\u000308Shit's touchy", + "cy": "\u000308Cachu'n fregus", + "de": "\u000308Kleinere Probleme vorhanden", + "fr": "\u000308Cette merde a un petit problème" + }, + "statusmajor": { + "en": "\u000304Shit's fucked:", + "cy": "\u000304Cachu wedi cyrraedd y ffan:", + "de": "\u000304Du bist am Arsch", + "fr": "\u000304Cette merde est foutue : " + }, + "location": { + "en": "You can find that shit at:", + "cy": "Gallwch ddod o hyd y cachu yn:", + "de": "Kann nicht gefunden werden:", + "fr": "Tu peux trouver cette merde ici : " + }, + "forkedrepo": { + "en": "{name} is a forked {language} repo with {open_issues} unresolved issues [{forks}F {watchers}W]", + "cy": "{name} ydy archif {language} fforchog gyda {open_issues} materion heb eu datrys [{forks}F {watchers}W]", + "de": "{name} ist eine geteilte {language} Repo mit {open_issues} ungelösten Problemen [{forks}F {watchers}W]", + "fr": "{name} est un dépôt fourché {language} avec {open_issues} problème(s) non résolu(s) [{forks}F {watchers}W]" + }, + "unforkedrepo": { + "en": "{name} is a {language} repo with {open_issues} unresolved issues [{forks}F {watchers}W]", + "cy": "{name} ydy archif {language} gyda {open_issues} materion heb eu datrys [{forks}F {watchers}W]", + "de": "{name} ist eine {language} Repo mit {open_issues} ungelösten Problemen [{forks}F {watchers}W]", + "fr": "{name} est un dépôt {language} avec {open_issues} problème(s) non résolu(s) [{forks}F {watchers}W]" + }, + "usernotfound": { + "en": "User not found.", + "cy": "Defnyddiwr heb ei ganfod.", + "de": "Benutzer nicht gefunden.", + "fr": "Utilisateur non trouvé." + }, + "issuenotfound": { + "en": "Unable to find that issue.", + "cy": "Wedi methu dod o hyd mater hwnnw", + "de": "Kann dieses Problem nicht finden.", + "fr": "Impossible de trouver ce problème." + }, + "issue": { + "en": "Issue \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} comments]{label}", + "cy": "Mater \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} sylwadau]{label}", + "de": "Problem \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} comments]{label}", + "fr": "Problème \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} commentaires]{label}" + }, + "commitcountboring": { + "en": "My code has been committed {count} times.", + "cy": "Mae fy cod wedi cael ei gyflawni ar {count} adegau.", + "de": "Mein Code wurde {count} mal überstellt.", + "fr": "Mon code a été modifié {count} fois." + }, + "commitcountfun": { + "en": "My repository has the same number of commits as {fact} ({count}).", + "cy": "Yr un nifer o ymrwymo fel {fact} gan fy archif ({count}).", + "de": "Meine Repository hat die gleiche Anzahl Commits wie {fact} ({count}).", + "fr": "Mon dépôt a le même nombre de modifications que {fact} ({count})." + }, + "commitcountyear": { + "en": "My repository's commits number {count}, the year that {fact}.", + "cy": "Nifer o ymrwymo gan fy archif: {count}, y flwyddyn y {fact}.", + "de": "Anzahl der Commits in meinem Repository {count}, des Jahres {fact}", + "fr": "Mon dépot compte {count} modifications, l'année où {fact}." + } +} + diff --git a/modules/ignore/api.js b/modules/ignore/api.js new file mode 100644 index 0000000..43fa89f --- /dev/null +++ b/modules/ignore/api.js @@ -0,0 +1,49 @@ +var _ = require('underscore')._; + +var api = function(dbot) { + return { + // Is user ignoring command/module? + 'isUserIgnoring': function(user, item, callback) { + this.internalAPI.isUserImpeded(user, item, 'ignores', callback); + }, + + // Is user banned from command/module? + 'isUserBanned': function(user, item, callback) { + this.internalAPI.isUserImpeded(user, item, 'bans', callback); + }, + + // Is channel ignoring module? + // TODO: Command support + 'isChannelIgnoring': function(channelName, item, callback) { + var isIgnoring = false, + channel = false; + + this.db.search('channel_ignores', { + 'server': server, + 'name': channel + }, function(result) { + channel = result; + }, function(err) { + if(!err && channel && _.include(channel.ignores, item)) { + isIgnoring = true; + } + callback(isIgnoring); + }); + }, + + // Resolve a nick and return their user and ignores object + 'getUserIgnores': function(user, callback) { + this.db.read('ignores', user.id, function(err, ignores) { + if(!err && ignores) { + callback(false, ignores); + } else { + callback(true, null); + } + }); + } + }; +} + +exports.fetch = function(dbot) { + return api(dbot); +}; diff --git a/modules/ignore/config.json b/modules/ignore/config.json index 14e12c2..b9df2fc 100644 --- a/modules/ignore/config.json +++ b/modules/ignore/config.json @@ -1,6 +1,6 @@ { "ignorable": false, - "dependencies": [ "command" ], + "dependencies": [ "users" ], "dbKeys": [ "ignores", "bans" ], - "help": "http://github.com/reality/depressionbot/blob/master/modules/ignore/README.md" + "dbType": "redis" } diff --git a/modules/ignore/ignore.js b/modules/ignore/ignore.js index 3e3d974..7f52078 100644 --- a/modules/ignore/ignore.js +++ b/modules/ignore/ignore.js @@ -5,15 +5,35 @@ * 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 _ = require('underscore')._, + databank = require('databank'), + uuid = require('node-uuid'), + NoSuchThingError = databank.NoSuchThingError; var ignore = function(dbot) { + this.internalAPI = { + 'isUserImpeded': function(user, item, by, callback) { + this.api.getUserIgnores(user, function(err, ignores) { + var isImpeded = false; + if(!err && ignores) { + if(_.has(dbot.commands, item)) { + item = dbot.commands[item].module; + } + if(_.include(ignores[by], item)) { + isImpeded = true; + } + } + callback(isImpeded); + }); + }.bind(this) + }; + var commands = { '~ignore': function(event) { var module = event.params[1]; var ignorableModules = _.chain(dbot.modules) .filter(function(module, name) { - return dbot.config[module].ignorable === true; + return dbot.config.modules[module].ignorable === true; }) .pluck('name') .value(); @@ -25,21 +45,30 @@ var ignore = function(dbot) { })); } else { if(module == '*' || _.include(ignorableModules, module)) { - if(_.has(dbot.db.ignores, event.user) && _.include(dbot.db.ignores[event.user], module)) { - event.reply(dbot.t('already_ignoring', { 'user': event.user })); - } else { - if(_.has(dbot.db.ignores, module)) { - dbot.db.ignores[event.user].push(module); - } else { - dbot.db.ignores[event.user] = [module]; + this.api.getUserIgnores(event.rUser, function(err, ignores) { + if(!ignores) { + ignores = { + 'id': event.rUser.id, + 'ignores': [], + 'bans': [] + }; } - dbot.instance.ignoreTag(event.user, module); - event.reply(dbot.t('ignored', { - 'user': event.user, - 'module': module - })); - } + if(!_.include(ignores.ignores, module)) { + ignores.ignores.push(module); + this.db.save('ignores', event.rUser.id, ignores, function(err) { + if(!err) { + dbot.instance.ignoreTag(event.user, module); + event.reply(dbot.t('ignored', { + 'user': event.user, + 'module': module + })); + } + }); + } else { + event.reply(dbot.t('already_ignoring', { 'user': event.user })); + } + }.bind(this)); } else { event.reply(dbot.t('invalid_ignore', { 'user': event.user })); } @@ -47,137 +76,192 @@ var ignore = function(dbot) { }, '~unignore': function(event) { - var ignoredModules = []; - if(_.has(dbot.db.ignores, event.user)) { - ignoredModules = dbot.db.ignores[event.user]; - } var module = event.params[1]; - if(_.isUndefined(module)) { - event.reply(dbot.t('unignore_usage', { - 'user': event.user, - 'modules': ignoredModules.join(', ') - })); - } else { - if(_.include(ignoredModules, module)) { - dbot.db.ignores[event.user].splice(dbot.db.ignores[event.user].indexOf(module), 1); - dbot.instance.removeIgnore(event.user, module) - event.reply(dbot.t('unignored', { - 'user': event.user, - 'module': module - })); + this.api.getUserIgnores(event.rUser, function(err, ignores) { + if(err || !ignores || _.isUndefined(module)) { + if(ignores) { + event.reply(dbot.t('unignore_usage', { + 'user': event.user, + 'modules': ignores.ignores.join(', ') + })); + } else { + event.reply(dbot.t('empty_unignore_usage', { + 'user': event.user + })); + } } else { - event.reply(dbot.t('invalid_unignore', { 'user': event.user })); + if(_.include(ignores.ignores, module)) { + ignores.ignores = _.without(ignores.ignores, module); + this.db.save('ignores', event.rUser.id, ignores, function(err) { + if(!err) { + dbot.instance.removeIgnore(event.user, module) + event.reply(dbot.t('unignored', { + 'user': event.user, + 'module': module + })); + } + }); + } else { + event.reply(dbot.t('invalid_unignore', { 'user': event.user })); + } } - } + }.bind(this)); }, '~ban': function(event) { - var user = event.params[1]; - var module = event.params[2]; + var nick = event.input[1], + item = event.input[2]; - if(_.isUndefined(user) || _.isUndefined(module)) { - event.reply(dbot.t('ban_usage', {'user': event.user})); - return; - } - - if(module == '*' || _.include(dbot.config.moduleNames, module) || _.include(dbot.commands, module)) { - if(_.has(dbot.db.bans, user) && _.include(dbot.db.bans[user], module)) { - event.reply(dbot.t('already_banned', { - 'user': event.user, - 'banned': user - })); - return; - } + if(module == '*' || _.include(dbot.config.moduleNames, item) || _.include(dbot.commands, item)) { + dbot.api.users.resolveUser(event.server, nick, function(user) { + this.api.getUserIgnores(user, function(err, ignores) { + if(!err) { + if(!ignores) { + ignores = { + 'id': user.id, + 'ignores': [], + 'bans': [] + }; + } - if(_.has(dbot.db.bans, event.params[1])) { - dbot.db.bans[event.params[1]].push(module); - } else { - dbot.db.bans[event.params[1]] = [module]; - } - - event.reply(dbot.t('banned_success', { - 'user': event.user, - 'banned': user, - 'module': module - })); + if(!_.include(ignores.bans, item)) { + ignores.bans.push(item); + this.db.save('ignores', user.id, ignores, function(err) { + if(!err) { + event.reply(dbot.t('banned_success', { + 'user': event.user, + 'banned': nick, + 'module': item + })); + } + }); + } else { + event.reply(dbot.t('already_banned', { + 'user': event.user, + 'banned': nick + })); + } + } + }.bind(this)); + }); } else { - event.reply(dbot.t('invalid_ban', {'user': event.user})); + event.reply(dbot.t('invalid_ban', { 'user': event.user })); } }, '~unban': function(event) { - var bannedModules = []; + var nick = event.input[1], + item = event.input[2]; - var user = event.params[1]; - var module = event.params[2]; - - if(_.isUndefined(user) || _.isUndefined(module)) { - event.reply(dbot.t('unban_usage', {'user': event.user})); - } else { - if(_.has(dbot.db.bans, user) && _.include(dbot.db.bans[user], module)) { - dbot.db.bans[user].splice(dbot.db.bans[user].indexOf(module), 1); - - event.reply(dbot.t('unbanned_success', { - 'user': event.user, - 'banned': user, - 'module': module - })); - } else { - event.reply(dbot.t('invalid_unban', { - 'user': event.user, - 'banned': user - })); - } - } + dbot.api.users.resolveUser(event.server, nick, function(user) { + this.api.getUserIgnores(user, function(err, ignores) { + if(err || !ignores) { + event.reply(dbot.t('invalid_unban', { + 'user': event.user, + 'banned': nick + })); + } else { + if(_.include(ignores.bans, item)) { + ignores.bans = _.without(ignores.bans, item); + this.db.save('ignores', user.id, ignores, function(err) { + event.reply(dbot.t('unbanned_success', { + 'user': event.user, + 'banned': nick, + 'module': item + })); + }); + } else { + event.reply(dbot.t('invalid_unban', { + 'user': event.user, + 'banned': nick + })); + } + } + }.bind(this)); + }.bind(this)); }, '~ignorechannel': function(event) { - var channel = ((event.params[1] == '@') ? event.channel.name : event.params[1]); - var module = event.params[2]; + var channelName = event.input[1], + module = event.input[2]; // Ignoring the value of 'ignorable' at the moment if(module == '*' || _.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 - })); - } + var channel = false; + + this.db.search('channel_ignores', { + 'server': event.server, + 'name': channelName + }, function(result) { + channel = result; + }, function(err) { + if(!channel) { + var id = uuid.v4(); + channel = { + 'id': id, + 'server': event.server, + 'name': channelName, + 'ignores': [] + }; + } + + if(!_.include(channel.ignores, module)) { + channel.ignores.push(module); + this.db.save('channel_ignores', channel.id, channel, function(err) { + dbot.instance.ignoreTag(channel, module); + event.reply(dbot.t('ignoring_channel', { + 'module': module, + 'channel': channelName + })); + }); + } else { + event.reply(dbot.t('already_ignoring_channel', { + 'module': module, + 'channel': channelName + })); + } + }.bind(this)); } 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]; + var channelName = event.input[1], + module = event.input[2], + channel = false; - 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 - })); - } + this.db.search('channel_ignores', { + 'server': event.server, + 'name': channelName + }, function(result) { + channel = result; + }, function(err) { + if(channel && _.include(channel.ignores, module)) { + channel.ignores = _.without(channel.ignores, module); + this.db.save('channel_ignores', channel.id, channel, function(err) { + dbot.instance.removeIgnore(channel, module); + event.reply(dbot.t('unignoring_channel', { + 'module': module, + 'channel': channelName + })); + }); + } else { + event.reply(dbot.t('not_ignoring_channel', { + 'module': module, + 'channel': channelName + })); + } + }.bind(this)); } }; + commands['~ban'].regex = [/^~ban ([^ ]+) ([^ ]+)$/, 3]; + commands['~unban'].regex = [/^~unban ([^ ]+) ([^ ]+)$/, 3]; + commands['~ignorechannel'].regex = [/^~ignorechannel ([^ ]+) ([^ ]+)$/, 3]; + commands['~unignorechannel'].regex = [/^~unignorechannel ([^ ]+) ([^ ]+)$/, 3]; + commands['~ban'].access = 'moderator'; commands['~unban'].access = 'moderator'; commands['~ignorechannel'].access = 'moderator'; @@ -187,11 +271,22 @@ var ignore = function(dbot) { this.onLoad = function() { dbot.instance.clearIgnores(); - _.each(dbot.db.ignores, function(ignores, item) { - _.each(ignores, function(ignore) { - dbot.instance.ignoreTag(item, ignore); - }, this); - }, this); + + this.db.scan('ignores', function(ignores) { + dbot.api.users.getUser(ignores.id, function(user) { + if(user) { + _.each(ignores.ignores, function(module) { + dbot.instance.ignoreTag(user.primaryNick, module); + }); + } + }); + }, function(err) { }); + + this.db.scan('channel_ignores', function(channel) { + _.each(channel.ignores, function(module) { + dbot.instance.ignoreTag(channel, module); + }); + }, function(err) { }); }; }; diff --git a/modules/ignore/strings.json b/modules/ignore/strings.json index bd29941..7075a5c 100644 --- a/modules/ignore/strings.json +++ b/modules/ignore/strings.json @@ -5,7 +5,8 @@ "na'vi": "{user}: Sar: ~ignore ['u]. U, nga ke tìng mikyun: {modules}.", "cy": "{user}: Defnydd: ~ignore [modiwl]. Modiwlau a allech anwybyddu yw: {modules}.", "nl": "{user}: Gebruik: ~ignore [module]. Modules die negeert kunnen worden zijn: {modules}.", - "de": "{user}: Benutzung: ~ignore [module]. Module, die ignoriert werden können: {modules}." + "de": "{user}: Benutzung: ~ignore [module]. Module, die ignoriert werden können: {modules}.", + "fr": "{user}: Utilisation: ~ignore [module]. Les modules que vous pouvez ignorer sont: {modules}." }, "already_ignoring": { "en": "{user}: You're already ignoring that module.", @@ -13,7 +14,8 @@ "na'vi": "{user}: 'uri nga ke tìng mikyun srekrr.", "cy": "{user}: Mi rwyt ti'n anwybyddu'r modiwl yna'n barod.", "nl": "{user}: Je negeert deze module al.", - "de": "{user}: Dieses Modul wird bereits ignoriert." + "de": "{user}: Dieses Modul wird bereits ignoriert.", + "fr": "{user}: Vous ignorez déjà ce module." }, "ignored": { "en": "{user}: Now ignoring {module}.", @@ -21,7 +23,8 @@ "na'vi": "{user}: Nga ke terìng mikyun {module}ne set.", "cy": "{user}: Nawr yn anwybyddu {module}", "nl": "{user}: {module} wordt nu genegeerd.", - "de": "{user}: {module} wird nun ignoriert." + "de": "{user}: {module} wird nun ignoriert.", + "fr": "{user}: {module} désormais ignoré." }, "invalid_ignore": { "en": "{user}: That isn't a valid module name.", @@ -29,7 +32,8 @@ "na'vi": "{user}: Tsatstxo eyawr ke lu.", "cy": "{user}: Nid oedd hwna'n modiwl dilys", "nl": "{user}: Dat is geen geldige modulenaam.", - "de": "{user}: Dies ist nicht der Name eines Moduls." + "de": "{user}: Dies ist nicht der Name eines Moduls.", + "fr": "{user}: Ceci ne correspond pas à un nom de module valide." }, "unignore_usage": { "en": "{user}: Usage: ~unignore [module]. Modules you are currently ignoring: {modules}.", @@ -37,7 +41,17 @@ "na'vi": "{user}: Sar: ~unignore ['u]. Uri, nga ke terìng mikyun: {modules}.", "cy": "{user}: Defnydd: ~unignore [modiwl]. Modiwlau rydech yn anwybyddu ar hyn o bryd: {modules}", "nl": "{user}: Gebruik: ~unignore [module]. Modules die momenteel worden genegeerd: {modules}.", - "de": "{user}: Benutzung: ~unignore [module]. Module, die im Moment ignoriert werden: {modules}." + "de": "{user}: Benutzung: ~unignore [module]. Module, die im Moment ignoriert werden: {modules}.", + "fr": "{user}: Utilisation: ~unignore [module]. Modules que vous ignorez actuellement: {modules}." + }, + "empty_unignore_usage": { + "en": "{user}: Usage: ~unignore [module].", + "es": "{user}: Modo de empleo: ~unignore [módulo].", + "na'vi": "{user}: Sar: ~unignore ['u].", + "cy": "{user}: Defnydd: ~unignore [modiwl].", + "nl": "{user}: Gebruik: ~unignore [module].", + "de": "{user}: Benutzung: ~unignore [module].", + "fr": "{user}: Utilisation: ~unignore [module]." }, "invalid_unignore": { "en": "{user}: You're not ignoring that module or it doesn't exist.", @@ -45,7 +59,8 @@ "na'vi":"{user}: Nga terìng mikyun fu fì'ul fìtsengit ke tok.", "cy": "{user}: Nid wyt ti'n anwybyddu'r modiwl yna neu nid yw e'n bodoli", "nl": "{user}: Deze module bestaat niet of wordt niet genegeerd.", - "de": "{user}: Dieses Modul wird entweder nicht ignoriert oder existiert nicht." + "de": "{user}: Dieses Modul wird entweder nicht ignoriert oder existiert nicht.", + "fr": "{user}: Vous n'ignorez pas ce module ou il n'existe pas." }, "unignored": { "en": "{user}: No longer ignoring {module}.", @@ -53,83 +68,96 @@ "na'vi": "{user}: Nga terìng mikyun {module}ne set", "cy": "{user}: Ddim yn anwybyddu {module} bellach", "nl": "{user}: {module} wordt niet langer genegeerd.", - "de": "{user}: {module} wird nicht länger ignoriert." + "de": "{user}: {module} wird nicht länger ignoriert.", + "fr": "{user}: {module} n'est plus ignoré à présent." }, "ban_usage": { "en": "{user}: Usage: ~ban [user] [module/command]. Use * for all modules and commands.", "cy": "{user}: Defnydd: ~ban [defnyddiwr] [modiwl/gorchymyn]. Defnyddio * am pob modiwlau a gorchmynion.", "nl": "{user}: Gebruik: ~ban [gebruiker] [module/commando]. Gebruik * voor alle modules en alle commandos.", - "de": "{user}: Benutzung ~ban [Benutzer] [module/Befehl]. Benutze * für alle Module und Befehle." + "de": "{user}: Benutzung ~ban [Benutzer] [module/Befehl]. Benutze * für alle Module und Befehle.", + "fr": "{user}: Utilisation: ~ban [user] [module/command]. Utilisez * pour tous les modules et commandes." }, "already_banned": { "en": "{user}: {banned} is already banned from that module.", "cy": "{user}: {banned} eisoes wedi ei wahardd o'r modiwl.", "nl": "{user}: {banned} is al geband van deze module.", - "de": "{user}: {banned} ist bereits von diesem Modul gebannt." + "de": "{user}: {banned} ist bereits von diesem Modul gebannt.", + "fr": "{user}: {banned} est déjà interdit d'utiliser ce module." }, "banned_success": { "en": "{user}: {banned} is now banned from {module}.", "cy": "{user}: {banned} ei wahardd yn awr am {module}.", "nl": "{user}: {banned} mag {module} nu niet meer gebruiken.", - "de": "{user}: {banned} ist nun von {module} gebannt." + "de": "{user}: {banned} ist nun von {module} gebannt.", + "fr": "{user}: {banned} est maintenant interdit d'utiliser {module}." }, "invalid_ban": { "en": "{user}: That isn't a valid module name.", "cy": "{user}: Nid oedd hwna'n modiwl dilys", "nl": "{user}: Dat is geen geldige modulenaam.", - "de": "{user}: Dies ist nicht der Name eines Moduls." + "de": "{user}: Dies ist nicht der Name eines Moduls.", + "fr": "{user}: Ceci n'est pas un nom de module valide." }, "unban_usage": { "en": "{user}: Usage: ~unban [user] [module].", "cy": "{user}: Defnydd: ~unban [defnyddiwr] [modiwl].", "nl": "{user}: Gebruik: ~unban [gebruiker] [module].", - "de": "{user}: Benutzung: ~unban [Benutzer] [module]." + "de": "{user}: Benutzung: ~unban [Benutzer] [module].", + "fr": "{user}: Utilisation: ~unban [user] [module]." }, "invalid_unban": { "en": "{user}: {banned} is not banned from that module or it doesn't exist.", "cy": "{user}: Nid oedd {banned} wedi ei wahardd o'r modiwl, neu nid yw'n bodoli.", "nl": "{user}: {banned} is niet geband van die module of de module bestaat niet.", - "de": "{user}: {banned} ist von diesem Modul nicht gebannt, oder es existiert nicht." + "de": "{user}: {banned} ist von diesem Modul nicht gebannt, oder es existiert nicht.", + "fr": "{user}: {banned} n'est pas interdit d'utiliser ce module, ou il n'existe pas." }, "unbanned_success": { "en": "{user}: {banned} is no longer banned from {module}.", "cy": "{user}: Nid yw {banned} yn cael ei wahardd mwyach.", "nl": "{user}: {banned} mag {module} weer gebruiken.", - "de": "{user}: {banned} wurde von {module} entbannt." + "de": "{user}: {banned} wurde von {module} entbannt.", + "fr": "{user}: {banned} n'est plus interdit d'utiliser {module}." }, "ignoring_channel": { "en": "Now ignoring {module} in {channel}.", "na'vi": "Oe ke stayawm {module}ur mì {channel}", "cy": "Bellach yn anwybyddu {module} yn {channel}.", "nl": "{module} wordt nu genegeerd in {channel}.", - "de": "{module} in [channel} wird nun ignoriert." + "de": "{module} in {channel} wird nun ignoriert.", + "fr": "{module} dans {channel} maintenant ignoré." }, "already_ignoring_channel": { "en": "Already ignoring {module} in {channel}.", "na'vi": "Oe ke stayawm {module}ur mì {channel} li", "cy": "Eisoes anwybyddu {module} yn {channel}", "nl": "{module} wordt al genegeerd in {channel}.", - "de": "{module} in {channel} wird bereits ignoriert." + "de": "{module} in {channel} wird bereits ignoriert.", + "fr": "{module} dans {channel} déjà ignoré." }, "module_not_exist": { "en": "{module} isn't loaded or doesn't exist.", "na'vi": "Oel ke omum teri {module}it fu {module} ke fkeytok", "cy": "Dydy {module} ddim yn lwythodd, neu ddim yn bodoli.", "nl": "{module} is niet geladen of bestaat niet.", - "de": "{module} ist nicht geladen oder existiert nicht." + "de": "{module} ist nicht geladen oder existiert nicht.", + "fr": "{module} n'est pas chargé ou n'existe pas." }, "unignoring_channel": { "en": "No longer ignoring {module} in {channel}.", "na'vi": "Oel stayawm {module}ur mì {channel} set.", "cy": "Nid anwybyddu {module} yn {channel} mwyach.", "nl": "{module} wordt niet meer genegeerd in {channel}.", - "de": "{module} in {channel} wird nicht länger ignoriert." + "de": "{module} in {channel} wird nicht länger ignoriert.", + "fr": "{module} dans {channel} n'est plus ignoré à présent." }, "not_ignoring_channel": { "en": "{module} wasn't being ignored in {channel}.", "na'vi": "Oel stayawm {module}ur mì {channel} li.", "cy": "Nid yw {module} yn cael ei anwybyddu yn {channel}.", "nl": "{module} werd niet genegeerd in {channel}.", - "de": "{module} wurde in {channel} nicht ignoriert." + "de": "{module} wurde in {channel} nicht ignoriert.", + "fr": "{module} n'était pas ignoré dans {channel}." } } diff --git a/modules/imgur/config.json b/modules/imgur/config.json index dc77624..c1899c7 100644 --- a/modules/imgur/config.json +++ b/modules/imgur/config.json @@ -1,6 +1,6 @@ { "dbKeys": [ "imgur" ], - "dependencies": [ "web", "api" ], + "dependencies": [ "web", "api", "link" ], "imagelength": 5, "nsfwwarn": true, "apikey": "86fd3a8da348b65", diff --git a/modules/imgur/imgur.js b/modules/imgur/imgur.js index 42edc79..0482748 100644 --- a/modules/imgur/imgur.js +++ b/modules/imgur/imgur.js @@ -8,7 +8,6 @@ var _ = require('underscore')._, crypto = require('crypto'); var imgur = function(dbot) { - this.db = dbot.db.imgur; this.internalAPI = { 'infoString': function(imgData) { info = ''; @@ -77,21 +76,21 @@ var imgur = function(dbot) { var testUrl = 'http://i.imgur.com/' + testSlug + '.' + ext[_.random(0, ext.length - 1)]; - this.db.totalHttpRequests += 1; + dbot.db.imgur.totalHttpRequests += 1; var image = request(testUrl, function(error, response, body) { // 492 is body.length of a removed image if(!error && response.statusCode == 200 && body.length != 492) { - this.db.totalImages += 1; + dbot.db.imgur.totalImages += 1; var hash = crypto.createHash('md5').update(body).digest("hex"); if(_.has(dbot.modules, 'quotes')){ // autoadd: {"abcdef": "facebookman"} - if(_.has(dbot.config.imgur.autoadd,hash)){ + if(_.has(dbot.config.modules.imgur.autoadd,hash)){ var category = dbot.config.imgur.autoadd[hash]; if (_.contains(category, testUrl)){ // there's probably less than 62^5 chance of this happening } else { - if(!_.has(dbot.db.quoteArrs, category)) dbot.db.quoteArrs[category] = []; - dbot.db.quoteArrs[category].push(testUrl); + dbot.api.quotes.addQuote(category, testUrl, + dbot.config.name, function() { }); } } } @@ -113,10 +112,10 @@ var imgur = function(dbot) { 'url': 'https://api.imgur.com/3/image/' + slug + '.json', 'json': true, 'headers': { - 'Authorization': 'Client-ID ' + dbot.config.imgur.apikey + 'Authorization': 'Client-ID ' + this.config.apikey } }, function(err, response, body) { - this.db.totalApiRequests += 1; + dbot.db.imgur.totalApiRequests += 1; callback(body); }.bind(this)); }, @@ -126,7 +125,7 @@ var imgur = function(dbot) { 'url': 'https://api.imgur.com/3/album/' + slug + '.json', 'json': true, 'headers': { - 'Authorization': 'Client-ID ' + dbot.config.imgur.apikey + 'Authorization': 'Client-ID ' + this.config.apikey } }, function(err, response, body) { this.db.totalApiRequests += 1; @@ -139,7 +138,7 @@ var imgur = function(dbot) { 'url': 'https://api.imgur.com/3/gallery/' + slug + '.json', 'json': true, 'headers': { - 'Authorization': 'Client-ID ' + dbot.config.imgur.apikey + 'Authorization': 'Client-ID ' + this.config.apikey } }, function(err, response, body) { this.db.totalApiRequests += 1; @@ -197,6 +196,7 @@ var imgur = function(dbot) { if(!_.has(dbot.db.imgur, 'totalHttpRequests')) dbot.db.imgur.totalHttpRequests = 0; if(!_.has(dbot.db.imgur, 'totalApiRequests')) dbot.db.imgur.totalApiRequests = 0; if(!_.has(dbot.db.imgur, 'totalImages')) dbot.db.imgur.totalImages = 0; + this.db = dbot.db.imgur; }.bind(this); }; diff --git a/modules/imgur/strings.json b/modules/imgur/strings.json index 91351f5..cb20fd9 100644 --- a/modules/imgur/strings.json +++ b/modules/imgur/strings.json @@ -4,9 +4,11 @@ "na'vi": "kxawm ke wivìntxu evengur", "cy": "Gallai fod yn anniogel ar gwaith", "nl": "bevat mogelijk gevoelige beelden", - "de": "Könnte 18+ Material enthalten" + "de": "Könnte 18+ Material enthalten", + "fr": "peut être risqué pour le travail (NSFW)" }, "imgurinfo": { - "en": "[{info}]" + "en": "[{info}]", + "fr": "[{info}]" } } diff --git a/modules/js/config.json b/modules/js/config.json index 084df95..41bb0ec 100644 --- a/modules/js/config.json +++ b/modules/js/config.json @@ -5,6 +5,5 @@ } }, "dependencies": [ "command" ], - "ignorable": true, - "help": "http://github.com/reality/depressionbot/blob/master/modules/js/README.md" + "ignorable": true } diff --git a/modules/js/js.js b/modules/js/js.js index d6094ff..023db38 100644 --- a/modules/js/js.js +++ b/modules/js/js.js @@ -21,6 +21,16 @@ var js = function(dbot) { // Run JS code un-sandboxed, with access to DBot memory (admin-only). '~ajs': function(event) { + var callback = function() { + var args = Array.prototype.slice.call(arguments); + for(var i=0;i y) return 1; - if(x < y) return -1; - return 0; - }); + process.nextTick(function(){ + thumbnails.sort(function(a, b) { + var x = a.nick.toLowerCase(); + var y = b.nick.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, + res.render('profile_grid', { + 'name': dbot.config.name, + 'connection': req.params.connection, + 'thumbnails': thumbnails, + }); + }); }); } } diff --git a/modules/profile/profile.js b/modules/profile/profile.js index 740790c..2a28d5c 100644 --- a/modules/profile/profile.js +++ b/modules/profile/profile.js @@ -2,28 +2,28 @@ 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(); - + // Ensure all users have a profile + dbot.api.users.getAllUsers(function(users){ + if(users){ + _.each(users, function(user){ + this.api.getProfileByUUID(user.id, function(err, uuid, profile){ + // If function returns an error and uuid, create a new profile + if(err && uuid){ + this.api.createProfile(user); + } + }.bind(this)); + }.bind(this)); + } + }.bind(this)); + // 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); + + //TODO(@samstudio8) Profile Merging + //dbot.api.command.addHook('~mergeusers', this.api.mergeProfile); }; }; diff --git a/modules/project/pages.js b/modules/project/pages.js index 929d7a8..529d634 100644 --- a/modules/project/pages.js +++ b/modules/project/pages.js @@ -3,8 +3,7 @@ var exec = require('child_process').exec, _ = require('underscore'); var pages = function(dbot) { - var quoteCat = dbot.db.quoteArrs[dbot.config.name], - rev, diff, branch, credit, authors = []; + var rev, diff, branch, credit, authors = []; exec("git log --format='%cN¬' | sort -u | tr -d '\n'", function (error, stdout, sderr) { var credit = stdout.split("¬"); // nobody uses ¬, do they? for (var i = 0; i < credit.length; i++) { @@ -30,72 +29,71 @@ var pages = function(dbot) { /* TODO: merge back into github module */ var milestones; - request({"url":"https://api.github.com/repos/" + dbot.config.github.defaultrepo + "/milestones?state=open","headers":{"User-Agent":"reality/depressionbot (project module)"}}, function(error, response, body){ + request({"url":"https://api.github.com/repos/" + dbot.config.modules.github.defaultrepo + "/milestones?state=open","headers":{"User-Agent":"reality/depressionbot (project module)"}}, function(error, response, body){ milestones = JSON.parse(body); }); return { '/project': function(req, res) { - var quote = dbot.config.name; - if(quoteCat) { - quote = quoteCat[Math.floor(Math.random()*quoteCat.length)]; - } + dbot.api.quotes.getQuote(dbot.config.name, function(quote) { + if(!quote) quote = dbot.config.name; - res.render('project', { - "translation": dbot.modules.project.api.translationProgress(), - "configList": dbot.modules.project.api.configList(), - "authors": authors, - "credits": dbot.t("credits"), - "thanks": dbot.t("thanks"), - "name": dbot.config.name, - "intro": dbot.t("dbotintro", { - "botname": dbot.config.name - }), - "curr839": dbot.config.language, - "repo": dbot.config.github.defaultrepo, - "branch": dbot.t("branch",{ - "branch": branch - }), - "currver": dbot.config.version, - "currlang": dbot.t("dbotspeaks",{ - "lang839": dbot.config.language, - "langen": dbot.strings[dbot.config.language]["en"], - "lang": dbot.t(dbot.config.language), - "name": dbot.config.name - }), - "projectstatus": dbot.t("projectstatus"), - "revnum": dbot.t("revnum",{ + res.render('project', { + "translation": dbot.modules.project.api.translationProgress(), + "configList": dbot.modules.project.api.configList(), + "authors": authors, + "credits": dbot.t("credits"), + "thanks": dbot.t("thanks"), "name": dbot.config.name, - "rev": rev - }), - "modules": dbot.config.moduleNames, - "loadmod": dbot.t("loadedmodules"), - "debugmode": dbot.t("debugmode-" + dbot.config.debugMode), - "milestones": milestones, - "milestoneprog": dbot.t("milestoneprog"), - "config": dbot.t("configoptions"), - "milestonename": dbot.t("milestonename"), - "openmilestone": dbot.t("openmilestone"), - "closedmilestone": dbot.t("closedmilestone"), - "development": dbot.t("development"), - "dquote": quote, - "diff": diff, - "pagetitle": dbot.t("pagetitle", { - "botname": dbot.config.name - }), - "git": dbot.t("git"), - "milestonehead": dbot.t("milestones"), - "propaganda": dbot.t("propaganda"), - "languagecurr": dbot.t(dbot.config.language), - "languagenati": dbot.t("langhead-native"), - "languageeng": dbot.t("en"), - "languageprog": dbot.t("langhead-progress"), - "languagetrans": dbot.t("langhead-translations"), - "languagetranshead": dbot.t("translations"), - "pullreqs": dbot.t("outstanding-pullreq") - }); - }, + "intro": dbot.t("dbotintro", { + "botname": dbot.config.name + }), + "curr839": dbot.config.language, + "repo": dbot.config.modules.github.defaultrepo, + "branch": dbot.t("branch",{ + "branch": branch + }), + "currver": dbot.config.version, + "currlang": dbot.t("dbotspeaks",{ + "lang839": dbot.config.language, + "langen": dbot.strings[dbot.config.language]["en"], + "lang": dbot.t(dbot.config.language), + "name": dbot.config.name + }), + "projectstatus": dbot.t("projectstatus"), + "revnum": dbot.t("revnum",{ + "name": dbot.config.name, + "rev": rev + }), + "modules": dbot.config.moduleNames, + "loadmod": dbot.t("loadedmodules"), + "debugmode": dbot.t("debugmode-" + dbot.config.debugMode), + "milestones": milestones, + "milestoneprog": dbot.t("milestoneprog"), + "config": dbot.t("configoptions"), + "milestonename": dbot.t("milestonename"), + "openmilestone": dbot.t("openmilestone"), + "closedmilestone": dbot.t("closedmilestone"), + "development": dbot.t("development"), + "dquote": quote, + "diff": diff, + "pagetitle": dbot.t("pagetitle", { + "botname": dbot.config.name + }), + "git": dbot.t("git"), + "milestonehead": dbot.t("milestones"), + "propaganda": dbot.t("propaganda"), + "languagecurr": dbot.t(dbot.config.language), + "languagenati": dbot.t("langhead-native"), + "languageeng": dbot.t("en"), + "languageprog": dbot.t("langhead-progress"), + "languagetrans": dbot.t("langhead-translations"), + "languagetranshead": dbot.t("translations"), + "pullreqs": dbot.t("outstanding-pullreq") + }); + }); + } }; }; diff --git a/modules/project/project.js b/modules/project/project.js index 417e98d..66a1273 100644 --- a/modules/project/project.js +++ b/modules/project/project.js @@ -14,30 +14,30 @@ var project = function(dbot) { var list = []; if(_.has(dbot.modules,'dent')){ list.push(dbot.t("dent-account", { - "username": dbot.config.dent.username + "username": dbot.config.modules.dent.username })); - if(_.has(dbot.config.dent.dentQuotes)) { + if(_.has(dbot.config.modules.dent.dentQuotes)) { list.push(dbot.t("dent-push")); } } if(_.has(dbot.modules,'link')){ - if(dbot.config.link.autoTitle){ + if(dbot.config.modules.link.autoTitle){ list.push(dbot.t("link-autotitle")); } } if(_.has(dbot.modules,'quotes')){ list.push(dbot.t("quote-rmlimit", { - "limit": dbot.config.quotes.rmLimit + "limit": dbot.config.modules.quotes.rmLimit })); } if(_.has(dbot.modules,'report')){ - if(dbot.config.report.notifyVoice){ + if(dbot.config.modules.report.notifyVoice){ list.push(dbot.t("report-notifyvoice")); } } if(_.has(dbot.modules,'web')){ list.push(dbot.t("web-port", { - "port": dbot.config.web.webPort + "port": dbot.config.modules.web.webPort })); } return list; diff --git a/modules/project/strings.json b/modules/project/strings.json index c5fd9a3..d731688 100644 --- a/modules/project/strings.json +++ b/modules/project/strings.json @@ -28,7 +28,7 @@ }, "en": { "en": "English", - "fr": "anglais", + "fr": "Anglais", "de": "Englisch", "it": "inglese", "cy": "saesneg", @@ -36,7 +36,7 @@ }, "fr": { "en": "French", - "fr": "français", + "fr": "Français", "de": "Französisch", "it": "francese", "cy": "Ffrangeg", @@ -44,7 +44,7 @@ }, "de": { "en": "German", - "fr": "allemande", + "fr": "Allemand", "de": "Deutsch", "it": "tedesco", "cy": "Almaeneg", @@ -52,7 +52,7 @@ }, "it": { "en": "Italian", - "fr": "italien", + "fr": "Italien", "de": "Italiener", "it": "italiano", "cy": "Eidaleg", @@ -60,7 +60,7 @@ }, "cy": { "en": "Welsh", - "fr": "gallois", + "fr": "Gallois", "de": "Walisisch", "it": "gallese", "cy": "cymraeg", @@ -77,7 +77,7 @@ }, "es": { "en": "Spanish", - "fr": "espagnole", + "fr": "Espagnol", "de": "Spanisch", "it": "spagnolo", "cy": "Sbaeneg", @@ -87,11 +87,12 @@ "nl": { "en": "Dutch", "cy": "Iseldireg", - "nl": "Nederlands" + "nl": "Nederlands", + "fr": "Néerlandais" }, "revnum": { "en": "{name} is at revision {rev}", - "fr": "{name} est à révision {rev}", + "fr": "{name} est à la révision {rev}", "de": "{name} ist in der Revision {rev}", "it": "{name} il numero di revisione {rev}", "cy": "{name} ar diwygiad {rev}", @@ -115,7 +116,7 @@ }, "debugmode-true": { "en": "Debug mode active", - "fr": "debug actif", + "fr": "Mode de débogage activé", "de": "Debug-Modus aktiviert", "it": "debug attivo", "cy": "Modd dadfygio gweithredol", @@ -123,7 +124,7 @@ }, "debugmode-false": { "en": "Debug off", - "fr": "debug éteint", + "fr": "Mode de débogage arrêté", "de": "Debug-Modus ausgeschaltet", "it": "debug spento", "cy": "Modd dadfygio wedi'i ddiffodd", @@ -131,7 +132,7 @@ }, "milestones": { "en": "Milestones", - "fr": "étapes", + "fr": "Etapes", "cy": "Cerrig milltir", "de": "Zwischenziel" }, @@ -177,36 +178,43 @@ }, "dent-account": { "en": "Submitting dents to @{username}", - "cy": "Cyflwyno 'dents' i'w @{username}" + "cy": "Cyflwyno 'dents' i'w @{username}", + "fr": "Envoi de 'dents' à @{username}" }, "dent-push": { "en": "Pushing quotes to identi.ca", - "cy": "Gwthio dyfyniadau i identi.ca" + "cy": "Gwthio dyfyniadau i identi.ca", + "fr": "Envoi des citations vers identi.ca" }, "quote-rmlimit": { "en": "Quote removal throttle set to {limit}", - "cy": "Trothwy ar dileu dyfyniadau gosod i {limit}" + "cy": "Trothwy ar dileu dyfyniadau gosod i {limit}", + "fr": "Commande d'enlèvement de citations réglée à {limit}" }, "report-notifyvoice": { "en": "Voiced users are being notified of reports", - "cy": "Defnyddwyr leisiwyd yn cael eu hysbysu o adroddiadau" + "cy": "Defnyddwyr leisiwyd yn cael eu hysbysu o adroddiadau", + "fr": "Les utilisateurs ayant la parole sont notifiés des rapports" }, "web-port": { "en": "Web is listening on {port}", - "cy": "We yn gwrando ar {port}" + "cy": "We yn gwrando ar {port}", + "fr": "Le web écoute sur {port}" }, "propaganda": { "en": "Contribute to the code on Github!", - "cy": "Cyfrannu at ay cod ar Github!" + "cy": "Cyfrannu at ay cod ar Github!", + "fr": "Contribuez au code sur GitHub!" }, "branch": { - "en": "{branch}" + "en": "{branch}", + "fr": "{branch}" }, "git": { "en": "version control", "de": "Versionsverwaltung", "es": "Control de versiones", - "fr": "Festion de versions", + "fr": "Gestion de versions", "it": "Controllo versione", "cy": "Rheoli fersiwn", "nl": "Versiebeheersysteem" @@ -230,7 +238,7 @@ }, "langhead-progress": { "en": "Translation Progress", - "fr": "Avancement de la Traduction", + "fr": "Avancement de la traduction", "cy": "Cynnydd Cyfieithu", "de": "Übersetzungsfortschritt" }, @@ -241,13 +249,16 @@ }, "outstanding-pullreq": { "en": "There are outstanding pull requests", - "cy": "Mae disgwyl ceisiadau tynnu" + "cy": "Mae disgwyl ceisiadau tynnu", + "fr": "Il a des requêtes pull en attente" }, "oaosidl": { - "en": "The Official Aberystwyth Open Source International Development League" + "en": "The Official Aberystwyth Open Source International Development League", + "fr": "La Ligue Officielle de Développement International Open Source d'Aberystwyth" }, "link-autotitle": { "en": "Automatically titling links in channels", - "cy": "Teitlo awtomatig cysylltiadau mewn sianelau" + "cy": "Teitlo awtomatig cysylltiadau mewn sianelau", + "fr": "Titre automatiquement les liens dans les channels" } } diff --git a/modules/quotes/README.md b/modules/quotes/README.md index c397a6c..fbb551d 100644 --- a/modules/quotes/README.md +++ b/modules/quotes/README.md @@ -11,42 +11,31 @@ This is the original reason that DBot was created, stores and displays quotes. #### rmLimit: 10 Amount of quotes which can be removed before admin approval is required. -### Commands +#### quotesOnJoin: false +Show quotes for users when they enter channels. + +### Commands: Quote Addition + +#### ~qadd [category] = [quote] +Add a new quote to the database under the given category. + +### Commands: Quote Retrieval + +#### ~[category] +Alias for ~q. #### ~q [category] Display a random quote from a given category. -#### ~qadd [category] = [quote] -Add a new quote to the database. +#### ~rq +Show a random quote from 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. +### Commands: Quote Removal #### ~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. @@ -55,10 +44,48 @@ and permanently delete them. Re-instate the quotes that are currently in the removal cache back into the main quote database. +#### ~rmlast [category] +Remove the last quote added to a given category. + +#### ~rm [category] = [quote] +Remove a given quote from the given category. + +### Commands: Stats & Searching + +#### ~qstats +Show a list of the biggest quote categories. + +#### ~qsearch [category] = [needle] +Search a category for quotes including the given text. If category is given as +_*_, then all categories will be searched for the given text. + +#### ~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. + +#### ~qrename [oldName] [newName] +Rename a quote category. + +#### ~qmerge [primary] [secondary] +Merge two quote categories into one. + +#### ~link [category] +Show a link to the page on the web interface which shows this category's quotes. + ### API -#### getQuote(event, category) -Returns a random quote from the given category. +#### addQuote(key, quote, user, callback) +Add the given quote to the category stored under the given key. If it's a new +quote category, then the owner of the category will be recorded as the given +user. The callback is called with one argument, the new quote count of the given +category. + +#### getQuote (key, callback) +Get a random quote from the given category. Callback is called with one +argument, either a random quote under the given key, or false if the category +doesn't exist. This getQuote function does not require a server argument, and as +such does not run the quote through the interpolation function (does not parse +~~category~~ etc). ### Removal Spam Protection diff --git a/modules/quotes/commands.js b/modules/quotes/commands.js index 6b4e20a..97229ad 100644 --- a/modules/quotes/commands.js +++ b/modules/quotes/commands.js @@ -1,13 +1,76 @@ -var _ = require('underscore')._; +var _ = require('underscore')._, + databank = require('databank'), + uuid = require('node-uuid'); var commands = function(dbot) { - var quotes = dbot.db.quoteArrs; var commands = { - // Alternative syntax to ~q + /*** Quote Addition ***/ + + // Add a quote to a category + '~qadd': function(event) { + var key = event.input[1].toLowerCase().trim(), + quote = event.input[2]; + + this.api.addQuote(key, quote, event.user, function(newCount) { + if(newCount) { + dbot.api.event.emit('~qadd', [ key, quote ]); + event.reply(dbot.t('quote_saved', { + 'category': key, + 'count': newCount + })); + } else { + event.reply(dbot.t('quote_exists')); + } + }); + }, + + /*** Quote Retrieval ***/ + + // Alternative ~q syntax '~': function(event) { commands['~q'].bind(this)(event); }, + // Retrieve quote from a category in the database. + '~q': function(event) { + var key = event.input[1]; + this.api.getInterpolatedQuote(event.server, event.channel, key, function(quote) { + if(quote) { + event.reply(key + ': ' + quote); + } else { + if(this.config.udFallback === true && _.has(dbot.modules, 'link')) { + dbot.api.link.udLookup(key, function(word, definition) { + if(word) { + event.reply(key + '[UD]: ' + definition); + } else { + event.reply(dbot.t('category_not_found', { 'category': key })); + } + }); + } else { + event.reply(dbot.t('category_not_found', { 'category': key })); + } + } + }.bind(this)); + }, + + // Choose a random quote category and a random quote from that + // TODO: This is quite inefficient, but databank must evolve to do otherwise. + '~rq': function(event) { + var categories = []; + this.db.scan('quote_category', function(result) { + if(result) { + categories.push(result); + } + }, function(err) { + var cIndex = _.random(0, _.size(categories) -1); + var qIndex = _.random(0, categories[cIndex].quotes.length - 1); + event.reply(categories[cIndex].name + ': ' + categories[cIndex].quotes[qIndex]); + }); + }, + + /*** Quote Removal ***/ + + // Show number of quotes in removal cache '~rmstatus': function(event) { var rmCacheCount = this.rmCache.length; if(rmCacheCount < dbot.config.quotes.rmLimit) { @@ -19,6 +82,7 @@ var commands = function(dbot) { } }, + // Confirm removal of quote cache '~rmconfirm': function(event) { var rmCacheCount = this.rmCache.length; this.rmCache.length = 0; @@ -26,213 +90,306 @@ var commands = function(dbot) { { 'count': rmCacheCount })); }, + // Reinstate all quotes in removal cache '~rmdeny': function(event) { var rmCache = this.rmCache; var rmCacheCount = rmCache.length; - for(var i=0;i 0) { + event.reply(dbot.t('search_results', { + 'category': matches[0].category, + 'needle': needle, + 'quote': matches[0].quote, + 'matches': matches.length + })); } else { - event.reply(dbot.t('q_not_exist_under', {'category': key, 'quote': quote})); + event.reply(dbot.t('no_results')); } - } else { - event.reply(dbot.t('category_not_found', {'category': key})); - } + }); } else { - event.reply(dbot.t('rmlast_spam')); + this.db.search('quote_category', { 'name': haystack }, function(result) { + category = result; + }, function(err) { + if(category) { + var matches = _.filter(category.quotes, function(quote) { + return quote.indexOf(needle) != -1; + }); + + if(matches.length == 0) { + event.reply(dbot.t('no_results')); + } else { + event.reply(dbot.t('search_results', { + 'category': haystack, + 'needle': needle, + 'quote': matches[0], + 'matches': matches.length + })); + } + } else { + event.reply(dbot.t('empty_category')); + } + }); } }, - + + // Count quotes in a given category or total quotes overall '~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 - })); + var key = input[1].trim().toLowerCase(), + category = false; + + this.db.search('quote_category', { 'name': key }, function(result) { + category = result; + }, function(err) { + if(category) { + event.reply(dbot.t('quote_count', { + 'category': key, + 'count': category.quotes.length + })); + } else { + event.reply(dbot.t('category_not_found', { 'category': key })); + } + }); + } else { + var quoteCount = 0; + this.db.scan('quote_category', function(category) { + if(category) { + quoteCount += category.quotes.length; + } + }, function(err) { + event.reply(dbot.t('total_quotes', { 'count': quoteCount })); + }); + } + }, + + // Rename a quote category + '~qrename': function(event) { + var oldName = event.input[1], + newName = event.input[2], + oldCategory = false, + newCategory = false; + + this.db.search('quote_category', { 'name': newName }, function(result) { + newCategory = result; + }, function(err) { + if(!newCategory) { + this.db.search('quote_category', { 'name': oldName }, function(result) { + oldCategory = result; + }, function(err) { + oldCategory.name = newName; + this.db.save('quote_category', oldCategory.id, oldCategory, function(err) { + event.reply(dbot.t('category_renamed', { + 'oldName': oldName, + 'newName': newName + })); + }); + }.bind(this)); } else { - event.reply(dbot.t('no_quotes', { 'category': key })); + event.reply(dbot.t('newcat_exists', { 'newcat': newName })); } - } 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 })); - } + }.bind(this)); }, - '~qadd': function(event) { - var key = event.input[1].toLowerCase().trim(); - var text = event.input[2].trim(); - if(!_.isArray(quotes[key])) { - quotes[key] = []; - } + // Merge a quote category insto another + '~qmerge': function(event) { + var primaryName = event.input[1], + secondName = event.input[2], + primary = false, + secondary = false; - 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; + this.db.search('quote_category', { 'name': primaryName }, function(result) { + primary = result; + }, function(err) { + if(primary) { + this.db.search('quote_category', { 'name': secondName }, function(result) { + secondary = result; + }, function(err) { + if(secondary) { + primary.quotes = _.union(primary.quotes, secondary.quotes); + this.db.save('quote_category', primary.id, primary, function(err) { + this.db.del('quote_category', secondary.id, function(err) { + event.reply(dbot.t('categories_merged', { + 'from': secondName, + 'into': primaryName + })); + }); + }.bind(this)); + } else { + event.reply(dbot.t('category_not_found', { 'category': secondName })); + } + }.bind(this)); + } else { + event.reply(dbot.t('category_not_found', { 'category': primaryName })); + } + }.bind(this)); }, - '~rq': function(event) { - if(_.keys(quotes).length > 0) { - var category = _.keys(quotes)[_.random(0, _.size(quotes) -1)]; - event.reply(category + ': ' + - this.internalAPI.interpolatedQuote(event.server, event.channel.name, event.user, category)); - } else { - event.reply(dbot.t('no_results')); - } - }, - + // Link to quote web page '~link': function(event) { - var key = event.input[1].toLowerCase(); - if(_.has(quotes, key)) { - if(_.has(dbot.config, 'web') && _.has(dbot.config.web, 'webHost') && - _.has(dbot.config.web, 'webPort')) { - event.reply(dbot.t('quote_link', { - 'category': key, - 'url': dbot.api.web.getUrl('quotes/' + encodeURIComponent(key)) - })); + var key = event.input[1].toLowerCase(), + category = false; + + this.db.search('quote_category', { 'name': key }, function(result) { + category = result; + }, function(err) { + if(category) { + if(_.has(dbot.modules, 'web')) { + event.reply(dbot.t('quote_link', { + 'category': key, + 'url': dbot.api.web.getUrl('quotes/' + encodeURIComponent(key)) + })); + } else { + event.reply(dbot.t('web_not_configured')); + } } else { - event.reply(dbot.t('web_not_configured')); + event.reply(dbot.t('category_not_found', { 'category': 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['~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-]+[\d\w\s-]*)[ ]?=[ ]?(.+)$/, 3]; + commands['~qrename'].regex = [/^~qrename ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3]; + commands['~qmerge'].regex = [/^~qmerge ([\d\w\s-]+?)[ ]?=[ ]?(.+)$/, 3]; commands['~link'].regex = [/^~link ([\d\w\s-]*)/, 2]; commands['~rmconfirm'].access = 'moderator'; commands['~rmdeny'].access = 'moderator'; + commands['~qrename'].access = 'moderator'; + commands['~qmerge'].access = 'moderator'; return commands; }; diff --git a/modules/quotes/config.json b/modules/quotes/config.json index b233bb0..d0fbe4a 100644 --- a/modules/quotes/config.json +++ b/modules/quotes/config.json @@ -1,9 +1,8 @@ { - "dbKeys": [ "quoteArrs" ], - "dependencies": [ "command", "users", "event" ], + "dbType": "redis", + "dependencies": [ "users", "event" ], "rmLimit": 10, "ignorable": true, "quotesOnJoin": false, - "udFallback": false, - "help": "http://github.com/reality/depressionbot/blob/master/modules/quotes/README.md" + "udFallback": false } diff --git a/modules/quotes/pages.js b/modules/quotes/pages.js index 7800cd0..abdd4c3 100644 --- a/modules/quotes/pages.js +++ b/modules/quotes/pages.js @@ -3,23 +3,39 @@ 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.' }); - } + this.api.getQuoteCategory(req.params.key, function(category) { + if(category) { + res.render('quotes', { + 'name': dbot.config.name, + 'quotes': category.quotes, + '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) }); + this.api.getCategoryKeys(function(keys) { + res.render('quotelist', { + 'name': dbot.config.name, + 'quotelist': keys + }); + }); }, - // 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() } }); + '/quoteremovals': function(req, res) { + res.render('quotes', { + 'name': dbot.config.name, + 'quotes': _.pluck(this.rmCache, 'quote') + }); } } }; diff --git a/modules/quotes/quotes.js b/modules/quotes/quotes.js index 5e52357..4f3e86c 100644 --- a/modules/quotes/quotes.js +++ b/modules/quotes/quotes.js @@ -1,50 +1,46 @@ -var _ = require('underscore')._; +var _ = require('underscore')._, + uuid = require('node-uuid'); var quotes = function(dbot) { dbot.sessionData.rmCache = []; - this.quotes = dbot.db.quoteArrs, - this.addStack = [], - this.rmAllowed = true, - this.rmCache = dbot.sessionData.rmCache, + this.rmCache = dbot.sessionData.rmCache; + this.quotes = dbot.db.quoteArrs; + this.rmAllowed = true; this.rmTimer; this.internalAPI = { - // Retrieve a random quote from a given category, interpolating any quote - // references (~~QUOTE CATEGORY~~) within it - 'interpolatedQuote': function(server, channel, user, key, quoteTree) { - if(!_.isUndefined(quoteTree) && quoteTree.indexOf(key) != -1) { - return ''; - } else if(_.isUndefined(quoteTree)) { - quoteTree = []; - } - - var index = _.random(0, this.quotes[key].length - 1); - var quoteString = this.quotes[key][index]; - - // Parse quote interpolations - var quoteRefs = quoteString.match(/~~([\d\w\s-]*)~~/g); - var thisRef; - - while(quoteRefs && (thisRef = quoteRefs.shift()) !== undefined) { - var cleanRef = dbot.cleanNick(thisRef.replace(/^~~/,'').replace(/~~$/,'').trim()); - if(cleanRef === '-nicks-') { - var randomNick = dbot.api.users.getRandomChannelUser(server, channel); - quoteString = quoteString.replace("~~" + cleanRef + "~~", randomNick); - quoteTree.pop(); - } else if(cleanRef === '-user-') { - quoteString = quoteString.replace("~~" + cleanRef + "~~", user); - quoteTree.pop(); - } else if(_.has(this.quotes, cleanRef)) { - quoteTree.push(key); - quoteString = quoteString.replace("~~" + cleanRef + "~~", - this.internalAPI.interpolatedQuote(server, channel, user, cleanRef, quoteTree.slice())); - quoteTree.pop(); + // Parse quote interpolations + 'interpolatedQuote': function(server, channel, key, quote, callback) { + var quoteRefs = quote.match(/~~([\d\w\s-]*)~~/g); + if(quoteRefs) { + var ref = this.internalAPI.cleanRef(quoteRefs[0].replace(/^~~/,'').replace(/~~$/,'').trim()); + if(ref === '-nicks-') { + dbot.api.users.getRandomChannelUser(server, channel, function(user) { + quote = quote.replace('~~' + ref + '~~', randomNick); + this.internalAPI.interpolatedQuote(server, channel, key, quote, callback); + }.bind(this)); + } else { + this.api.getQuote(ref, function(interQuote) { + if(!interQuote || ref == key) { + interQuote = ''; + } + quote = quote.replace('~~' + ref + '~~', interQuote); + this.internalAPI.interpolatedQuote(server, channel, key, quote, callback); + }.bind(this)); } + } else { + callback(quote); } - - return quoteString; }.bind(this), + 'cleanRef': function(key) { + key = key.toLowerCase(); + while(key.slice(-1) == '_') { + key = key.substring(0, key.length-1); + } + return key; + }, + 'resetRemoveTimer': function(event, key, quote) { this.rmAllowed = false; setTimeout(function() { @@ -57,7 +53,7 @@ var quotes = function(dbot) { }); clearTimeout(this.rmTimer); - if(this.rmCache.length < dbot.config.quotes.rmLimit) { + if(this.rmCache.length < this.config.rmLimit) { this.rmTimer = setTimeout(function() { this.rmCache.length = 0; // lol what }.bind(this), 600000); @@ -70,37 +66,81 @@ var quotes = function(dbot) { }; this.api = { - 'getQuote': function(event, category) { - var key = category.trim().toLowerCase(); - var altKey; - if(key.split(' ').length > 0) { - altKey = key.replace(/ /g, '_'); - } + 'addQuote': function(key, quote, user, callback) { + var key = key.toLowerCase().trim(), + newCount, + category = false; - if(key.charAt(0) !== '_') { // lol - if(_.has(this.quotes, key)) { - return this.internalAPI.interpolatedQuote(event.server, event.channel.name, event.user, key); - } else if(_.has(this.quotes, altKey)) { - return this.internalAPI.interpolatedQuote(event.server, event.channel.name, event.user, altKey); + this.db.search('quote_category', { 'name': key }, function(result) { + category = result; + }, function(err) { + if(!category) { + var id = uuid.v4(); + category = { + 'id': id, + 'name': key, + 'quotes': [], + 'owner': user + }; + } + + if(_.include(category.quotes, quote)) { + callback(false); } else { - return false; + newCount = category.quotes.push(quote); + this.db.save('quote_category', category.id, category, function(err) { + this.rmAllowed = true; + callback(newCount); + }.bind(this)); } - } + }.bind(this)); + + }, + + 'getQuote': function(key, callback) { + this.api.getQuoteCategory(key, function(category) { + if(category) { + var quotes = category.quotes; + var index = _.random(0, quotes.length - 1); + callback(quotes[index]); + } else { + callback(false); + } + }); }, - 'getQuoteCategory': function(name) { - console.log(name); - var key = name.trim().toLowerCase(); - if(_.has(this.quotes, key)) { - return this.quotes[key]; - } else { - return false; - } + 'getInterpolatedQuote': function(server, channel, key, callback) { + key = key.trim().toLowerCase(), + + this.api.getQuote(key, function(quote) { + if(quote) { + this.internalAPI.interpolatedQuote(server, channel, key, quote, callback); + } else { + callback(quote); + } + }.bind(this)); + }, + + 'getQuoteCategory': function(key, callback) { + var category = false, + key = key.trim().toLowerCase(); + + this.db.search('quote_category', { 'name': key }, function(result) { + category = result; + }, function(err) { + callback(category); + }); + }, + + 'getCategoryKeys': function(callback) { + var keys = []; + this.db.scan('quote_category', function(result) { + if(result) keys.push(result.name); + }, function(err) { + callback(keys); + }); } }; - - this.api['getQuoteCategory'].external = true; - this.api['getQuoteCategory'].extMap = [ 'name' ]; this.listener = function(event) { if(event.action == 'PRIVMSG') { @@ -111,17 +151,17 @@ var quotes = function(dbot) { } if(once) { - event.message = '~qadd realityonce=reality ' + once[1]; - event.action = 'PRIVMSG'; - event.params = event.message.split(' '); - dbot.instance.emit(event); - } + this.api.addQuote('realityonce', 'reality' + once[1], event.user, function(newCount) { + event.reply('\'reality ' + once[1] + '\' saved (' + newCount + ').'); + }); + } } else if(event.action == 'JOIN') { if(this.config.quotesOnJoin == true) { - var userQuote = this.api.getQuote(event, event.user) - if(userQuote) { - event.reply(event.user + ': ' + this.api.getQuote(event, event.user)); - } + this.api.getQuote(event.user, function(quote) { + if(quote) { + event.reply(event.user + ': ' + quote); + } + }); } } }.bind(this); diff --git a/modules/quotes/strings.json b/modules/quotes/strings.json index 461efbd..24baacc 100644 --- a/modules/quotes/strings.json +++ b/modules/quotes/strings.json @@ -5,7 +5,8 @@ "na'vi": "{category} yawne ke lu kawturu.", "cy": "Does neb yn caru {category}", "nl": "Niemand houdt van {category}", - "de": "Niemand hat {category} gerne" + "de": "Niemand hat {category} gerne", + "fr": "Personne n'aime {category}" }, "large_categories": { "en": "Largest categories: ", @@ -13,7 +14,8 @@ "na'vi": "U atsawl: ", "cy": "Categoriau mwyaf: ", "nl": "Grootste categoriën: ", - "de": "Größte Kategorien: " + "de": "Größte Kategorien: ", + "fr": "Les plus grandes catégories :" }, "empty_category": { "en": "That category has no quotes in. Commence incineration.", @@ -21,7 +23,8 @@ "na'vi": "Tsauru upxare lu. Nga skxawng lu.", "cy": "Nid yw'r categori yna efo dyfyniadau. Cychwyn orfflosgiad", "nl": "Die categorie heeft geen quotes. Bereid het verbrandingsritueel voor.", - "de": "In dieser Kategorie befinden sich keine Zitate. Verbennung einleiten." + "de": "In dieser Kategorie befinden sich keine Zitate. Verbennung einleiten.", + "fr": "Cette catégorie ne contient aucune citation. Commencer l'incinération." }, "no_results": { "en": "No results found.", @@ -29,7 +32,8 @@ "na'vi": "Oel kea humit rìmun", "cy": "Dim canlyniadau ar gael", "nl": "Geen resultaten gevonden.", - "de": "Keine Ergebnisse gefunden." + "de": "Keine Ergebnisse gefunden.", + "fr": "Aucun résultat trouvé." }, "locked_category": { "en": "{category} is locked. Commence incineration.", @@ -37,7 +41,8 @@ "na'vi": "{category} ke fkeytok set. Nga skxawng lu nafì'u", "cy": "Mae {category} wedi cloi. Cychwyn orfflosgiad", "nl": "{category} is op slot. Bereid het verbrandingsritueel voor.", - "de": "{category} ist geschlossen. Verbrennung einleiten." + "de": "{category} ist geschlossen. Verbrennung einleiten.", + "fr": "{category} est verrouillé(e). Commencer l'incinération." }, "no_quotes": { "en": "No quotes exist under {category}", @@ -45,7 +50,8 @@ "na'vi": "Kea upxare fkeytok {category}mì", "cy": "Does dim dyfyniadau gan {category}", "nl": "Er zijn geen quotes in {category}", - "de": "Es existieren keine Ziatate in {category}" + "de": "Es existieren keine Ziatate in {category}", + "fr": "Il n'existe aucune citation dans {category}" }, "last_removed": { "en": "Last quote removed from {category}.", @@ -53,7 +59,8 @@ "na'vi": "Oel 'upxareti aham 'aku {category}ta", "cy": "Dyfyniad olaf wedi ei ddileu o {category}", "nl": "De laatste quote is verwijderd uit {category}.", - "de": "Das letzte Zitat wurde aus {category} entfernt." + "de": "Das letzte Zitat wurde aus {category} entfernt.", + "fr": "Dernière citation enlevée de {category}." }, "no_recent_adds": { "en": "No quotes were added recently.", @@ -61,7 +68,8 @@ "na'vi": "Kea upxareti samung nìfkrr", "cy": "Nid oes unrhyw dyfyniadau wedi ei ychwwanegu'n ddiweddar", "nl": "Er zijn recentelijk geen quotes toegevoegd.", - "de": "Es wurden neulich keine Zitate hinzugefügt." + "de": "Es wurden neulich keine Zitate hinzugefügt.", + "fr": "Aucune citation n'a été ajoutée récemment." }, "rmlast_spam": { "en": "No spamming that shit. Try again in a few minutes...", @@ -69,7 +77,8 @@ "na'vi": "Nga Tsasngelit ke zene fpivere'. Sweylu nga fmivi ye'rìn...", "cy": "Peidiwch a sbamio hwna. Triwch eto mewn ychydyg funudau...", "nl": "Dat spammen kun je ook lekker laten. Probeer het zometeen nog eens...", - "de": "Hör auf zu spammen. Versuche es in einigen Minuten erneut..." + "de": "Hör auf zu spammen. Versuche es in einigen Minuten erneut...", + "fr": "Pas de spam de cette merde. Réessaye dans quelques minutes..." }, "removed_from": { "en": "'{quote}' removed from {category}", @@ -77,7 +86,8 @@ "na'vi": "'{quote}'(it/ti) 'ìyaku {category}", "cy": "'{quote}' wedi ei ddileu o {category}", "nl": "{quote} verwijderd uit {category}", - "de": "'{quote} aus {category} gelöscht" + "de": "'{quote} aus {category} gelöscht", + "fr": "'{quote}' enlevé(e) {category}" }, "q_not_exist_under": { "en": "'{quote}' doesn't exist under '{category}'.", @@ -85,7 +95,8 @@ "na'vi": "'{quote}' ke fkeytok '{category}'ta.", "cy": "Nid yw '{quote}' yn bodoli yn '{category}'", "nl": "{quote} bestaat niet in '{category}'.", - "de": "'{quote} existiert nicht in '{category}'." + "de": "'{quote} existiert nicht in '{category}'.", + "fr": "'{quote}' ne semble pas exister dans '{category}'." }, "total_quotes": { "en": "Total quote count: {count}.", @@ -93,7 +104,8 @@ "na'vi": "'upxareri holpxay: {count}.", "cy": "Cyfanswm dyfyniadau: {count}.", "nl": "Totaal aantal quotes: {count}.", - "de": "Anzahl Zitate insgesamt: {count}." + "de": "Anzahl Zitate insgesamt: {count}.", + "fr": "Nombre total de citations: {count}." }, "quote_exists": { "en": "Quote already in DB. Initiate incineration.", @@ -101,7 +113,8 @@ "na'vi": "'Upxarel säomumit fìtsengit tok srekrr. Nga skxawng lu.", "cy": "Dyfyniad yn y gronfa ddata yn barod. Cychwyn orfflosgiad", "nl": "De quote bestaat al. Bereid het verbrandingsritueel voor.", - "de": "Zitat ist bereits in der Datenbank. Verbennung einleiten." + "de": "Zitat ist bereits in der Datenbank. Verbennung einleiten.", + "fr": "La citation existe déjà dans la base de données. Initier l'incinération." }, "quote_saved": { "en": "Quote saved in '{category}' ({count}).", @@ -109,7 +122,8 @@ "na'vi": "Oe zayerok '{category}'mì ({count}).", "cy": "Dyfyniad wedi ei gadw yn '{category}' ({count}).", "nl": "Quote opgeslagen in '{category}' ({count}).", - "de": "Zitat in '{category}' gespeichert ({count})." + "de": "Zitat in '{category}' gespeichert ({count}).", + "fr": "Citation sauvegardée dans '{category}' ({count})." }, "quote_replace": { "en": "No replacing arrays, you whore.", @@ -117,7 +131,8 @@ "na'vi": "Ngal fìsäomumit ke tsun rivawn. Nga muntxa sayi suteo hrh.", "cy": "Peidiwch a newid rhestrau, y cachgi", "nl": "Geen arrays vervangen, slet.", - "de": "Ersetze keine Arrays, du Schlampe." + "de": "Ersetze keine Arrays, du Schlampe.", + "fr": "On ne remplace pas les arrays, espèce de salope." }, "quote_count": { "en": "{category} has {count} quotes.", @@ -125,7 +140,8 @@ "na'vi": "{count}a upxare {category}ur lu.", "cy": "{count} dyfyniad yn {category}", "nl": "{category} heeft {count} quotes.", - "de": "{count} Zitate befinden sich in {category}." + "de": "{count} Zitate befinden sich in {category}.", + "fr": "{category} contient {count} citations." }, "quote_link": { "en": "Link to {category} - {url}", @@ -133,7 +149,8 @@ "na'vi": "Fya'o {category}ne - {url}", "cy": "Dolen i {category} - {url}", "nl": "Link naar {category} - {url}", - "de": "Link zu {category} - {url}" + "de": "Link zu {category} - {url}", + "fr": "Lien vers {category} - {url}" }, "search_results": { "en": "{category} ({needle}): '{quote}' [{matches} results]", @@ -141,47 +158,66 @@ "na'vi": "{category} ({needle}): '{quote}' [kum a{matches}]", "cy": "{category} ({needle}): '{quote}' [{matches} canlyniad]", "nl": "{category} ({needle}): '{quote}' [{matches} resultaten]", - "de": "{category} ({needle}): '{quote}' [{matches} Ergebnisse]" + "de": "{category} ({needle}): '{quote}' [{matches} Ergebnisse]", + "fr": "{category} ({needle}): '{quote}' [{matches} résultats]" }, "quote_cache_auto_remove": { "en": "There are {count} quotes in the removal cache, which will be automatically cleared.", "na'vi": "{count}a 'upxarel sngelit tok, Oel 'ayku sngelit lukenga.", "cy": "Mae {count} dyfyniadau yn y celc dileu a fydd yn cael ei clirio yn awtomatig.", "nl": "Er staan {count} quotes klaar om verwijderd te worden, wat automatisch zal gebeuren.", - "de": "Es befinden sich {count} im Cache, die automatisch entfernt werden." + "de": "Es befinden sich {count} im Cache, die automatisch entfernt werden.", + "fr": "Il y a {count} citations dans le cache de suppression, qui va être automatiquement vidé." }, "quote_cache_manual_remove": { "en": "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", "cy": "Mae {count} dyfyniadau yn y celc dileu a fydd yn cael ei clirio â llaw.", "nl": "Er staan {count} quotes klaar om verwijderd te worden, wat handmatig zal moeten gebeuren.", - "de": "Es befinden sich {count} im Cache, die manuell entfernt werden müssen." + "de": "Es befinden sich {count} im Cache, die manuell entfernt werden müssen.", + "fr": "Il y a {count} citations dans le cache de suppression, qui doit être manuellement vidé." }, "quote_cache_cleared": { "en": "{count} quotes cleared from the removal cache.", "na'vi": "Oel 'aìmku {count}a 'upxareti ta sngel.", "cy": "{count} dyfyniadau wedi ei clirio o'r celc dileu", "nl": "{count} quotes verwijderd uit de verwijderlijst.", - "de": "{count} Zitate wurden aus dem Cache entfernt." + "de": "{count} Zitate wurden aus dem Cache entfernt.", + "fr": "{count} citation(s) supprimée(s) du cache de suppression." }, "quote_cache_reinstated": { "en": "{count} quotes reinstated from the removal cache.", "na'vi": "{count}a 'upxare tolätxaw ta sngel.", "cy": "{count} dyfyniadau wedi ei adfer o'r celc dileu", "nl": "{count} quotes hersteld van de verwijderlijst.", - "de": "{count} Zitate wurden aus dem Cache gerettet." + "de": "{count} Zitate wurden aus dem Cache gerettet.", + "fr": "{count} citation(s) restaurée(s) du cache de suppression." }, "rm_cache_limit": { "en": "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.", "cy": "Sylw: Gormod o dyfyniadau wedi ei clirio, rhaid i'r celc dileu yn cael ei glirio neu adfer â llaw gyda ~rmconfirm neu ~rmdeny.", "nl": "Waarschuwing: Er zijn te veel quotes verwijderd. rmCache moet opgeschoond of herinstalleerd worden met ~rmconfirm of ~rmdeny", - "de": "Achtung: Zu viele Zitate gelöscht, rmCache muss geleert werden oder manuell via ~rmconfirm oder ~rmdeny wiedereingesetzt werden." + "de": "Achtung: Zu viele Zitate gelöscht, rmCache muss geleert werden oder manuell via ~rmconfirm oder ~rmdeny wiedereingesetzt werden.", + "fr": "Attention : Trop de citations enlevées, rmCache doit être vidé ou restauré manuellement avec ~rmconfirm ou ~rmdeny." }, "web_not_configured": { "en": "Cannot link to category. Web module is either not loaded or misconfigured.", "cy": "Ni all gysylltu â gategori. Modiwl we yn naill ai nid lwytho neu wedi ei camffurfweddu.", "nl": "De categorie kan niet gelinkt worden. De web module is niet geladen of onjuist geconfigureerd.", - "de": "Diese Kategorie kann nicht gelinkt werden. Das Web-Modul ist entweder nicht geladen oder falsch konfiguriert." + "de": "Diese Kategorie kann nicht gelinkt werden. Das Web-Modul ist entweder nicht geladen oder falsch konfiguriert.", + "fr": "Impossible de donner le lien vers la catégorie. Le module web n'est pas chargé ou est mal configuré." + }, + "category_renamed": { + "en": "Category ~{oldName} renamed to ~{newName}.", + "fr": "Catégorie ~{oldName} renommée ~{newName}." + }, + "newcat_exists": { + "en": "Category ~{newcat} already exists. Delete that first or use ~qmerge.", + "fr": "La catégorie ~{newcat} existe déjà. Supprimez-la en premier ou utilisez ~qmerge." + }, + "categories_merged": { + "en": "Category ~{from} and its quotes merged into ~{into}.", + "fr": "La catégorie ~{from} et ses citations ont été fusionnée dans ~{into}." } } diff --git a/modules/rain/config.json b/modules/rain/config.json index f176f2a..afffe93 100644 --- a/modules/rain/config.json +++ b/modules/rain/config.json @@ -1,6 +1,5 @@ { "dependencies": [ "command" ], "ignorable": true, - "help": "http://github.com/reality/depressionbot/blob/master/modules/rain/README.md", "apikey": "7707e78c7125741e" } diff --git a/modules/rain/strings.json b/modules/rain/strings.json index 53d2316..3b7a6d2 100644 --- a/modules/rain/strings.json +++ b/modules/rain/strings.json @@ -4,96 +4,110 @@ "na'vi": "tompa ke zup {place}ur.", "cy": "Dyw hi ddim yn bwrw glaw yn {place}.", "nl": "Het regent niet in {place}.", - "de": "Es ist trocken in {place}." + "de": "Es ist trocken in {place}.", + "fr": "Il ne pleut pas à {place}." }, "rain-1": { "en" : "It's raining in {place}.", "na'vi":"tompa zup {place}ur.", "cy": "Mae'n bwrw glaw yn {place}.", "nl": "Het regent in {place}.", - "de": "Es regnet in {place}." + "de": "Es regnet in {place}.", + "fr": "Il pleut à {place}." }, "rain-2": { "en" : "It's raining rather a lot in {place}.", "na'vi":"tompa zup {place}ur nìhawng.", "cy": "Mae'n bwrw glaw cryn dipyn yn {place}.", "nl": "Het regent tamelijk hard in {place}.", - "de": "Es regnet relativ viel in {place}." + "de": "Es regnet relativ viel in {place}.", + "fr": "Il pleut assez fortement à {place}." }, "rain-3": { "en" : "It's raining shitloads.", "na'vi":"tompa zup {place}ur nìhawng.", "cy": "Mae'n bwrw glaw yn uffern o lawer yn {place}.", "nl": "Het regent nu toch wel erg hard.", - "de": "Es regnet stark." + "de": "Es regnet stark.", + "fr": "Il pleut à mort." }, "rain-4": { "en" : "It's raining fucktons.", "na'vi": "tompa zup {place}ur nìhawng.", "cy": "Mae'n bwrw glaw yn drwm iawn yn {place}.", "nl": "De regen komt verdorie met bakken uit de lucht.", - "de": "Es regnet extrem stark." + "de": "Es regnet extrem stark.", + "fr": "Il pleut vraiment des putains de tonnes." }, "rain-5": { "en" : "It's raining whales (in {place})!", "na'vi": "payoang zup {place}ur", "cy": "Mae'n bwrw glaw cathod a chŵn yn {place}.", "nl": "Een punker krijgt z'n hanekam niet meer omhoog gehouden.", - "de": "Es regnet unglaublich stark (in {place})!" + "de": "Es regnet unglaublich stark (in {place})!", + "fr": "Il pleut des baleines (à {place}) !" }, "rain-6": { "en" : "IT'S SO FUCKING WET OUT HERE MAN", "na'vi" : "pey lu oeru nìhawng taluna tok fìtengeti", "cy": "MAE'N WIR GWLYB TU ALLAN.", "nl": "Dit is dus een inloopdouche.", - "de": "ES IST SO VERDAMMT NASS HIER MANN." + "de": "ES IST SO VERDAMMT NASS HIER MANN.", + "fr": "C'EST TELLEMENT MOUILLE DEHORS MEC" }, "rain-7": { "en" : "I AM SO MOIST RIGHT NOW", "na'vi":"pey lu oeru nìhawng taluna tok fìtengeti", "cy": "RYDW I'N LLAITH IAWN AR HYN O BRYD", "nl": "IK VOEL ME NU TOCH ANDERS WEL BEHOORLIJK ONDER GEZEKEN.", - "de": "ICH BIN SO EINGENÄSST." + "de": "ICH BIN SO EINGENÄSST.", + "fr": "JE SUIS TELLEMENT MOITE LA" }, "rain-8": { "en" : "You used a #3 pencil instead of #2, didn't you?", "na'vi": "Nga pamrel sami hu pencìl a#3 ulte ke pamrel sami pencìl a#2 kefyak?", "cy": "Rydych yn defnyddio #2 pensil yn hytrach na #3 pensil, ie?", "nl": "PROOST.", - "de": "Jemand muss dich hassen" + "de": "Jemand muss dich hassen", + "fr": "T'as utilisé un crayon #3 au lieu d'un #2, n'est-ce pas ?" }, "rain-9": { "en" : "WELCOME TO ATLANTIS", "na'vi": "ziva'u nìprrte atlantisftu.", "cy": "CROESO I ATLANTIS!", "nl": "HAD IK GEVRAAGD OM EEN OMMETJE ATLANTIS?!", - "de": "WILLKOMMEN IN ATLANTIS" + "de": "WILLKOMMEN IN ATLANTIS", + "fr": "BIENVENUE A ATLANTIS" }, "rain-10": { "en" : "GET INSIDE", "na'vi": "Sweylu txo nga livatam futa ngal fìtsengeti tok", "nl": "SCHUIL, BITCH! SCHUIL!", - "de": "REIN MIT DIR" + "de": "REIN MIT DIR", + "fr": "RENTRE CHEZ TOI" }, "rain-11": { "en" : "LOL U PROBABLY DIED", "na'vi": "hrh kxawm nga tolerkup.", "cy": "EWCH I MEWN", "nl": "HA, NU BEN JE TOCH ZEKER VERZOPEN?!", - "de": "LOL DU MIST VERMUTLICH TOT" + "de": "LOL DU MIST VERMUTLICH TOT", + "fr": "LOL T'ES PROBABLEMENT MORT" }, "rain-e": { "en" : "Unable to fetch weather data.", "na'vi":"Oel ke tsun rivum sänumeti teriyafkeyk.", "cy": "Nid gallu nôl data tywydd.", "nl": "Geen weerinformatie beschikbaar.", - "de": "Keine Wetterdaten vorhanden." + "de": "Keine Wetterdaten vorhanden.", + "fr": "Impossible de récupérer les données météo." }, "rain-u": { "en": "Location too ambiguous to process.", "na'vi":"tsengeri, yayayr lu oeru.", "cy": "Lleoliad yn rhy amwys i brosesu.", "nl": "Deze locatie is te onduidelijk.", - "de": "Wähle den nächst größeren Ort." + "de": "Wähle den nächst größeren Ort.", + "fr": "Location trop ambiguë à traiter." } } diff --git a/modules/reddit/config.json b/modules/reddit/config.json index f934fa5..5a94458 100644 --- a/modules/reddit/config.json +++ b/modules/reddit/config.json @@ -1,3 +1,4 @@ { - "outputPrefix": "\u000312reddit\u000f" + "outputPrefix": "\u000312reddit\u000f", + "dependencies": [ "link" ] } diff --git a/modules/reddit/strings.json b/modules/reddit/strings.json index c862277..7400e26 100644 --- a/modules/reddit/strings.json +++ b/modules/reddit/strings.json @@ -2,20 +2,24 @@ "about_subreddit": { "en": "[{display_name} has {subscribers} subscribers ({active} active)]", "cy": "[Mae {display_name} wedi {subscribers} subscribers ({active} active)]", - "de": "[{display_name} hat {subscribers} Abonnenten ({active} active)]" + "de": "[{display_name} hat {subscribers} Abonnenten ({active} active)]", + "fr": "[{display_name} a {subscribers} abonnés ({active} actifs)]" }, "about_post": { "en": "[Post by {poster} in {subreddit} — Comments: {comments}, Score: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)] — {url}", "cy": "[Postio gan {poster} yn {subreddit} - Sylwadau: {comments}, Sgôr: {score} (\u00039]▲{up}\u000f|\u000312{down}▼\u000f)] — {url}", - "de": "[Beitrag von {poster} in {subreddit} — Kommentare: {comments}, Bewertung: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)] — {url}" + "de": "[Beitrag von {poster} in {subreddit} — Kommentare: {comments}, Bewertung: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)] — {url}", + "fr": "[Message de {poster} dans {subreddit} — Commentaires: {comments}, Score: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)] — {url}" }, "about_comment": { "en": "[Comment by {poster} in {subreddit} — Score: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)]", "cy": "[Sylw gan {poster} yn {subreddit} - Sgôr: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)]", - "de": "[Kommentar von {poster} in {subreddit} — Bewertung: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)]" + "de": "[Kommentar von {poster} in {subreddit} — Bewertung: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)]", + "fr": "[Commentaire de {poster} dans {subreddit} — Score: {score} (\u00039▲{up}\u000f|\u000312{down}▼\u000f)]" }, "nsfw": { "en": "[NSFW]", - "cy": "[Anniogel Ar Gwaith (AAG)]" + "cy": "[Anniogel Ar Gwaith (AAG)]", + "fr": "[Risqué Pour le Travail (NSFW)]" } } diff --git a/modules/regex/config.json b/modules/regex/config.json index a2a42c7..c945e96 100644 --- a/modules/regex/config.json +++ b/modules/regex/config.json @@ -1,4 +1,3 @@ { - "ignorable": true, - "help": "http://github.com/reality/depressionbot/blob/master/modules/regex/README.md" + "ignorable": true } diff --git a/modules/report/config.json b/modules/report/config.json index c495082..975bd13 100644 --- a/modules/report/config.json +++ b/modules/report/config.json @@ -1,6 +1,6 @@ { "ignorable": true, "notifyVoice": false, - "dependencies": [ "command", "users" ], - "help": "http://github.com/reality/depressionbot/blob/master/modules/report/README.md" + "dependencies": [ "users" ], + "dbType": "redis" } diff --git a/modules/report/pages.js b/modules/report/pages.js new file mode 100644 index 0000000..e8bbc89 --- /dev/null +++ b/modules/report/pages.js @@ -0,0 +1,44 @@ +var _ = require('underscore')._; + +var pages = function(dbot) { + return { + '/notify': function(req, res) { + res.render('servers', { + 'name': dbot.config.name, + 'servers': _.keys(dbot.config.servers) + }); + }, + + '/notify/:server': function(req, res) { + var server = req.params.server; + res.render('channels', { + 'name': dbot.config.name, + 'server': server, + 'channels': _.keys(dbot.instance.connections[server].channels) + }); + }, + + '/notify/:server/:channel': function(req, res) { + var server = req.params.server, + channel = req.params.channel, + notifies = []; + + this.db.search('notifies', { + 'server': server, + 'channel': channel + }, function(notify) { + notifies.push(notify); + }, function(err) { + res.render('notifies', { + 'name': dbot.config.name, + 'server': server, + 'notifies': notifies + }); + }); + } + }; +}; + +exports.fetch = function(dbot) { + return pages(dbot); +}; diff --git a/modules/report/report.js b/modules/report/report.js index 9819f43..b196147 100644 --- a/modules/report/report.js +++ b/modules/report/report.js @@ -1,6 +1,11 @@ -var _ = require('underscore')._; +var _ = require('underscore')._, + uuid = require('node-uuid'), + async = require('async'); var report = function(dbot) { + if(!dbot.db.pending) dbot.db.pending = {}; + this.pending = dbot.db.pending; + this.api = { 'notify': function(server, channel, message) { var channel = dbot.instance.connections[server].channels[channel]; @@ -12,42 +17,78 @@ var report = function(dbot) { } }, this); - _.each(ops, function(user) { - dbot.say(server, user.name, message); - }, this); + dbot.api.users.getChannel(server, channel, function(channel) { + if(channel) { + var perOps = channel.op; + if(this.config.notifyVoice) pOps = _.union(perOps, channel.voice); + + async.eachSeries(ops, function(nick, next) { + dbot.api.users.resolveUser(server, nick, function(user) { + perOps = _.without(perOps, user.id); next(); + }); + }, function() { + offlineUsers = perOps; + _.each(offlineUsers, function(id) { + if(!this.pending[id]) this.pending[id] = []; + this.pending[id].push(message); + }.bind(this)); + }.bind(this)); + } + }.bind(this)); + + var i = 0; + var notifyChannel = function(ops) { + if(i >= ops.length) return; + dbot.say(server, ops[i].name, message); + setTimeout(function() { + i++; notifyChannel(ops); + }, 1000); + }; + notifyChannel(ops); } }; + this.listener = function(event) { + if(_.has(this.pending, event.rUser.id)) { + _.each(this.pending[event.rUser.id], function(message) { + dbot.say(event.server, event.rUser.currentNick, message); + }); + delete this.pending[event.rUser.id]; + } + }.bind(this); + this.on = 'JOIN'; + var commands = { '~report': function(event) { - var channelName = event.input[1]; - var nick = event.input[2]; - var reason = event.input[3]; + var channelName = event.input[1], + nick = event.input[2], + reason = event.input[3]; - if(_.has(event.allChannels, channelName)) { - if(dbot.api.users.isChannelUser(event.server, nick, channelName, true)) { - nick = dbot.api.users.resolveUser(event.server, nick, true); - this.api.notify(event.server, channelName, dbot.t('report', { - 'reporter': event.user, - 'reported': nick, - 'channel': channelName, - 'reason': reason - })); - event.reply(dbot.t('reported', { 'reported': nick })); + dbot.api.users.resolveUser(event.server, nick, function(reportee) { + if(_.has(event.allChannels, channelName)) { + if(reportee) { + this.api.notify(event.server, channelName, dbot.t('report', { + 'reporter': event.user, + 'reported': nick, + 'channel': channelName, + 'reason': reason + })); + event.reply(dbot.t('reported', { 'reported': nick })); + } else { + event.reply(dbot.t('user_not_found', { + 'reported': nick, + 'channel': channelName + })); + } } else { - event.reply(dbot.t('user_not_found', { - 'reported': nick, - 'channel': channelName - })); - } - } else { - event.reply(dbot.t('not_in_channel', { 'channel': channelName })); - } + event.reply(dbot.t('not_in_channel', { 'channel': channelName })); + } + }.bind(this)); }, '~notify': function(event) { - var channelName = event.input[1]; - var message = event.input[2]; + var channelName = event.input[1], + message = event.input[2]; if(_.has(event.allChannels, channelName)) { this.api.notify(event.server, channelName, dbot.t('notify', { @@ -55,6 +96,17 @@ var report = function(dbot) { 'notifier': event.user, 'message': message })); + + var id = uuid.v4(); + this.db.save('notifies', id, { + 'id': id, + 'server': event.server, + 'channel': channelName, + 'user': event.user, + 'time': new Date().getTime(), + 'message': message + }, function() {}); + event.reply(dbot.t('notified', { 'user': event.user, 'channel': channelName diff --git a/modules/report/strings.json b/modules/report/strings.json index 469326d..15068af 100644 --- a/modules/report/strings.json +++ b/modules/report/strings.json @@ -4,33 +4,39 @@ "na'vi": "{reporter}ìl fpìl futa {reported} fe' lu taluna {reason}.", "cy": "Sylw! {reporter} wedi adrodd {reported} yn {channel}. Y rheswm a rhoddwyd oedd: \"{reason}.\"", "nl": "Waarschuwing: {reporter} heeft {reported} aangegeven in {channel}. De reden die gegeven werd was: \"{reason}.\"", - "de": "Achtung: {reporter} hat {reported} in {channel} gemeldet. Grund: \"{reason}.\"" + "de": "Achtung: {reporter} hat {reported} in {channel} gemeldet. Grund: \"{reason}.\"", + "fr": "Attention : {reporter} a reporté {reported} dans {channel}. Raison donnée : \"{reason}.\"" }, "reported": { "en": "Thank you, {reported} has been reported to the channel administrators.", "na'vi": "Irayo si ngari, fìtsengìri ayeyktan omum teri {reported}it set.", "cy": "Diolch, {reported} wedi cael ei adrodd i'r gweinyddwyr sianel.", "nl": "Bedankt, {reported} is aangegeven bij de administrators.", - "de": "Danke, {reported} wurde bei den Administratoren dieses Kanals gemeldet." + "de": "Danke, {reported} wurde bei den Administratoren dieses Kanals gemeldet.", + "fr": "Merci, {reported} a été reporté aux administrateurs du canal." }, "user_not_found": { "en": "{reported} isn't a known user in {channel}.", "na'vi": "Oel ke omum {reported}it mì {channel}.", "cy": "Nid yw {reported} yn ddefnyddiwr hysbys yn {channel}.", "nl": "{reported} is geen bestaande gebruiker in {channel}.", - "de": "{reported} ist kein bekannter Benutzer in {channel}." + "de": "{reported} ist kein bekannter Benutzer in {channel}.", + "fr": "{reported} n'est pas un utilisateur connu dans {channel}." }, "not_in_channel": { "en": "I am not present in {channel}.", "na'vi": "Oel {channel}it ke tok.", "cy": "Dydw i ddim yn bresennol yn {channel}.", "nl": "Ik ben niet aanwezig in {channel}.", - "de": "Ich bin nicht in {channel} aktiv." + "de": "Ich bin nicht in {channel} aktiv.", + "fr": "Je ne suis pas présent dans {channel}." }, "notify": { - "en": "Attention {channel}: {notifier}: {message}" + "en": "Attention {channel}: {notifier}: {message}", + "fr": "Attention {channel}: {notifier}: {message}" }, "notified": { - "en": "{user}: {channel} staff notified." + "en": "{user}: {channel} staff notified.", + "fr": "{user}: l'équipe de {channel} a été notifiée." } } diff --git a/modules/soundcloud/config.json b/modules/soundcloud/config.json new file mode 100644 index 0000000..50a1537 --- /dev/null +++ b/modules/soundcloud/config.json @@ -0,0 +1,5 @@ +{ + "client_id": "391bcf67b5ad70fd64c6a4f79374f2f4", + "outputPrefix": "\u000307soundcloud\u000f", + "dependencies": [ "link" ] +} diff --git a/modules/soundcloud/soundcloud.js b/modules/soundcloud/soundcloud.js new file mode 100644 index 0000000..65a3177 --- /dev/null +++ b/modules/soundcloud/soundcloud.js @@ -0,0 +1,70 @@ +/** + * Module Name: soundcloud + * Description: Various SoundCloud functionality + */ +var _ = require('underscore')._, + request = require('request'); + +var soundcloud = function(dbot) { + this.ApiRoot = 'http://api.soundcloud.com'; + + this.commands = { + '~soundcloud': function(event) { + var query = event.input[1]; + request.get(this.ApiRoot + '/tracks.json', { + 'qs': { + 'client_id': this.config.client_id, + 'q': query + }, + 'json': true + }, function(error, response, body) { + if(body.length != 0) { + body = body[0]; + if(!body.genre) body.genre = ''; + event.reply(dbot.t('sc_track', { + 'title': body.title, + 'artist': body.user.username, + 'genre': body.genre.trim(), + 'plays': body.playback_count, + 'favs': body.favoritings_count + }) + ' — ' + body.permalink_url); + } else { + event.reply(dbot.t('sc_notrack')); + } + }); + } + }; + this.commands['~soundcloud'].regex = [/^~soundcloud (.+)$/, 2]; + + this.onLoad = function() { + dbot.api.link.addHandler(this.name, + /https?:\/\/(www\.)?soundcloud\.com\//, + function(event, match, name) { + var url = match.input; + request.get(this.ApiRoot + '/resolve.json', { + 'qs': { + 'client_id': this.config.client_id, + 'url': url + }, + 'json': true + }, function(error, response, body) { + if(response.statusCode == 200) { + if(body.kind == 'track') { + if(!body.genre) body.genre = ''; + event.reply(dbot.t('sc_track', { + 'title': body.title, + 'artist': body.user.username, + 'genre': body.genre.trim(), + 'plays': body.playback_count, + 'favs': body.favoritings_count + })); + } + } + }); + }.bind(this)); + }.bind(this); +}; + +exports.fetch = function(dbot) { + return new soundcloud(dbot); +}; diff --git a/modules/soundcloud/strings.json b/modules/soundcloud/strings.json new file mode 100644 index 0000000..afd8a94 --- /dev/null +++ b/modules/soundcloud/strings.json @@ -0,0 +1,10 @@ +{ + "sc_track": { + "en": "[{title} by {artist} — {genre} — \u000312▶\u000f{plays} \u000304♥\u000f{favs}]", + "fr": "[{title} par {artist} — {genre} — \u000312▶\u000f{plays} \u000304♥\u000f{favs}]" + }, + "sc_notrack": { + "en": "No results found.", + "fr": "Aucun résultat trouvé." + } +} diff --git a/modules/spelling/config.json b/modules/spelling/config.json index 3a54ca9..c945e96 100644 --- a/modules/spelling/config.json +++ b/modules/spelling/config.json @@ -1,4 +1,3 @@ { - "ignorable": true, - "help": "http://github.com/reality/depressionbot/blob/master/modules/spelling/README.md" + "ignorable": true } diff --git a/modules/spelling/strings.json b/modules/spelling/strings.json index 0c67074..f55d486 100644 --- a/modules/spelling/strings.json +++ b/modules/spelling/strings.json @@ -5,7 +5,8 @@ "na'vi": "Sweylu {correcter} pamrel sivi: {fix}", "cy": "Oedd {correcter} yn feddwl: {fix}", "nl": "{correcter} bedoelde: {fix}", - "de": "{correcter} meinte: {fix}" + "de": "{correcter} meinte: {fix}", + "fr": "{correcter} voulait dire : {fix}" }, "spelling_other": { "en": "{correcter} thinks {candidate} meant: {fix}", @@ -13,6 +14,7 @@ "na'vi": "{correcter} fpìl futa sweylu {candiate} pamrel sivi: {fix}", "cy": "Mae {correcter} yn meddwl bod {candidate} yn feddwl: {fix}", "nl": "{correcter} denkt dat {candidate} bedoelde: {fix}", - "de": "{corrector} denkt, dass {canditate} meinte: {fix}" + "de": "{corrector} denkt, dass {canditate} meinte: {fix}", + "fr": "{correcter} pense que {candidate} voulait dire : {fix}" } } diff --git a/modules/spotify/README.md b/modules/spotify/README.md index a67c2de..ec0839f 100644 --- a/modules/spotify/README.md +++ b/modules/spotify/README.md @@ -1,2 +1,28 @@ -#spotify -dbot spotify module +## spotify + +Various Spotify functionality. + +### Description +This module posts information on Spotify links, as well as providing Spotify +search functionality. + +## Commands + +### ~spotify [query] +Search spotify for a song. + +### ~syt [youtube link] +Attempt to get a Spotify link from a YouTube link. If no link is provided with +the commands, it will attempt to use the last link posted in the channel. + +## API + +#### spotifySearch(query, callback) +Run a search query on Spotify. If no results are found, the callback will pass +false. If a result is found, the callback takes two arguments: data about the +found track, and a link to the track. + +### Hooks + +#### link +Posts information about a Spotify link when one is posted in a channel. diff --git a/modules/spotify/config.json b/modules/spotify/config.json index 6ee3966..0b1135b 100644 --- a/modules/spotify/config.json +++ b/modules/spotify/config.json @@ -1,5 +1,5 @@ { - "dependencies": [ "command" ], + "dependencies": [ "link" ], "ignorable": true, "outputPrefix": "\u00039spotify\u000f" } diff --git a/modules/spotify/strings.json b/modules/spotify/strings.json index ee111e6..09231af 100644 --- a/modules/spotify/strings.json +++ b/modules/spotify/strings.json @@ -2,28 +2,33 @@ "artist": { "en": "[artist] {artist}", "cy": "[cyfansoddwr] {artist}", - "de": "[Interpret] {artist}" + "de": "[Interpret] {artist}", + "fr": "[artiste] {artist}" }, "album": { "en": "[album] {artist} – {album}", - "cy": "[albwm] {artist} - {album}", - "de": "[Album] {artist} – {album}" + "cy": "[albwm] {artist} – {album}", + "de": "[Album] {artist} – {album}", + "fr": "[album] {artist} – {album}" }, "track": { "en": "[track] {artist} – {track} (from {album})", - "cy": "[trac] {artist} - {track} (o'r albwm: {album})", - "de": "[Track] {artist} – {track} (aus dem Album {album})" + "cy": "[trac] {artist} – {track} (o'r albwm: {album})", + "de": "[Track] {artist} – {track} (aus dem Album {album})", + "fr": "[piste] {artist} – {track} (tiré de {album})" }, "found": { "en": "[{url}] {artist} – {track} (from {album})", - "cy": "[{url}] {artist} - {track} (o'r albwm: {album})", - "de": "[{url}] {artist} – {track} (aus dem Album {album})" + "cy": "[{url}] {artist} – {track} (o'r albwm: {album})", + "de": "[{url}] {artist} – {track} (aus dem Album {album})", + "fr": "[{url}] {artist} – {track} (tiré de {album})" }, "not-found": { "en": "No results.", - "na'vi": "{s}, Oel ke tsun rivum ayuoti.", + "na'vi": "Oel ke tsun rivum ayuoti.", "cy": "Dim canlyniadau.", "nl": "Geen resultaten.", - "de": "Kein Ergebnis." + "de": "Kein Ergebnis.", + "fr": "Aucun résultat." } } diff --git a/modules/stats b/modules/stats deleted file mode 160000 index f7599c7..0000000 --- a/modules/stats +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f7599c7564396a22a8ca9f05bd386d6531f41d1a diff --git a/modules/timers/config.json b/modules/timers/config.json deleted file mode 100644 index 9cc23c8..0000000 --- a/modules/timers/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "help": "https://github.com/reality/depressionbot/blob/master/modules/timers/README.md" -} diff --git a/modules/users/README.md b/modules/users/README.md index 0a11e5b..90ac175 100644 --- a/modules/users/README.md +++ b/modules/users/README.md @@ -5,8 +5,7 @@ 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. +kind of thing. It's mainly a utility module for other modules to use. ### Commands @@ -27,30 +26,67 @@ Requires moderator level access by default. ### API -#### resolveUser(server, nick, [useLowerCase]) -This resolves a given nick to its primary user and returns it. +#### resolveUser(server, nick, callback) +This resolves a given nick to its primary user, returning false if no user +record is found in the store associated with the given nickname (either as a +primary nick or an alias). The callback is called with one argument, a _user_ +object or false if no user was found. -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. +#### getUser(uuid, callback) +Get a user by its uuid. Callback is called with one argument, a _user_ object or +false if no user was found by that uuid. -#### resolveUser(server, user) -Return whether a user is known either as an alias or a primary user. +#### getChannel(server, channelName, callback) +This resolves a given server and channel name to a particular channel. The +callback is called with one argument, a _channel_ object or false if no channel +was found with the given name on the given server. -#### isPrimaryUser(server, nick) -Return whether a nick is known as a primary user. +#### getRandomChannelUser(server, channel, callback) +Given a server name and a channel name, this gets a random user from that +channel. Callback is called with one argument, a _user_ object or false if no +channel was found from the given parameters. -#### getAliases(server, user) -Return a list of aliases for a given primary user. +#### getAllusers(callback) +Get a list of all users the bot currently knows about. Callback is called with +one argument, a list of user records. -#### isOnline(server, user, channel, useLowerCase) -Return whether a user is online in a given channel on the given server. +#### isOnline +Is the given nick on the given server currently in the given channel. Callback +is called with one argument, a boolean as to whether the nick is currently in +the specified place. + +### Data + +#### User Object + + { + id: user uuid, + primaryNick, + currentNick: Current or last used nickname, + server, + channels: A list of names for channels this user has been in, + aliases: A list of known aliases for this user + } + +#### Channel Object + + { + id: channel uuid, + server, + name, + users: A list of the uuids of users who are in this channel + } ### Events -#### nick_changed(server, newNick) -This is executed when a new alias is added for a user. +#### new_user(user) +This is executed when a new user is added to the known users DB. -#### new_user(server, nick) -This is executed when a new primary user is added to the known users DB. +#### new_user_alias(user, newNick) +When a new user alias is added. + +#### new_channel(channel) +Executed when DBot creates a new channel object. + +#### new_channel_user(user, channel) +Executed when DBot creates a new channel object. diff --git a/modules/users/api.js b/modules/users/api.js index 19c62a6..8fc537a 100644 --- a/modules/users/api.js +++ b/modules/users/api.js @@ -1,4 +1,9 @@ -var _ = require('underscore')._; +var _ = require('underscore')._, + uuid = require('node-uuid'), + databank = require('databank'), + AlreadyExistsError = databank.AlreadyExistsError, + NoSuchThingError = databank.NoSuchThingError, + NotImplementedError = databank.NotImplementedError; var api = function(dbot) { var escapeRegexen = function(str) { @@ -6,100 +11,124 @@ var api = function(dbot) { }; 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]; + // Return a user record given a primary nick or an alias + 'resolveUser': function(server, nick, callback) { + var user = false; + this.db.search('users', { 'server': server }, function(result) { + if(result.primaryNick == nick || _.include(result.aliases, nick)) { + user = result; } - } else { - // this is retarded - user = user.toLowerCase(); - var toMatch = new RegExp("^" + escapeRegexen(user) + "$", "i"); + }, function(err) { + if(!err) { + callback(user); + } + }); + }, - var resolvedUser = _.find(knownUsers.users, function(nick) { - return nick.match(toMatch) !== null; - }, this); + // Return a user record given a UUID + 'getUser': function(uuid, callback) { + this.db.read('users', uuid, function(err, user) { + if(err) user = false; + callback(user); + }); + }, - if(!resolvedUser) { - resolvedUser = _.find(knownUsers.aliases, function(nick, alias) { - if(alias.match(toMatch) !== null) return nick; + 'getChannel': function(server, channelName, callback) { + var channel = false; + this.db.search('channel_users', { + 'server': server, + 'name': channelName + }, function(result) { + channel = result; + }, function(err) { + if(!err) { + callback(channel); + } + }); + }, + + 'getRandomChannelUser': function(server, channel, callback) { + var channel; + this.db.search('channel_users', { + 'server': server, + 'channel': channel + }, function(result) { + channel = result; + }, function(err) { + if(!err) { + if(!_.isUndefined(channel.users)) { + var randomUser = channel.users[_.random(0, channel.users.length - 1)]; + this.api.resolveUser(server, randomUser, function(user) { + callback(user); + }); + } else { + callback(false); + } + } + }); + }, + + 'getAllUsers': function(callback) { + var users = []; + this.db.scan('users', function(user) { + users.push(user); + }, function(err) { + if(!err) { + callback(users); + } + }); + }, + + 'getAllChannels': function(callback) { + var channels = []; + this.db.scan('channel_users', function(channel) { + channels.push(channel); + }, function(err) { + if(!err) { + callback(channels); + } + }); + }, + + 'isOnline': function(server, nick, channel, callback) { + this.api.resolveUser(server, nick, function(user) { + var possiNicks = [user].concat(user.aliases); + + if(_.has(dbot.instance.connections[server].channels, channel)) { + var onlineNicks = dbot.instance.connections[server].channels[channel].nicks; + var isOnline = _.any(onlineNicks, function(nick) { + nick = nick.name; + return _.include(possiNicks, nick); }, this); - if(!_.isUndefined(resolvedUser)) user = resolvedUser; + + callback(isOnline); } - else{ - user = resolvedUser; + }); + }, + + 'isKnownUser': function(server, nick, callback) { + this.api.resolveUser(server, nick, function(isKnown) { + if(isKnown == false) { + callback(false); + } else { + callback(true); } - } - 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); + }); } }; + api['resolveUser'].external = true; + api['resolveUser'].extMap = [ 'server', 'nick', 'callback' ]; + + api['getChannel'].external = true; + api['getChannel'].extMap = [ 'server', 'channel', 'callback' ]; + + api['getAllUsers'].external = true; + api['getAllUsers'].extMap = [ 'callback' ]; + + api['getAllChannels'].external = true; + api['getAllChannels'].extMap = [ 'callback' ]; + return api; }; diff --git a/modules/users/commands.js b/modules/users/commands.js index 57452c1..6409ab9 100644 --- a/modules/users/commands.js +++ b/modules/users/commands.js @@ -3,106 +3,139 @@ var _ = require('underscore')._; var commands = function(dbot) { var commands = { '~alias': function(event) { - var knownUsers = this.getServerUsers(event.server), - alias = event.params[1].trim(); + var nick = event.params[1].trim() || event.user; + this.api.resolveUser(event.server, nick, function(user) { + if(user) { + if(nick == user.primaryNick) { + var aliases = _.first(user.aliases, 10); + var including = 'including: ' + aliases.join(', ') + '.'; - 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: ' + aliases.join(', ') + '.'; - - event.reply(dbot.t('primary', { - 'user': alias, - 'count': aliasCount - }) + including); + if(user.aliases.length != 0) { + event.reply(dbot.t('primary', { + 'user': nick, + 'currentNick': user.currentNick, + 'count': user.aliases.length, + }) + including); + } else { + event.reply(dbot.t('primary', { + 'user': nick, + 'currentNick': user.currentNick, + 'count': user.aliases.length + }).slice(0, -2) + "."); + } + } else { + event.reply(dbot.t('alias', { + 'alias': nick, + 'user': user.primaryNick + })); + } } else { - event.reply(dbot.t('primary', { - 'user': alias, - 'count': aliasCount - }).slice(0, -2) + "."); + event.reply(dbot.t('unknown_alias', { 'alias': nick })); } - } 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 })); - } + }); + }, + + '~addalias': function(event) { + var nick = event.input[1], + alias = event.input[2]; + + this.api.resolveUser(event.server, nick, function(user) { + if(user) { + if(!_.include(user.aliases, alias)) { + user.aliases.push(alias); + this.db.save('users', user.id, user, function(err) { + if(!err) { + event.reply(dbot.t('alias_added', { + 'user': user.primaryNick, + 'alias': alias + })); + } + }); + } else { + event.reply(dbot.t('alias_exists', { 'alias': alias })); + } + } else { + event.reply(dbot.t('unknown_alias', { 'alias': nick })); + } + }.bind(this)); }, '~setaliasparent': function(event) { - var knownUsers = this.getServerUsers(event.server); - var newParent = event.params[1]; + var newPrimary = event.params[1].trim(); + this.api.resolveUser(event.server, newPrimary, function(user) { + if(user && user.primaryNick != newPrimary) { + var newAlias = user.primaryNick; + user.primaryNick = newPrimary; + user.aliases = _.without(user.aliases, newPrimary); + user.aliases.push(newAlias); + this.internalAPI.updateChannelPrimaryUser(event.server, newAlias, newPrimary); - 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; + this.db.save('users', user.id, user, function(err) { + if(!err) { + event.reply(dbot.t('aliasparentset', { + 'newParent': newPrimary, + 'newAlias': newAlias + })); + dbot.api.event.emit('~setaliasparent', { + 'server': event.server, + 'alias': newAlias + }); + } + }); + } else { + event.reply(dbot.t('unknown_alias', { 'alias': newPrimarj })); + } + }.bind(this)); }, '~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; - } + this.api.resolveUser(event.server, primaryUser, function(user) { + if(user) { + this.api.resolveUser(event.server, secondaryUser, function(oldUser) { + if(oldUser) { + user.aliases.push(oldUser.primaryNick); + user.aliases = _.union(user.aliases, oldUser.aliases); + this.internalAPI.mergeChannelUsers(event.server, oldUser, user); + this.db.del('users', oldUser.id, function(err) { + if(!err) { + this.db.save('users', user.id, user, function(err) { + if(!err) { + this.internalAPI.mergeChannelUsers(event.server, secondaryUser, primaryUser); + event.reply(dbot.t('merged_users', { + 'old_user': secondaryUser, + 'new_user': primaryUser + })); + dbot.api.event.emit('~mergeusers', [ + event.server, + oldUser, + user + ]); + } + }.bind(this)); + } + }.bind(this)); + } else { + event.reply(dbot.t('unprimary_error', { 'nick': secondaryUser })); + } + }.bind(this)); + } else { + event.reply(dbot.t('unprimary_error', { 'nick': primaryUser })); + } + }.bind(this)); + } }; commands['~alias'].regex = [/^~alias ([\d\w[\]{}^|\\`_-]+?)/, 2]; commands['~setaliasparent'].regex = [/^~setaliasparent ([\d\w[\]{}^|\\`_-]+?)/, 2]; commands['~mergeusers'].regex = [/^~mergeusers ([\d\w[\]{}^|\\`_-]+?)\s*?([\d\w[\]{}^|\\`_-]+?)/, 3]; + commands['~addalias'].regex = [/^~addalias ([\d\w[\]{}^|\\`_-]+?) ([\d\w[\]{}^|\\`_-]+?)$/, 3]; commands['~setaliasparent'].access = 'moderator'; commands['~mergeusers'].access = 'moderator'; + commands['~addalias'].access = 'moderator'; return commands; }; diff --git a/modules/users/config.json b/modules/users/config.json index 866deb7..c9b6aa2 100644 --- a/modules/users/config.json +++ b/modules/users/config.json @@ -1,6 +1,6 @@ { "ignorable": false, - "dependencies": [ "command", "event" ], + "dependencies": [ "event" ], "dbKeys": [ "knownUsers" ], - "help": "https://github.com/reality/depressionbot/blob/master/modules/users/README.md" + "dbType": "redis" } diff --git a/modules/users/strings.json b/modules/users/strings.json index 5a1957c..7e9f3a9 100644 --- a/modules/users/strings.json +++ b/modules/users/strings.json @@ -4,41 +4,55 @@ "na'vi": "ayfko syaw {user} {alias} nìteng", "cy": "{alias} ydy enw arall o {user}", "nl": "{alias} is een alias van {user}", - "de": "{alias} ist ein Nickname von {user}" + "de": "{alias} ist ein Nickname von {user}", + "fr": "{alias} est un alias de {user}" }, "primary": { - "en": "{user} is a primary user with {count} aliases, ", - "na'vi": "{user} lu txin ulte {count}a stxo lu poru, ", - "cy": "Mae {user} yn ddefnyddiwr gynradd gyda {count} enwau eraill, ", - "nl": "{user} is een primaire gebruiker met {count} aliassen, ", - "de": "{user} ist ein Benutzer mit {count} Nicknamen, " + "en": "{user} (currently {currentNick}) is a primary user with {count} aliases, ", + "na'vi": "{user} ({currentNick}) lu txin ulte {count}a stxo lu poru, ", + "nl": "{user} ({currentNick}) is een primaire gebruiker met {count} aliassen, ", + "cy": "Mae {user} ({currentNick}) yn ddefnyddiwr gynradd gyda {count} enwau eraill, ", + "de": "{user} ({currentNick}) ist ein Benutzer mit {count} Nicknamen, ", + "fr": "{user} (actuellement {currentNick}) est un utilisateur primaire avec {count} alias, " }, "unknown_alias": { "en": "{alias} does not currently exist as an alias or known user.", "na'vi": "{alias} ke fkeytok nìfkrr", "cy": "Nid yw {alias} yn bodoli fel enw arall neu defnyddiwr yn hysbys.", "nl": "{alias} staat momenteel niet bekend als een bestaande gebruiker of alias.", - "de": "{alias} existiert nicht oder ist kein Nickname eines Benutzers." + "de": "{alias} existiert nicht oder ist kein Nickname eines Benutzers.", + "fr": "{alias} n'existe pas actuellement en tant qu'alias ou utilisateur connu." }, "aliasparentset": { "en": "{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.", "cy": "Mae {newParent} ydy defnyddiwr rhiant nawr, a {alias} ydy enw arall.", "nl": "{newParent} is nu de bovenliggende gebruiker, en {newAlias} is een alias.", - "de": "{newParent} ist nun der Hauptname und {newAlias} ist ein Nickname." + "de": "{newParent} ist nun der Hauptname und {newAlias} ist ein Nickname.", + "fr": "{newParent} est maintenant le même utilisateur parent, et {newAlias} est un alias." }, "unprimary_error": { - "en": "One of those users isn't currently recorded as a primary user.", + "en": "{nick} isn't recorded as a primary user.", "na'vi": "fo sute txin ke lu.", "cy": "Nid yw un o'r defnyddwyr hynny yn cael ei gofnodi ar hyn o bryd fel defnyddiwr gynradd.", "nl": "Een van deze gebruikers is nog niet bekend als een primaire gebruiker.", - "de": "Einer dieser Benutzer ist nicht als Hauptbenutzer gespeichert." + "de": "Einer dieser Benutzer ist nicht als Hauptbenutzer gespeichert.", + "fr": "{nick} n'est pas enregistré en tant qu'utilisateur primaire." }, "merged_users": { "en": "{old_user} and their aliases have been merged into {new_user}.", "na'vi": "{old_user} ulte stxo alahe {new_user} lu set.", "cy": "{old_user} a'u enwau eraill wedi cael eu cyfuno i mewn i {new_user}.", "nl": "{old_user} en zijn aliassen zijn samengevoegd met {new_user}.", - "de": "{old_user} und seine Nicknamen wurden zusammengelegt in {new_user}." + "de": "{old_user} und seine Nicknamen wurden zusammengelegt in {new_user}.", + "fr": "{old_user} et ses alias ont été fusionnés dans {new_user}." + }, + "alias_exists": { + "en": "Alias {alias} already exists.", + "fr": "L'alias {alias} existe déjà." + }, + "alias_added": { + "en": "Alias {alias} added to {user}.", + "fr": "Alias {alias} ajouté à {user}." } } diff --git a/modules/users/users.js b/modules/users/users.js index a12493a..0e0f53e 100644 --- a/modules/users/users.js +++ b/modules/users/users.js @@ -2,93 +2,228 @@ * Name: Users * Description: Track known users */ -var _ = require('underscore')._; +var _ = require('underscore')._, + uuid = require('node-uuid'), + async = require('async'); 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; + /*** Internal API ***/ + this.internalAPI = { + 'createUser': function(server, nick, callback) { + var id = uuid.v4(); + this.db.create('users', id, { + 'id': id, + 'primaryNick': nick, + 'currentNick': nick, + 'server': server, + 'channels': [], + 'aliases': [] + }, function(err, result) { + if(!err) { + dbot.api.event.emit('new_user', [ result ]); + callback(result); + } + }); + }.bind(this), + + 'createChannel': function(server, name, callback) { + var id = uuid.v4(); + this.db.create('channel_users', id, { + 'id': id, + 'server': server, + 'name': name, + 'users': [], + 'op': [], + 'voice': [] + }, function(err, result) { + if(!err) { + dbot.api.event.emit('new_channel', [ result ]); + callback(result); + } + }); + }.bind(this), + + 'addChannelUser': function(channel, user, staff, callback) { + if(!_.include(channel.users, user.id)) { + channel.users.push(user.id); } - }, 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' && nick != dbot.config.name) { - if(!_.has(knownUsers.channelUsers, event.channel.name)) { - knownUsers.channelUsers[event.channel.name] = []; + if(!_.include(user.channels, channel.id)) { + user.channels.push(channel.id); } - var channelUsers = knownUsers.channelUsers[event.channel.name]; - if(this.api.isKnownUser(event.server, nick)) { - nick = this.api.resolveUser(event.server, nick); + if(!channel.op) channel.op = []; + if(!channel.voice) channel.voice = []; + + if(staff.op) { + channel.op.push(user.id); + } else if(staff.voice) { + channel.voice.push(user.id); + } + + this.db.save('users', user.id, user, function(err) { + this.db.save('channel_users', channel.id, channel, function(err) { + dbot.api.event.emit('new_channel_user', [ user, channel ]); + callback(err); + }); + }.bind(this)); + }.bind(this), + + 'modChannelStaff': function(channel, user, staff, callback) { + var needsUpdating = false; + + if(!channel.op) { + channel.op = []; + needsUpdating = true; + } + if(!channel.voice) { + channel.voice = []; + needsUpdating = true; + } + + if(!_.include(channel.op, user.id) && staff.op) { + channel.op.push(user.id); + needsUpdating = true; + } else if(!_.include(channel.voice, user.id) && staff.voice) { + channel.voice.push(user.id); + needsUpdating = true; + } else if(_.include(channel.op, user.id) && !staff.op) { + channel.op = _.without(channel.op, user.id); + needsUpdating = true; + } else if(_.include(channel.voice, user.id) && !staff.voice) { + channel.voice = _.without(channel.voice, user.id); + needsUpdating = true; + } + + if(needsUpdating) { + this.db.save('channel_users', channel.id, channel, function(err) { + callback(err); + }); } else { - knownUsers.users.push(nick); - dbot.api.event.emit('new_user', [ event.server, nick ]); + callback(); + } + }.bind(this), + + 'updateChannelPrimaryUser': function(server, oldUser, newUser) { + this.db.search('channel_users', { 'server': server }, function(channel) { + channel.users = _.without(channel.users, oldUser); + if(!_.include(channel.users, newUser)) channel.users.push(newUser); + this.db.save('channel_users', channel.id, channel, function(err) { + if(err) { + // QQ + } + }); + }.bind(this), function(err) { + if(err) { + // QQ + } + }); + }.bind(this), + + 'mergeChannelUsers': function(server, oldUser, newUser) { + newUser.channels = _.union(oldUser.channels, newUser.channels); + _.each(newUser.channels, function(name) { + this.api.getChannel(server, name, function(channel) { + if(_.include(channel.users, oldUser.id)) { + channel.users = _.without(channel.users, oldUser.id); + } + if(!_.include(channel.users, newUser.id)) { + channel.users.push(newUser.id); + } + this.db.save('channel_users', channel.id, channel, function(err) { + if(err) { + // QQ + } + }); + }.bind(this)); + }, this); + }.bind(this) + }; + + this.listener = function(event) { + if(event.action == 'JOIN' && event.user != dbot.config.name) { + if(!event.rUser) { + this.internalAPI.createUser(event.server, event.user, function(user) { + this.internalAPI.addChannelUser(event.rChannel, user, {}, function() {}); + }.bind(this)); + } else if(!_.include(event.rUser.channels, event.rChannel.id)) { + this.internalAPI.addChannelUser(event.rChannel, event.rUser, {}, function() {}); } - if(!_.include(channelUsers, nick)) { - channelUsers.push(nick); + if(event.rUser.currentNick != event.user) { + event.rUser.currentNick = event.user; + this.db.save('users', event.rUser.id, event.rUser, function() {}); } } else if(event.action == 'NICK') { - var newNick = event.newNick; - if(!this.api.isKnownUser(newNick)) { - knownUsers.aliases[newNick] = this.api.resolveUser(event.server, event.user); - dbot.api.event.emit('nick_change', [ event.server, newNick ]); - } + this.api.isKnownUser(event.server, event.newNick, function(isKnown) { + event.rUser.currentNick = event.newNick; + + if(!isKnown) { + event.rUser.aliases.push(event.newNick); + } + + this.db.save('users', event.rUser.id, event.rUser, function(err) { + dbot.api.event.emit('new_user_alias', [ event.rUser, event.newNick ]); + }); + }.bind(this)); } }.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]; + this.on = ['JOIN', 'NICK']; - _.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.event.emit('new_user', [ event.server, nick ]); - } - if(!_.include(channelUsers, nick)) { - channelUsers.push(nick); - } - }, this); + this.onLoad = function() { + dbot.instance.addPreEmitHook(function(event, callback) { + if(event.user) { + this.api.resolveUser(event.server, event.user, function(user) { + event.rUser = user; + callback(false); + }); + } else { + callback(false); + } + }.bind(this)); + + dbot.instance.addPreEmitHook(function(event, callback) { + if(event.channel) { + this.api.getChannel(event.server, event.channel.name, function(channel) { + event.rChannel = channel; + callback(false); + }); + } else { + callback(false); + } }.bind(this)); - var connections = dbot.instance.connections; - _.each(connections, function(connection) { + dbot.instance.addListener('366', 'users', function(event) { + var checkChannel = function(channel) { + async.eachSeries(_.keys(event.channel.nicks), function(nick, next) { + var staff = event.channel.nicks[nick]; + + this.api.resolveUser(event.server, nick, function(user) { + var checkChannelUser = function(user) { + if(!_.include(channel.users, user.id)) { + this.internalAPI.addChannelUser(channel, user, staff, next); + } else { + this.internalAPI.modChannelStaff(channel, user, staff, next); + } + }.bind(this); + + if(user) { + checkChannelUser(user); + } else { + this.internalAPI.createUser(event.server, nick, checkChannelUser); + } + }.bind(this)); + }.bind(this), function(err) {}); + }.bind(this); + + if(!event.rChannel) { + this.internalAPI.createChannel(event.server, event.channel.name, checkChannel); + } else { + checkChannel(event.rChannel); + } + }.bind(this)); + + _.each(dbot.instance.connections, function(connection) { connection.updateNickLists(); }); }; diff --git a/modules/warning/config.json b/modules/warning/config.json index 914c1d7..9fb677f 100644 --- a/modules/warning/config.json +++ b/modules/warning/config.json @@ -1,4 +1,4 @@ { "dependencies": [ "report", "users", "web" ], - "dbKeys": [ "warnings" ] + "dbType": "redis" } diff --git a/modules/warning/pages.js b/modules/warning/pages.js index e60a634..72c5129 100644 --- a/modules/warning/pages.js +++ b/modules/warning/pages.js @@ -1,4 +1,5 @@ -var _ = require('underscore')._; +var _ = require('underscore')._, + async = require('async'); var pages = function(dbot) { this.warnings = dbot.db.warnings; @@ -7,37 +8,59 @@ var pages = function(dbot) { '/warning': function(req, res) { res.render('servers', { 'name': dbot.config.name, - 'servers': _.keys(this.warnings) + 'servers': _.keys(dbot.config.servers) }); }, '/warning/:server': function(req, res) { - var server = req.params.server; + var server = req.params.server, + userIds = [], + userNicks = []; - if(_.has(this.warnings, server)) { - res.render('users', { - 'name': dbot.config.name, - 'server': server, - 'users': _.keys(this.warnings[server]) + this.db.search('warnings', { 'server': server }, function(warning) { + if(!_.include(userIds, warning.warnee)) userIds.push(warning.warnee); + }, function(err) { + async.eachSeries(userIds, function(id, callback) { + dbot.api.users.getUser(id, function(user) { + userNicks.push(user.primaryNick); + callback(false); + }); + }, function(err) { + res.render('users', { + 'name': dbot.config.name, + 'server': server, + 'users': userNicks + }); }); - } else { - res.render('error'); - } + }); }, '/warning/:server/:user': function(req, res) { var server = req.params.server, user = req.params.user; - if(_.has(this.warnings, server) && _.has(this.warnings[server], user)) { - res.render('warnings', { - 'name': dbot.config.name, + dbot.api.users.resolveUser(server, user, function(user) { + var warnings = []; + this.db.search('warnings', { 'server': server, - 'warnings': this.warnings[server][user] + 'warnee': user.id + }, function(warning) { + warnings.push(warning); + }, function(err) { + async.eachSeries(warnings, function(warning, callback) { + dbot.api.users.getUser(warning.warner, function(user) { + warning.warner = user.primaryNick; + callback(false); + }); + }, function(err) { + res.render('warnings', { + 'name': dbot.config.name, + 'server': server, + 'warnings': warnings + }); + }); }); - } else { - res.render('error'); - } + }.bind(this)); } }; }; diff --git a/modules/warning/strings.json b/modules/warning/strings.json index c4abcab..295a263 100644 --- a/modules/warning/strings.json +++ b/modules/warning/strings.json @@ -1,11 +1,14 @@ { "no_warnings": { - "en": "No warnings found for {user}." + "en": "No warnings found for {user}.", + "fr": "Aucun avertissement trouvé pour {user}." }, "warning_info": { - "en": "{user} has {num} warnings. More info can be found at {url}" + "en": "{user} has {num} warnings. More info can be found at {url}", + "fr": "{user} a {num} avertissements. Plus d'informations peuvent être trouvées ici : {url}" }, "warn_notify": { - "en": "Attention: {warner} has issued a warning to {warnee} for \"{reason}.\" More info can be found at {url}" + "en": "Attention: {warner} has issued a warning to {warnee} for \"{reason}.\" More info can be found at {url}", + "fr": "Attention : {warner} a donné un avertissement à {warnee} pour \"{reason}.\". Plus d'informations peuvent être trouvées ici : {url}" } } diff --git a/modules/warning/warning.js b/modules/warning/warning.js index cbea752..34398e0 100644 --- a/modules/warning/warning.js +++ b/modules/warning/warning.js @@ -1,54 +1,69 @@ var _ = require('underscore')._; + uuid = require('node-uuid'); var warning = function(dbot) { - this.warnings = dbot.db.warnings; - this.commands = { '~warn': function(event) { - var warner = event.user, + var warner = event.rUser, server = event.server, - warnee = dbot.api.users.resolveUser(server, event.input[1]), reason = event.input[2], adminChannel = dbot.config.servers[server].admin_channel; - // Store the warn - if(!_.has(this.warnings, server)) this.warnings[server] = {}; - if(!_.has(this.warnings[server], warnee)) this.warnings[server][warnee] = []; - - this.warnings[server][warnee].push({ - 'warner': warner, - 'reason': reason, - 'time': new Date().getTime() - }); - - // Notify interested parties - var notifyString = dbot.t('warn_notify', { - 'warner': warner, - 'warnee': warnee, - 'reason': reason, - 'url': dbot.api.web.getUrl('warning/' + server + '/' + warnee) - }); - if(!_.isUndefined(adminChannel)) { - adminChannel = event.channel.name; - } - dbot.api.report.notify(server, adminChannel, notifyString); - dbot.say(server, adminChannel, notifyString); - dbot.say(server, warnee, notifyString); + dbot.api.users.resolveUser(server, event.input[1], function(warnee) { + if(warnee) { + var id = uuid.v4(); + this.db.save('warnings', id, { + 'id': id, + 'server': event.server, + 'warnee': warnee.id, + 'warner': warner.id, + 'reason': reason, + 'time': new Date().getTime() + }, function(err) { + var notifyString = dbot.t('warn_notify', { + 'warner': warner.primaryNick, + 'warnee': warnee.primaryNick, + 'reason': reason, + 'url': dbot.api.web.getUrl('warning/' + server + '/' + + warnee.primaryNick) + }); + if(_.isUndefined(adminChannel)) { + adminChannel = event.channel.name; + } + dbot.api.report.notify(server, adminChannel, notifyString); + dbot.say(server, adminChannel, notifyString); + dbot.say(server, warnee.currentNick, notifyString); + }); + } else { + event.reply(dbot.t('warnee_not_found', { 'user': event.input[1] })); + } + }.bind(this)); }, '~warnings': function(event) { var warnee = event.params[1], server = event.server; - if(_.has(this.warnings, server) && _.has(this.warnings[server], warnee)) { - event.reply(dbot.t('warning_info', { - 'user': warnee, - 'num': this.warnings[server][warnee].length, - 'url': dbot.api.web.getUrl('warning/' + server + '/' + warnee) - })); - } else { - event.reply(dbot.t('no_warnings', { 'user': warnee })); - } + dbot.api.users.resolveUser(server, warnee, function(warnee) { + var warnings = 0; + this.db.search('warnings', { + 'server': server, + 'warnee': warnee.id + }, function(warning) { + warnings++; + }, function(err) { + if(warnings > 0) { + event.reply(dbot.t('warning_info', { + 'user': warnee.primaryNick, + 'num': warnings, + 'url': dbot.api.web.getUrl('warning/' + server + '/' + + warnee.primaryNick) + })); + } else { + event.reply(dbot.t('no_warnings', { 'user': warnee })); + } + }); + }.bind(this)); } }; diff --git a/modules/web/README.md b/modules/web/README.md index c00af25..0f729c0 100644 --- a/modules/web/README.md +++ b/modules/web/README.md @@ -7,31 +7,21 @@ Web interface It's a web interface for DBot. What of it? ## Requirements -###Express and Jade@0.25 + +### Express and Jade ``` -npm install express -npm install jade@0.25 +npm install express jade ``` -###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 + +### Twitter Bootstrap ``` cd depressionbot/public/ wget http://twitter.github.com/bootstrap/assets/bootstrap.zip unzip bootstrap.zip rm bootstrap.zip ``` -###d3.js + +### d3.js ``` cd depressionbot/public/ mkdir d3 diff --git a/modules/web/config.json b/modules/web/config.json index e6b635e..a92a66f 100644 --- a/modules/web/config.json +++ b/modules/web/config.json @@ -1,6 +1,5 @@ { "webHost": "nourishedcloud.com", "webPort": 8080, - "externalPath": false, - "help": "https://github.com/reality/depressionbot/blob/master/modules/web/README.md" + "externalPath": false } diff --git a/modules/web/web.js b/modules/web/web.js index 25f4997..75d3c08 100644 --- a/modules/web/web.js +++ b/modules/web/web.js @@ -3,13 +3,14 @@ var express = require('express'), fs = require('fs'); var webInterface = function(dbot) { + this.config = dbot.config.modules.web; this.pub = 'public'; this.app = express(); this.app.use(express.static(this.pub)); this.app.set('view engine', 'jade'); - - var server = this.app.listen(dbot.config.web.webPort); + + var server = this.app.listen(this.config.webPort); this.reloadPages = function() { var pages = dbot.pages; @@ -60,12 +61,10 @@ var webInterface = function(dbot) { this.api = { 'getUrl': function(path) { - console.log(path); + if(path.charAt(0) == '/') path = path.substr(1); if(this.config.externalPath) { - console.log('external'); return this.config.externalPath + '/' + path; } else { - console.log('internal'); return 'http://' + this.config.webHost + ':' + this.config.webPort + '/' + path; } } diff --git a/modules/words/words.js b/modules/words/words.js index eac90b0..d26a648 100644 --- a/modules/words/words.js +++ b/modules/words/words.js @@ -11,8 +11,15 @@ var words = function(dbot) { event.reply('No definitions found for ' + query); } }); - } + }, + + '~jimble': function(event) { + event.reply(event.params[1].split('').sort(function() { + return (Math.round(Math.random()) - 0.5); + }).join('')); + } }; + this.commands['~jimble'].regex = [/^~jimble (.+)$/, 2]; this.onLoad = function() { this.wn = new Wordnik({ diff --git a/modules/youare/youare.js b/modules/youare/youare.js index 2aa4ecd..407b95f 100644 --- a/modules/youare/youare.js +++ b/modules/youare/youare.js @@ -2,7 +2,7 @@ var youAre = function(dbot) { this.listener = function(event) { var key = event.message.valMatch(/(\bis\b|\bare\b)\s+([\w\s\d]*?)(\s+)?(,|\.|\band\b|$)/, 5); - if(key && key[2] != "" && Number.prototype.chanceIn(1, 100) && event.user != 'aisbot') { + if(key && key[2] != "" && (Math.floor(Math.random() * (101)) / 1) == 1) { event.reply(event.user + ': You\'re ' + key[2] + '.'); } }.bind(this); diff --git a/modules/youtube/config.json b/modules/youtube/config.json new file mode 100644 index 0000000..ce5cb36 --- /dev/null +++ b/modules/youtube/config.json @@ -0,0 +1,3 @@ +{ + "outputPrefix": "\u00031,0You\u000315,5Tube\u000f" +} diff --git a/modules/youtube/strings.json b/modules/youtube/strings.json new file mode 100644 index 0000000..3e5a413 --- /dev/null +++ b/modules/youtube/strings.json @@ -0,0 +1,8 @@ +{ + "yt_video": { + "en": "[{title} by {author} — \u000312▶\u000f{plays} (\u00039▲{likes}\u000f|\u000312{dislikes}▼\u000f)]" + }, + "yt_noresults": { + "en": "No results found." + } +} diff --git a/modules/youtube/youtube.js b/modules/youtube/youtube.js new file mode 100644 index 0000000..30bc33f --- /dev/null +++ b/modules/youtube/youtube.js @@ -0,0 +1,79 @@ +/** + * Module Name: youtube + * Description: Various Youtube functionality + */ +var _ = require('underscore')._, + request = require('request'); + +var youtube = function(dbot) { + this.ApiRoot = 'https://gdata.youtube.com/feeds/api'; + this.params = { + 'alt': 'json', + 'v': 2 + }; + + this.commands = { + '~youtube': function(event) { + request.get(this.ApiRoot + '/videos', { + 'qs': _.extend(this.params, { + 'q': encodeURIComponent(event.input[1]), + 'max-results': 1 + }), + 'json': true + }, function(error, response, body) { + if(_.isObject(body) && _.has(body, 'feed') && _.has(body.feed, + 'entry') && _.has(body.feed.entry[0], 'yt$statistics')) { + var v = body.feed.entry[0]; + if(!_.has(v, 'yt$rating')) { + v['yt$rating'] = { + 'numLikes': 0, + 'numDislikes': 0 + }; + } + event.reply(dbot.t('yt_video', { + 'title': v.title['$t'], + 'plays': v['yt$statistics'].viewCount, + 'author': v.author[0].name['$t'], + 'likes': v['yt$rating'].numLikes, + 'dislikes': v['yt$rating'].numDislikes + })); + } else { + event.reply(dbot.t('yt_noresults')); + } + }); + } + }; + this.commands['~youtube'].regex = [/^~youtube (.+)$/, 2]; + + this.onLoad = function() { + dbot.api.link.addHandler(this.name, + /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/, + function(event, match, name) { + request.get(this.ApiRoot + '/videos/' + match[2], { + 'qs': this.params, + 'json': true + }, function(error, response, body) { + if(_.isObject(body) && _.has(body, 'entry')) { + var v = body.entry; + if(!_.has(v, 'yt$rating')) { + v['yt$rating'] = { + 'numLikes': 0, + 'numDislikes': 0 + }; + } + event.reply(dbot.t('yt_video', { + 'title': v.title['$t'], + 'plays': v['yt$statistics'].viewCount, + 'author': v.author[0].name['$t'], + 'likes': v['yt$rating'].numLikes, + 'dislikes': v['yt$rating'].numDislikes + })); + } + }); + }.bind(this)); + }.bind(this); +}; + +exports.fetch = function(dbot) { + return new youtube(dbot); +}; diff --git a/run.js b/run.js index 17d8c8e..839bf21 100644 --- a/run.js +++ b/run.js @@ -1,12 +1,12 @@ var fs = require('fs'), _ = require('underscore')._, - jsbot = require('./jsbot/jsbot'); + jsbot = require('./jsbot/jsbot'), + DatabaseDriver = require('./database').DatabaseDriver; require('./snippets'); var DBot = function() { /*** Load the DB ***/ - if(fs.existsSync('db.json')) { try { this.db = JSON.parse(fs.readFileSync('db.json', 'utf-8')); @@ -18,37 +18,12 @@ var DBot = function() { this.db = {}; } - if(!_.has(this.db, 'config')) { - this.db.config = {}; - } + this.reloadConfig(); - /*** Load the Config ***/ - - if(!fs.existsSync('config.json')) { - console.log('Error: config.json file does not exist. Stopping'); - process.exit(); - } - - this.config = _.clone(this.db.config); - try { - _.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 the fancy DB ***/ + this.ddb = new DatabaseDriver(this.config); /*** Load main strings ***/ - try { this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8')); } catch(err) { @@ -79,6 +54,38 @@ var DBot = function() { this.instance.connectAll(); }; +DBot.prototype.reloadConfig = function() { + this.config = {}; + + if(!fs.existsSync('config.json')) { + console.log('Error: config.json file does not exist. Stopping'); + process.exit(); + } + + try { + var configFile = fs.readFileSync('config.json', 'utf-8'); + this.config = JSON.parse(configFile); + this.customConfig = JSON.parse(configFile); + } 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 + if(!_.has(this.customConfig, 'modules')) { + this.customConfig.modules = {}; + this.config.modules = {}; + } + _.defaults(this.config, defaultConfig); +}; + // Say something in a channel DBot.prototype.say = function(server, channel, message) { this.instance.say(server, channel, message); @@ -97,8 +104,8 @@ DBot.prototype.t = function(string, formatData) { if(_.has(this.strings[string], lang)) { var module = this.stringMap[string]; formattedString = this.strings[string][lang].format(formatData); - if(this.config[module] && this.config[module].outputPrefix) { - formattedString = '[' + this.config[module].outputPrefix + '] ' + + if(this.config.modules[module] && this.config.modules[module].outputPrefix) { + formattedString = '[' + this.config.modules[module].outputPrefix + '] ' + formattedString; } } @@ -134,10 +141,8 @@ DBot.prototype.reloadModules = function() { this.api = {}; this.stringMap = {}; this.usage = {}; + this.reloadConfig(); - // Load config changes - _.extend(this.config, this.db.config); - try { this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8')); } catch(err) { @@ -148,7 +153,7 @@ DBot.prototype.reloadModules = function() { // Enforce having command. it can still be reloaded, but dbot _will not_ // function without it, so not having it should be impossible - if(!moduleNames.include("command")) { + if(!_.include(moduleNames, 'command')) { moduleNames.push("command"); } @@ -159,171 +164,159 @@ DBot.prototype.reloadModules = function() { this.instance.removeListeners(); - _.each(moduleNames, function(name) { + var name, moduleDir, config; + for(i=0;i