From bc528feed2b39a32fc8888c394bac4b290f83267 Mon Sep 17 00:00:00 2001 From: robsonbittencourt Date: Mon, 27 Feb 2017 17:29:21 -0300 Subject: [PATCH 1/5] Fix #18 Add translation to other languages --- locales/en-US/translation.json | 49 ++++++++++++++++++ locales/pt-BR/translation.json | 47 +++++++++++++++++ migrations/002-language.sql | 6 +++ package.json | 2 + src/assembler.js | 21 ++++++++ src/core.js | 7 +++ src/first-run.js | 38 ++++---------- src/hubot.js | 23 +++++---- src/lib/i18n.js | 43 ++++++++++++++++ src/message-handler/conversation.js | 16 +++--- .../handlers/gear-configure-handler.js | 10 ++-- .../handlers/gear-status-handler.js | 51 +++++++++++-------- .../handlers/gears-tasks-handler.js | 8 +-- .../handlers/language-handler.js | 31 +++++++++++ src/message-handler/message-handler.js | 4 +- src/speech.js | 25 +++++---- .../gear-test/locales/en-US/translation.json | 3 ++ 17 files changed, 299 insertions(+), 85 deletions(-) create mode 100644 locales/en-US/translation.json create mode 100644 locales/pt-BR/translation.json create mode 100644 migrations/002-language.sql create mode 100644 src/lib/i18n.js create mode 100644 src/message-handler/handlers/language-handler.js create mode 100644 test/resource/node_modules_test/gear-test/locales/en-US/translation.json diff --git a/locales/en-US/translation.json b/locales/en-US/translation.json new file mode 100644 index 0000000..6e635a9 --- /dev/null +++ b/locales/en-US/translation.json @@ -0,0 +1,49 @@ +{ + "locale": { + "change": "hubot change the language to $", + "sucess": "Language changed to *{{language}}*", + "error": "Ops. I don't know the language *{{language}}*" + }, + + "hello": "Hello,", + "thanks": "Thanks,", + "ops": "Ops! ", + "configure": "configure ", + "configureGear": "configure {{gear}}", + "didNotUnderstand": "Sorry, I didn't understand. (Expected responses: ", + + "firstUse": { + "message1": "My name is {{botName}} and from now on I will help you with some tasks using the Slack.", + "message2": "Before I need you to do some settings. How was you who started me I will define you as my system administrator. So you can access the settings in the future.", + "message3": "Initially I do not know perform tasks. But there are gears that when coupled to me add me skills.", + "message4": "At this time all the gears are active. You can deactivate them using the command ", + "message5": "Some gears have settings. To let them use the command ", + "message6": "Below is a list of gears available:" + }, + + "gears": { + "description": "gear {{gearDescription}}", + "deactivate": "deactivate gear-name", + "configure": "configure gear-name", + "already": "This gear is already {{statusDescription}}.", + "activation": { + "success": "Successfully {{statusDescription}} *{{gearDescription}}*", + "error": "Could not {{statusDescription}} *{{gearDescription}}*", + "logs": "See the detailed error in logs", + "activate": "activate", + "deactivate": "deactivate", + "active": "active", + "inactive": "inactive", + "actived": "activated", + "deactivated": "deactivated" + } + }, + + "feature": { + "disabled": "Sorry, this feature is disabled.", + "onlyChannel": "Sorry, this feature can only be performed on a channel.", + "onlyPrivate": "Sorry, this feature can only be performed directly with me in private." + } +} + + diff --git a/locales/pt-BR/translation.json b/locales/pt-BR/translation.json new file mode 100644 index 0000000..7d7cb2e --- /dev/null +++ b/locales/pt-BR/translation.json @@ -0,0 +1,47 @@ +{ + "hello": "Olá,", + "thanks": "Obrigado,", + "ops": "Ops! ", + "configure": "configurar ", + "configureGear": "configurar {{gear}}", + "didNotUnderstand": "Desculpe, eu não entendi. (Respostas esperadas: ", + + "locale": { + "change": "hubot alterar idioma para $", + "sucess": "Idioma alterado para *{{language}}*", + "error": "Ops. Eu não conheço o idioma *{{language}}*" + }, + + "firstUse": { + "message1": "Meu nome é {{botName}} e a partir de agora eu vou lhe ajudar com algumas tarefas utilizando o Slack.", + "message2": "Antes eu preciso que você faça algumas configurações. Como foi você que me iniciou vou defini-lo como meu administrador. Então você pode acessar as configurações no futuro.", + "message3": "Inicialmente eu não sei fazer muitas coisas. Mas existem engrenagens que quando acopladas à mim me adicionam habilidades.", + "message4": "Neste momento todas as engrenagens estão ativas. Você pode desativá-las utilizando o comando ", + "message5": "Algumas engrenagens possuem configurações. Para fazê-las use o comando ", + "message6": "Abaixo segue uma lista com as engrenagens disponíveis:" + }, + + "gears": { + "description": "gear {{gearDescription}}", + "deactivate": "desativar nome-da-engrenagem", + "configure": "configurar nome-da-engrenagem", + "already": "Esta engrenagem já está {{statusDescription}}.", + "activation": { + "success": "*{{gearDescription}}* {{statusDescription}} com sucesso", + "error": "Não consegui {{statusDescription}} *{{gearDescription}}*", + "logs": "Veja o detalhe do erro nos logs", + "activate": "ativar", + "deactivate": "desativar", + "active": "ativada", + "inactive": "inativada", + "actived": "ativado", + "deactivated": "desativado" + } + }, + + "feature": { + "disabled": "Desculpe, essa funcionalidade está desabilitada.", + "onlyChannel": "Desculpe, esta funcionalidade só pode ser utilizada em um canal.", + "onlyPrivate": "Desculpe. esta funcionalidade só pode ser utilizada diretamente comigo no privado." + } +} diff --git a/migrations/002-language.sql b/migrations/002-language.sql new file mode 100644 index 0000000..6972518 --- /dev/null +++ b/migrations/002-language.sql @@ -0,0 +1,6 @@ +-- Up +ALTER TABLE config ADD language TEXT; +UPDATE config SET language = 'en-US'; + +-- Down +ALTER TABLE config DROP language TEXT; diff --git a/package.json b/package.json index 6d46077..0ee67b6 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "pm2": "^2.1.6", "dialog": "^0.2.0", "yargs": "^6.4.0", + "i18next": "^7.1.0", + "i18next-sync-fs-backend": "^0.1.0", "gear-help": "^2.0.0", "gear-jenkins": "^2.0.0" }, diff --git a/src/assembler.js b/src/assembler.js index 076a57f..a299fbf 100644 --- a/src/assembler.js +++ b/src/assembler.js @@ -1,10 +1,12 @@ 'use strict'; const fs = require('fs'); +const path = require('path'); const db = require('./lib/db'); const log = require('./lib/log'); const speech = require('./speech'); +const i18n = require('./lib/i18n'); const gearNamePrefix = 'gear-'; @@ -42,6 +44,7 @@ module.exports = class Assembler { this.tryToLoad('categories', gear, this.loadCategories); this.tryToLoad('handlers', gear, this.loadHandlers); this.tryToLoad('configHandler', gear, this.loadConfigHandler); + this.tryToLoad('locales', gear, this.loadLocales); } tryToLoad(type, gear, assemble) { @@ -94,6 +97,21 @@ module.exports = class Assembler { gear.configHandler = require(self.configsHandlersPath(gear)); } + loadLocales(gear, self) { + fs.readdir(self.localesPath(gear), (error, list) => { + if (error) return; + + gear.locales = []; + + list.forEach((dir) => { + const localeFile = require(path.join(self.localesPath(gear), dir, 'translation.json')); + i18n.addResourceBundle(dir, gear.description, localeFile); + + gear.locales.push(dir.toLowerCase()); + }); + }); + } + configsPath(gear) { return `${global.__nodeModules}${gear.name}/config/config.json`; } @@ -114,6 +132,9 @@ module.exports = class Assembler { return `${global.__nodeModules}${gear.name}/src/configHandler/configHandler`; } + localesPath(gear) { + return `${global.__nodeModules}${gear.name}/locales`; + } }; function logStartAssembling() { diff --git a/src/core.js b/src/core.js index b9bac99..e7200c1 100644 --- a/src/core.js +++ b/src/core.js @@ -7,6 +7,7 @@ const Bot = require('slackbots'); const db = require('./lib/db'); const Hubot = require('./hubot'); const log = require('./lib/log'); +const i18n = require('./lib/i18n'); const firstRun = require('./first-run'); const Assembler = require('./assembler'); const messageHandler = require('./message-handler/message-handler'); @@ -40,6 +41,7 @@ Core.prototype.onStart = function onStart() { this.hubot = new Hubot(this); this.hubot.gears = new Assembler().build(); this.firstRunChecker(); + this.setLanguage(); }; Core.prototype.onMessage = function onMessage(message) { @@ -61,6 +63,11 @@ Core.prototype.firstRunChecker = function firstRunChecker() { }); }; +Core.prototype.setLanguage = function setLanguage() { + db.getDb().get('SELECT * FROM config') + .then(config => i18n.changeLanguage(config.language)); +}; + Core.prototype.getUserByName = function getUserByName(name) { return this.users.find(user => user.name === name); }; diff --git a/src/first-run.js b/src/first-run.js index c1f864a..105638a 100644 --- a/src/first-run.js +++ b/src/first-run.js @@ -11,43 +11,25 @@ function firstRun(core, message) { const hubot = core.hubot; const messageDelay = 3000; - hubot.speak(message, message1(hubot, core, message), messageDelay) - .then(() => hubot.speak(message, message2(hubot), messageDelay)) - .then(() => hubot.speak(message, message3(hubot), messageDelay)) - .then(() => hubot.speak(message, message4(hubot), messageDelay)) - .then(() => hubot.speak(message, message5(hubot), messageDelay)) - .then(() => hubot.speak(message, message6(hubot), messageDelay)) - .then(() => hubot.speak(message, postGearsNames(hubot), messageDelay)); + hubot.speak(message, message1(hubot, core, message), null, messageDelay) + .then(() => hubot.speak(message, 'firstUse.message2', null, messageDelay)) + .then(() => hubot.speak(message, 'firstUse.message3', null, messageDelay)) + .then(() => hubot.speak(message, message4(hubot), null, messageDelay)) + .then(() => hubot.speak(message, message5(hubot), null, messageDelay)) + .then(() => hubot.speak(message, 'firstUse.message6', null, messageDelay)) + .then(() => hubot.speak(message, postGearsNames(hubot), null, messageDelay)); } function message1(hubot, core, message) { - return hubot.speech().hello(core.getUserById(message.user)).append('My name is ').append(core.name) - .append(' and from now on I will help you with some tasks using the Slack.').end(); -} - -function message2(hubot) { - return hubot.speech().append('Before I need you to do some settings.') - .append(' How was you who started me I will define you as my system administrator.') - .append(' So you can access the settings in the future.').end(); -} - -function message3(hubot) { - return hubot.speech().append('Initially I do not know perform tasks.') - .append(' But there are gears that when coupled to me add me skills.').end(); + return hubot.speech().hello(core.getUserById(message.user)).append('firstUse.message1', { botName: 'hubot' }).end(); } function message4(hubot) { - return hubot.speech().append('At this time all the gears are active. You can deactivate them using the command ') - .bold('deactivate gear-name').period().end(); + return hubot.speech().append('firstUse.message4').bold('gears.deactivate').period().end(); } function message5(hubot) { - return hubot.speech().append('Some gears have settings. To let them use the command ') - .bold('configure gear-name').period().end(); -} - -function message6(hubot) { - return hubot.speech().append('Below is a list of gears available:').end(); + return hubot.speech().append('firstUse.message5').bold('gears.configure').period().end(); } function postGearsNames(hubot) { diff --git a/src/hubot.js b/src/hubot.js index 187a957..708d1f5 100644 --- a/src/hubot.js +++ b/src/hubot.js @@ -3,6 +3,7 @@ const Q = require('q'); const log = require('./lib/log'); +const i18n = require('./lib/i18n'); const speech = require('./speech'); let core; @@ -14,14 +15,14 @@ module.exports = class Hubot { core = receivedCore; } - speakTo(recipient, text, message, delay = 1000) { + speakTo(recipient, text, vars, message, delay = 1000) { const deferred = Q.defer(); const channel = message ? message.channel : recipient; core.ws.send(JSON.stringify({ type: 'typing', channel })); setTimeout(() => { - core.postMessage(recipient, text, { as_user: true }) + core.postMessage(recipient, i18n.t(text, vars), { as_user: true }) .then(() => deferred.resolve(), () => deferred.reject()); }, delay); @@ -29,20 +30,24 @@ module.exports = class Hubot { return deferred.promise; } - speak(message, text, delay) { - return this.speakTo(this.getRecipient(message), text, message, delay); + speak(message, text, vars, delay) { + return this.speakTo(this.getRecipient(message), text, vars, message, delay); + } + + i18n(text, vars) { + return i18n.t(text, vars); } logInfo(info) { - log.info(info); + log.info(i18n.t(info)); } logError(error) { - log.error(error); + log.error(i18n.t(error)); } logDetailedError(error, metadata) { - log.detailedError(error, metadata); + log.detailedError(i18n.t(error), metadata); } isFromChannel(message) { @@ -61,8 +66,8 @@ module.exports = class Hubot { return core.getRecipient(message); } - speech(message) { - return speech.start(message); + speech(text) { + return speech.start(text); } findGear(gear) { diff --git a/src/lib/i18n.js b/src/lib/i18n.js new file mode 100644 index 0000000..e255724 --- /dev/null +++ b/src/lib/i18n.js @@ -0,0 +1,43 @@ +'use strict'; + +const path = require('path'); + +const i18n = require('i18next'); +const backend = require('i18next-sync-fs-backend'); + +exports.t = t; +exports.changeLanguage = changeLanguage; +exports.addResourceBundle = addResourceBundle; + +const localesPath = path.join(__dirname, '../..', 'locales', '{{lng}}/{{ns}}.json'); + +const initOptions = { + debug: false, + initImmediate: false, + lng: 'en-US', + fallbackLng: 'en-US', + backend: { + loadPath: localesPath + } +}; + +i18n.use(backend) + .init(initOptions); + +function t(key, vars) { + if (i18n.exists(key)) { + return vars ? i18n.t(key, vars) : i18n.t(key); + } + + return key; +} + +function changeLanguage(locale) { + if (locale) { + i18n.changeLanguage(locale); + } +} + +function addResourceBundle(lng, ns, resources) { + i18n.addResourceBundle(lng, ns, resources); +} diff --git a/src/message-handler/conversation.js b/src/message-handler/conversation.js index 2fc09ee..d7ed0ea 100644 --- a/src/message-handler/conversation.js +++ b/src/message-handler/conversation.js @@ -4,6 +4,8 @@ const EventEmitter = require('events'); const Q = require('q'); +const i18n = require('../lib/i18n'); + exports.startConversation = startConversation; exports.hasActiveConversation = hasActiveConversation; exports.notify = notify; @@ -51,7 +53,7 @@ function speak(hubot, message, conversation, callback) { if (withExpectedResponse(conversation, interaction, response, hubot, message, callback)) return; - Q(handleResponse(conversation, interaction, response)).then((text) => { + Q(handleResponse(hubot, conversation, interaction, response)).then((text) => { conversation.nextInteration++; if (text) { @@ -82,7 +84,7 @@ function justSpeak(conversation, interaction, callback) { function withoutExpectedResponse(conversation, interaction, response, hubot, message, callback) { if (!interaction.expectedResponses) { - Q(handleResponse(conversation, interaction, response)).then((text) => { + Q(handleResponse(hubot, conversation, interaction, response)).then((text) => { conversation.nextInteration++; speakReturnedText(hubot, message, conversation, text, callback); }, (text) => { @@ -134,10 +136,10 @@ function hasAnotherInteraction(conversation, interaction, response, hubot, messa return false; } -function handleResponse(conversation, interaction, response) { +function handleResponse(hubot, conversation, interaction, response) { if (interaction.handler) { const handler = require(`${nodeModules}gear-${conversation.gear}/${interaction.handler}`); - return handler.handle(response.text); + return handler.handle(hubot, response.text); } return null; @@ -151,7 +153,7 @@ function getExpectedResponse(expectedResponses, response) { return expectedResponses[0]; } - return expectedResponses.find(r => r.response === response.text); + return expectedResponses.find(r => i18n.t(r.response) === response.text); } return null; @@ -160,13 +162,13 @@ function getExpectedResponse(expectedResponses, response) { function getExpectedResponses(interaction) { const expectedResponses = []; - interaction.expectedResponses.forEach(e => expectedResponses.push(e.response)); + interaction.expectedResponses.forEach(e => expectedResponses.push(i18n.t(e.response))); return expectedResponses; } function invalidResponseMessage(hubot, expectedResponses) { - return hubot.speech().append('Sorry, I didn\'t understand.(Expected responses: ') + return hubot.speech().append('didNotUnderstand') .bold(expectedResponses.join(', ')).append(').').end(); } diff --git a/src/message-handler/handlers/gear-configure-handler.js b/src/message-handler/handlers/gear-configure-handler.js index b4615dc..3e3756b 100644 --- a/src/message-handler/handlers/gear-configure-handler.js +++ b/src/message-handler/handlers/gear-configure-handler.js @@ -10,7 +10,7 @@ function handle(hubot, message, core) { if (isAdmin) { const param = { user: message.user, - gear: gearDescription(message), + gear: gearDescription(message, hubot), interactions: discoverConfig(hubot, message) }; @@ -22,16 +22,16 @@ function handle(hubot, message, core) { function isGearConfigureMessage(hubot, message) { return hubot.gears.find((gear) => { - const configureMessage = `configure ${gear.description}`; + const configureMessage = hubot.i18n('configureGear', { gear: gear.description }); return message.text === configureMessage; }) != null; } function discoverConfig(hubot, message) { - return hubot.gears.find(g => g.description === gearDescription(message)).configs; + return hubot.gears.find(g => g.description === gearDescription(message, hubot)).configs; } -function gearDescription(message) { - return message.text.replace('configure ', ''); +function gearDescription(message, hubot) { + return message.text.replace(hubot.i18n('configure'), ''); } diff --git a/src/message-handler/handlers/gear-status-handler.js b/src/message-handler/handlers/gear-status-handler.js index 652bb6d..80b6a44 100644 --- a/src/message-handler/handlers/gear-status-handler.js +++ b/src/message-handler/handlers/gear-status-handler.js @@ -1,12 +1,11 @@ 'use strict'; const db = require('../../lib/db'); -const speech = require('../../speech'); exports.handle = handle; function handle(hubot, message, core) { - const action = getAction(message); + const action = getAction(message, hubot); if (action && isGearChangeStatusMessage(action, hubot, message)) { core.isAdminUser(message.user) @@ -22,12 +21,14 @@ function changeStatus(isAdmin, action, hubot, message) { if (isAdmin) { const gear = discoverGear(action, hubot, message); - if (gear && gear.active === action.status) { - hubot.speak(message, `This gear is already ${action.statusDescription}.`); - } else { - changeGearStatus(action, hubot, gear.description) - .then(() => hubot.speak(message, sucessMessage(action, gear.description)), - () => hubot.speak(message, errorMessage(action, gear.description))); + if (gear) { + if (gear.active === action.status) { + hubot.speak(message, 'gears.already', { statusDescription: action.already }); + } else { + changeGearStatus(action, hubot, gear.description) + .then(() => hubot.speak(message, sucessMessage(action, hubot, gear.description)), + () => hubot.speak(message, errorMessage(action, hubot, gear.description))); + } } } } @@ -47,24 +48,30 @@ function discoverGear(action, hubot, message) { return hubot.findGear(gearDescription); } -function sucessMessage(action, gearDescription) { - return speech.start(`Successfully ${action.description}d `).bold(`gear ${gearDescription}`).end(); +function sucessMessage(action, hubot, gearDescription) { + return hubot.speech().append('gears.activation.success', { statusDescription: action.success, gearDescription }) + .end(); } -function errorMessage(action, gearDescription) { - return speech.start(`Could not ${action.description} `).bold(`gear ${gearDescription}`).period() - .append('See the detailed error in logs').end(); +function errorMessage(action, hubot, gearDescription) { + return hubot.speech().append('gears.activation.error', { statusDescription: action.description, gearDescription }) + .end(); } -function getAction(message) { - if (message.text.startsWith('activate')) { - return { - description: 'activate', status: true, statusDescription: 'active' - }; - } else if (message.text.startsWith('deactivate')) { - return { - description: 'deactivate', status: false, statusDescription: 'inactive' - }; +function getAction(message, hubot) { + const active = hubot.i18n('gears.activation.active'); + const inactive = hubot.i18n('gears.activation.inactive'); + const activate = hubot.i18n('gears.activation.activate'); + const deactivate = hubot.i18n('gears.activation.deactivate'); + const activated = hubot.i18n('gears.activation.actived'); + const deactivated = hubot.i18n('gears.activation.deactivated'); + + if (message.text.startsWith(activate)) { + return { description: activate, status: true, already: active, success: activated }; + } + + if (message.text.startsWith(deactivate)) { + return { description: deactivate, status: false, already: inactive, success: deactivated }; } return null; diff --git a/src/message-handler/handlers/gears-tasks-handler.js b/src/message-handler/handlers/gears-tasks-handler.js index c13dbce..444b66e 100644 --- a/src/message-handler/handlers/gears-tasks-handler.js +++ b/src/message-handler/handlers/gears-tasks-handler.js @@ -20,7 +20,7 @@ function handle(hubot, message, core) { } function tryExecuteTask(hubot, core, gear, message, task) { - const acceptance = trigger.check(message.text, task.trigger); + const acceptance = trigger.check(message.text, hubot.i18n(task.trigger)); if (acceptance.ok) { if (gear.active) { @@ -32,7 +32,7 @@ function tryExecuteTask(hubot, core, gear, message, task) { const handler = getHandler(gear, task); handler.handle(hubotClone, message, task, acceptance.params); } else { - hubot.speak(message, 'Sorry, this feature is disabled.'); + hubot.speak(message, 'feature.disabled'); } return true; @@ -43,12 +43,12 @@ function tryExecuteTask(hubot, core, gear, message, task) { function incorretMessageSource(hubot, task, message) { if (task.onlyInChannel && !hubot.isFromChannel(message)) { - hubot.speak(message, 'Sorry, this feature can only be performed on a channel.'); + hubot.speak(message, 'feature.onlyChannel'); return true; } if (task.onlyInPrivate && !hubot.isFromPrivate(message)) { - hubot.speak(message, 'Sorry, this feature can only be performed directly with me in private.'); + hubot.speak(message, 'feature.onlyPrivate'); return true; } diff --git a/src/message-handler/handlers/language-handler.js b/src/message-handler/handlers/language-handler.js new file mode 100644 index 0000000..e394cf7 --- /dev/null +++ b/src/message-handler/handlers/language-handler.js @@ -0,0 +1,31 @@ +'use strict'; + +const db = require('../../lib/db'); +const i18n = require('../../lib/i18n'); +const trigger = require('../trigger'); + +exports.handle = handle; + +function handle(hubot, message) { + const acceptance = trigger.check(message.text, hubot.i18n('locale.change')); + + if (acceptance.ok) { + const language = acceptance.params[0]; + + const gearsWithLanguage = hubot.gears.filter(g => g.locales.includes(language.toLowerCase())); + + if (gearsWithLanguage.length > 0) { + i18n.changeLanguage(language); + + db.getDb().run('UPDATE config SET language = ?', language); + + hubot.speak(message, 'locale.sucess', { language }); + } else { + hubot.speak(message, 'locale.error', { language }); + } + + return true; + } + + return false; +} diff --git a/src/message-handler/message-handler.js b/src/message-handler/message-handler.js index 55648bc..eb3baa9 100644 --- a/src/message-handler/message-handler.js +++ b/src/message-handler/message-handler.js @@ -5,6 +5,7 @@ const gearStatus = require('./handlers/gear-status-handler'); const gearsTasks = require('./handlers/gears-tasks-handler'); const conversation = require('./handlers/conversation-handler'); const gearConfigure = require('./handlers/gear-configure-handler'); +const language = require('./handlers/language-handler'); exports.callTasks = callTasks; @@ -26,6 +27,7 @@ function getHandlers() { conversation, gearStatus, gearConfigure, - gearsTasks + gearsTasks, + language ]; } diff --git a/src/speech.js b/src/speech.js index bce30be..e59751e 100644 --- a/src/speech.js +++ b/src/speech.js @@ -1,5 +1,7 @@ 'use strict'; +const i18n = require('./lib/i18n'); + exports.start = start; exports.error = error; @@ -21,18 +23,21 @@ Speecher.prototype.init = function init(text) { return this; }; -Speecher.prototype.append = function append(text) { - if (this.initialized()) this.message += text; +Speecher.prototype.append = function append(text, vars) { + if (this.initialized()) { + this.message += i18n.t(text, vars); + } + return this; }; Speecher.prototype.hello = function hello(user) { - this.append('Hello,').user(user).append('! '); + this.append('hello').user(user).append('! '); return this; }; Speecher.prototype.thanks = function thanks(user) { - this.append('Thanks,').user(user).append('!! '); + this.append('thanks').user(user).append('!! '); return this; }; @@ -46,19 +51,21 @@ Speecher.prototype.channel = function channel() { return this; }; -Speecher.prototype.bold = function bold(text) { - this.append(`*${text}*`); +Speecher.prototype.bold = function bold(text, vars) { + const translatedText = i18n.t(text, vars); + this.append(`*${translatedText}*`); return this; }; -Speecher.prototype.italic = function italic(text) { - this.append(`_${text}_`); +Speecher.prototype.italic = function italic(text, vars) { + const translatedText = i18n.t(text, vars); + this.append(`_${translatedText}_`); return this; }; Speecher.prototype.errorOccurs = function errorOccurs(text) { this.isError = true; - this.init().append(':exclamation: _').append(`Ops! ${optionalText(text)}`); + this.init().append(':exclamation: _').append('ops').append(optionalText(text)); return this; }; diff --git a/test/resource/node_modules_test/gear-test/locales/en-US/translation.json b/test/resource/node_modules_test/gear-test/locales/en-US/translation.json new file mode 100644 index 0000000..1797133 --- /dev/null +++ b/test/resource/node_modules_test/gear-test/locales/en-US/translation.json @@ -0,0 +1,3 @@ +{ + +} From 2a6e3ece663c57bc975247df150566a326d1039c Mon Sep 17 00:00:00 2001 From: robsonbittencourt Date: Sat, 11 Mar 2017 17:21:47 -0300 Subject: [PATCH 2/5] Fix #45 Ignore accents and case to trigger actions --- src/core.js | 3 +++ src/lib/normalizer.js | 9 +++++++++ src/message-handler/conversation.js | 3 ++- src/message-handler/handlers/gear-configure-handler.js | 3 ++- src/message-handler/handlers/gear-status-handler.js | 3 ++- src/message-handler/trigger.js | 6 +----- 6 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 src/lib/normalizer.js diff --git a/src/core.js b/src/core.js index e7200c1..ce483d7 100644 --- a/src/core.js +++ b/src/core.js @@ -10,6 +10,7 @@ const log = require('./lib/log'); const i18n = require('./lib/i18n'); const firstRun = require('./first-run'); const Assembler = require('./assembler'); +const normalizer = require('./lib/normalizer'); const messageHandler = require('./message-handler/message-handler'); let botName; @@ -46,6 +47,8 @@ Core.prototype.onStart = function onStart() { Core.prototype.onMessage = function onMessage(message) { if (isChatMessage(message) && !isFromHubot(message)) { + message.text = normalizer.normalize(message.text); + if (isFirstInteraction(this, message)) { isFirstRun = false; firstRun.firstRun(this, message); diff --git a/src/lib/normalizer.js b/src/lib/normalizer.js new file mode 100644 index 0000000..e7c08b8 --- /dev/null +++ b/src/lib/normalizer.js @@ -0,0 +1,9 @@ +'use strict'; + +const stringfy = require('string'); + +exports.normalize = normalize; + +function normalize(text) { + return stringfy(text).trim().latinise().s.toLowerCase(); +} diff --git a/src/message-handler/conversation.js b/src/message-handler/conversation.js index d7ed0ea..72053db 100644 --- a/src/message-handler/conversation.js +++ b/src/message-handler/conversation.js @@ -5,6 +5,7 @@ const EventEmitter = require('events'); const Q = require('q'); const i18n = require('../lib/i18n'); +const trigger = require('./trigger'); exports.startConversation = startConversation; exports.hasActiveConversation = hasActiveConversation; @@ -153,7 +154,7 @@ function getExpectedResponse(expectedResponses, response) { return expectedResponses[0]; } - return expectedResponses.find(r => i18n.t(r.response) === response.text); + return expectedResponses.find(r => trigger.check(i18n.t(r.response), response.text).ok); } return null; diff --git a/src/message-handler/handlers/gear-configure-handler.js b/src/message-handler/handlers/gear-configure-handler.js index 3e3756b..deb862d 100644 --- a/src/message-handler/handlers/gear-configure-handler.js +++ b/src/message-handler/handlers/gear-configure-handler.js @@ -1,5 +1,6 @@ 'use strict'; +const trigger = require('../trigger'); const conversation = require('../conversation'); exports.handle = handle; @@ -24,7 +25,7 @@ function isGearConfigureMessage(hubot, message) { return hubot.gears.find((gear) => { const configureMessage = hubot.i18n('configureGear', { gear: gear.description }); - return message.text === configureMessage; + return trigger.check(message.text, configureMessage).ok; }) != null; } diff --git a/src/message-handler/handlers/gear-status-handler.js b/src/message-handler/handlers/gear-status-handler.js index 80b6a44..3ba7ade 100644 --- a/src/message-handler/handlers/gear-status-handler.js +++ b/src/message-handler/handlers/gear-status-handler.js @@ -1,6 +1,7 @@ 'use strict'; const db = require('../../lib/db'); +const trigger = require('../trigger'); exports.handle = handle; @@ -40,7 +41,7 @@ function changeGearStatus(action, hubot, gear) { } function isGearChangeStatusMessage(action, hubot, message) { - return hubot.gears.find(gear => message.text === `${action.description} ${gear.description}`) !== null; + return hubot.gears.find(gear => trigger.check(message.text, `${action.description} ${gear.description}`).ok) !== null; } function discoverGear(action, hubot, message) { diff --git a/src/message-handler/trigger.js b/src/message-handler/trigger.js index b5c6bef..4ed5c13 100644 --- a/src/message-handler/trigger.js +++ b/src/message-handler/trigger.js @@ -1,6 +1,6 @@ 'use strict'; -const stringfy = require('string'); +const normalize = require('../lib/normalizer').normalize; exports.check = check; @@ -69,7 +69,3 @@ class TriggerParams { return newMessage.split(this.placeholder).filter(s => s); } } - -function normalize(text) { - return stringfy(text).trim().latinise().s.toLowerCase(); -} From 3f35dabf30e271bbffd9c443bc0664421a2409c8 Mon Sep 17 00:00:00 2001 From: robsonbittencourt Date: Sun, 12 Mar 2017 13:09:48 -0300 Subject: [PATCH 3/5] Fixes #43 #46 Standardize hubot commands In a channel all commands should start with the bot name In a private talk with bot, bot name should be supressed Internal commands appears in help --- locales/en-US/translation.json | 22 +----- locales/pt-BR/translation.json | 22 +----- src/assembler.js | 34 ++++---- src/core.js | 10 ++- src/first-run.js | 4 +- src/hubot.js | 14 ++++ .../config/categories.json | 8 ++ .../config/tasks.json | 8 ++ .../locales/en-US/translation.json | 13 +++ .../locales/pt-BR/translation.json | 13 +++ .../src/handlers/gear-configure.js | 46 +++++++++++ .../config/categories.json | 8 ++ .../gear-hubot-gears-status/config/tasks.json | 15 ++++ .../locales/en-US/translation.json | 27 +++++++ .../locales/pt-BR/translation.json | 27 +++++++ .../src/handlers/status.js | 76 ++++++++++++++++++ .../config/categories.json | 8 ++ .../gear-hubot-language/config/tasks.json | 8 ++ .../locales/en-US/translation.json | 15 ++++ .../locales/pt-BR/translation.json | 15 ++++ .../src/handlers/language.js} | 12 +-- .../handlers/gear-configure-handler.js | 38 --------- .../handlers/gear-status-handler.js | 79 ------------------- .../handlers/gears-tasks-handler.js | 29 ++++++- src/message-handler/message-handler.js | 8 +- test/lib/assembler.js | 2 +- 26 files changed, 369 insertions(+), 192 deletions(-) create mode 100644 src/internal-gears/gear-hubot-gear-configure/config/categories.json create mode 100644 src/internal-gears/gear-hubot-gear-configure/config/tasks.json create mode 100644 src/internal-gears/gear-hubot-gear-configure/locales/en-US/translation.json create mode 100644 src/internal-gears/gear-hubot-gear-configure/locales/pt-BR/translation.json create mode 100644 src/internal-gears/gear-hubot-gear-configure/src/handlers/gear-configure.js create mode 100644 src/internal-gears/gear-hubot-gears-status/config/categories.json create mode 100644 src/internal-gears/gear-hubot-gears-status/config/tasks.json create mode 100644 src/internal-gears/gear-hubot-gears-status/locales/en-US/translation.json create mode 100644 src/internal-gears/gear-hubot-gears-status/locales/pt-BR/translation.json create mode 100644 src/internal-gears/gear-hubot-gears-status/src/handlers/status.js create mode 100644 src/internal-gears/gear-hubot-language/config/categories.json create mode 100644 src/internal-gears/gear-hubot-language/config/tasks.json create mode 100644 src/internal-gears/gear-hubot-language/locales/en-US/translation.json create mode 100644 src/internal-gears/gear-hubot-language/locales/pt-BR/translation.json rename src/{message-handler/handlers/language-handler.js => internal-gears/gear-hubot-language/src/handlers/language.js} (53%) delete mode 100644 src/message-handler/handlers/gear-configure-handler.js delete mode 100644 src/message-handler/handlers/gear-status-handler.js diff --git a/locales/en-US/translation.json b/locales/en-US/translation.json index 6e635a9..35c5bf9 100644 --- a/locales/en-US/translation.json +++ b/locales/en-US/translation.json @@ -1,15 +1,7 @@ { - "locale": { - "change": "hubot change the language to $", - "sucess": "Language changed to *{{language}}*", - "error": "Ops. I don't know the language *{{language}}*" - }, - "hello": "Hello,", "thanks": "Thanks,", "ops": "Ops! ", - "configure": "configure ", - "configureGear": "configure {{gear}}", "didNotUnderstand": "Sorry, I didn't understand. (Expected responses: ", "firstUse": { @@ -24,19 +16,7 @@ "gears": { "description": "gear {{gearDescription}}", "deactivate": "deactivate gear-name", - "configure": "configure gear-name", - "already": "This gear is already {{statusDescription}}.", - "activation": { - "success": "Successfully {{statusDescription}} *{{gearDescription}}*", - "error": "Could not {{statusDescription}} *{{gearDescription}}*", - "logs": "See the detailed error in logs", - "activate": "activate", - "deactivate": "deactivate", - "active": "active", - "inactive": "inactive", - "actived": "activated", - "deactivated": "deactivated" - } + "configure": "configure gear-name" }, "feature": { diff --git a/locales/pt-BR/translation.json b/locales/pt-BR/translation.json index 7d7cb2e..59afaec 100644 --- a/locales/pt-BR/translation.json +++ b/locales/pt-BR/translation.json @@ -2,16 +2,8 @@ "hello": "Olá,", "thanks": "Obrigado,", "ops": "Ops! ", - "configure": "configurar ", - "configureGear": "configurar {{gear}}", "didNotUnderstand": "Desculpe, eu não entendi. (Respostas esperadas: ", - "locale": { - "change": "hubot alterar idioma para $", - "sucess": "Idioma alterado para *{{language}}*", - "error": "Ops. Eu não conheço o idioma *{{language}}*" - }, - "firstUse": { "message1": "Meu nome é {{botName}} e a partir de agora eu vou lhe ajudar com algumas tarefas utilizando o Slack.", "message2": "Antes eu preciso que você faça algumas configurações. Como foi você que me iniciou vou defini-lo como meu administrador. Então você pode acessar as configurações no futuro.", @@ -24,19 +16,7 @@ "gears": { "description": "gear {{gearDescription}}", "deactivate": "desativar nome-da-engrenagem", - "configure": "configurar nome-da-engrenagem", - "already": "Esta engrenagem já está {{statusDescription}}.", - "activation": { - "success": "*{{gearDescription}}* {{statusDescription}} com sucesso", - "error": "Não consegui {{statusDescription}} *{{gearDescription}}*", - "logs": "Veja o detalhe do erro nos logs", - "activate": "ativar", - "deactivate": "desativar", - "active": "ativada", - "inactive": "inativada", - "actived": "ativado", - "deactivated": "desativado" - } + "configure": "configurar nome-da-engrenagem" }, "feature": { diff --git a/src/assembler.js b/src/assembler.js index a299fbf..ce230bd 100644 --- a/src/assembler.js +++ b/src/assembler.js @@ -12,8 +12,10 @@ const gearNamePrefix = 'gear-'; module.exports = class Assembler { - constructor() { + constructor(gearsPath, isInternal) { this.gears = []; + this.gearsPath = gearsPath; + this.isInternal = isInternal; } build() { @@ -22,20 +24,24 @@ module.exports = class Assembler { } loadGears(self) { - fs.readdir(global.__nodeModules, (error, list) => { - const gearsNames = list.filter(e => e.startsWith(gearNamePrefix)); + const list = fs.readdirSync(this.gearsPath); - gearsNames.forEach((gearName) => { - const gearDescription = gearName.replace('gear-', ''); + const gearsNames = list.filter(e => e.startsWith(gearNamePrefix)); - self.gears.push({ name: gearName, description: gearDescription }); - }); + gearsNames.forEach((gearName) => { + const gearDescription = gearName.replace('gear-', ''); - self.gears.forEach((gear, index) => self.loadGear(self.gears, gear, index)); + self.gears.push({ name: gearName, description: gearDescription }); }); + + self.gears.forEach((gear, index) => self.loadGear(self.gears, gear, index)); } loadGear(gears, gear, index) { + if (gear) { + gear.isInternal = this.isInternal; + } + logStartAssembling(); logAddingGear(gears, gear, index); this.tryToLoad('gearStatus', gear, this.loadGearStatus); @@ -113,27 +119,27 @@ module.exports = class Assembler { } configsPath(gear) { - return `${global.__nodeModules}${gear.name}/config/config.json`; + return `${this.gearsPath}${gear.name}/config/config.json`; } tasksPath(gear) { - return `${global.__nodeModules}${gear.name}/config/tasks.json`; + return `${this.gearsPath}${gear.name}/config/tasks.json`; } categoriesPath(gear) { - return `${global.__nodeModules}${gear.name}/config/categories.json`; + return `${this.gearsPath}${gear.name}/config/categories.json`; } handlersPath(gear, handler) { - return `${global.__nodeModules}${gear.name}/src/handlers/${handler}`; + return `${this.gearsPath}${gear.name}/src/handlers/${handler}`; } configsHandlersPath(gear) { - return `${global.__nodeModules}${gear.name}/src/configHandler/configHandler`; + return `${this.gearsPath}${gear.name}/src/configHandler/configHandler`; } localesPath(gear) { - return `${global.__nodeModules}${gear.name}/locales`; + return `${this.gearsPath}${gear.name}/locales`; } }; diff --git a/src/core.js b/src/core.js index ce483d7..36e8124 100644 --- a/src/core.js +++ b/src/core.js @@ -1,6 +1,7 @@ 'use strict'; const util = require('util'); +const path = require('path'); const Bot = require('slackbots'); @@ -39,8 +40,15 @@ Core.prototype.run = function run() { Core.prototype.onStart = function onStart() { botUser = this.getUserByName(botName); + this.hubot = new Hubot(this); - this.hubot.gears = new Assembler().build(); + + const internalGearPath = path.join(__dirname, 'internal-gears/'); + this.hubot.gears = new Assembler(internalGearPath, true).build(); + + const gears = new Assembler(global.__nodeModules, false).build(); + this.hubot.gears = this.hubot.gears.concat(gears); + this.firstRunChecker(); this.setLanguage(); }; diff --git a/src/first-run.js b/src/first-run.js index 105638a..a8b7ce1 100644 --- a/src/first-run.js +++ b/src/first-run.js @@ -35,7 +35,9 @@ function message5(hubot) { function postGearsNames(hubot) { const speech = hubot.speech(); - hubot.gears.forEach(g => speech.item(g.description).line()); + const gears = hubot.gears.filter(g => !g.isInternal); + + gears.forEach(g => speech.item(g.description).line()); return speech.end(); } diff --git a/src/hubot.js b/src/hubot.js index 708d1f5..4d76e26 100644 --- a/src/hubot.js +++ b/src/hubot.js @@ -74,4 +74,18 @@ module.exports = class Hubot { return this.gears.find(g => g.description === gear); } + isAdminUser(user) { + return core.isAdminUser(user); + } + + removeBotNameFromMessage(message) { + let messageWithoutBotName = message.text; + + if (messageWithoutBotName.startsWith(`${core.name} `)) { + messageWithoutBotName = messageWithoutBotName.replace(`${core.name} `, ''); + } + + return messageWithoutBotName; + } + }; diff --git a/src/internal-gears/gear-hubot-gear-configure/config/categories.json b/src/internal-gears/gear-hubot-gear-configure/config/categories.json new file mode 100644 index 0000000..4ba9804 --- /dev/null +++ b/src/internal-gears/gear-hubot-gear-configure/config/categories.json @@ -0,0 +1,8 @@ +[ + { + "key": "hubot", + "name": "hubot-gear-configure:category.name", + "description": "hubot-gear-configure:category.description", + "visible": true + } +] diff --git a/src/internal-gears/gear-hubot-gear-configure/config/tasks.json b/src/internal-gears/gear-hubot-gear-configure/config/tasks.json new file mode 100644 index 0000000..e1c1632 --- /dev/null +++ b/src/internal-gears/gear-hubot-gear-configure/config/tasks.json @@ -0,0 +1,8 @@ +[ + { + "handler": "gear-configure", + "trigger": "hubot-gear-configure:command.configure.trigger", + "category": "hubot", + "description": "hubot-gear-configure:command.configure.description" + } +] diff --git a/src/internal-gears/gear-hubot-gear-configure/locales/en-US/translation.json b/src/internal-gears/gear-hubot-gear-configure/locales/en-US/translation.json new file mode 100644 index 0000000..5d366f2 --- /dev/null +++ b/src/internal-gears/gear-hubot-gear-configure/locales/en-US/translation.json @@ -0,0 +1,13 @@ +{ + "category": { + "name": "Gear Configuration", + "description": "Gears configurations" + }, + "command": { + "configure": { + "trigger": "configure $", + "description": "Enables configuration options for gears" + } + }, + "configure": "configure " +} diff --git a/src/internal-gears/gear-hubot-gear-configure/locales/pt-BR/translation.json b/src/internal-gears/gear-hubot-gear-configure/locales/pt-BR/translation.json new file mode 100644 index 0000000..7ee0c89 --- /dev/null +++ b/src/internal-gears/gear-hubot-gear-configure/locales/pt-BR/translation.json @@ -0,0 +1,13 @@ +{ + "category": { + "name": "Configuração de Engrenagens", + "description": "Configurações de engrenagens" + }, + "command": { + "configure": { + "trigger": "configurar $", + "description": "Habilita as opções de configuração para engrenagens." + } + }, + "configure": "configurar " +} diff --git a/src/internal-gears/gear-hubot-gear-configure/src/handlers/gear-configure.js b/src/internal-gears/gear-hubot-gear-configure/src/handlers/gear-configure.js new file mode 100644 index 0000000..584cbe6 --- /dev/null +++ b/src/internal-gears/gear-hubot-gear-configure/src/handlers/gear-configure.js @@ -0,0 +1,46 @@ +'use strict'; + +const trigger = require('../../../../message-handler/trigger'); +const conversation = require('../../../../message-handler/conversation'); + +exports.handle = handle; + +function handle(hubot, message) { + if (isGearConfigureMessage(hubot, message)) { + const gear = discoverGear(hubot, message); + + if (gear) { + hubot.isAdminUser(message.user).then((isAdmin) => { + if (isAdmin) { + const param = { + user: message.user, + gear: gearDescription(message, hubot), + interactions: gear.configs + }; + + conversation.startConversation(hubot, param, message); + } + }); + } + + return true; + } + + return false; +} + +function isGearConfigureMessage(hubot, message) { + return hubot.gears.find((gear) => { + const configureMessage = hubot.i18n('hubot-gear-configure:command.configure.trigger', { gear: gear.description }); + + return trigger.check(message.text, configureMessage).ok; + }) != null; +} + +function discoverGear(hubot, message) { + return hubot.gears.find(g => g.description === gearDescription(message, hubot)); +} + +function gearDescription(message, hubot) { + return message.text.replace(hubot.i18n('hubot-gear-configure:configure'), ''); +} diff --git a/src/internal-gears/gear-hubot-gears-status/config/categories.json b/src/internal-gears/gear-hubot-gears-status/config/categories.json new file mode 100644 index 0000000..6c7b630 --- /dev/null +++ b/src/internal-gears/gear-hubot-gears-status/config/categories.json @@ -0,0 +1,8 @@ +[ + { + "key": "hubot", + "name": "hubot-gears-status:category.name", + "description": "hubot-gears-status:category.description", + "visible": true + } +] diff --git a/src/internal-gears/gear-hubot-gears-status/config/tasks.json b/src/internal-gears/gear-hubot-gears-status/config/tasks.json new file mode 100644 index 0000000..2531826 --- /dev/null +++ b/src/internal-gears/gear-hubot-gears-status/config/tasks.json @@ -0,0 +1,15 @@ +[ + { + "handler": "status", + "trigger": "hubot-gears-status:command.activate.trigger", + "category": "hubot", + "description": "hubot-gears-status:command.activate.description" + }, + + { + "handler": "status", + "trigger": "hubot-gears-status:command.deactivate.trigger", + "category": "hubot", + "description": "hubot-gears-status:command.deactivate.description" + } +] diff --git a/src/internal-gears/gear-hubot-gears-status/locales/en-US/translation.json b/src/internal-gears/gear-hubot-gears-status/locales/en-US/translation.json new file mode 100644 index 0000000..12e6d8b --- /dev/null +++ b/src/internal-gears/gear-hubot-gears-status/locales/en-US/translation.json @@ -0,0 +1,27 @@ +{ + "category": { + "name": "General", + "description": "General commands" + }, + "command": { + "activate": { + "trigger": "activate $", + "description": "To activate a gear use the command replacing $ by gear name." + }, + "deactivate": { + "trigger": "deactivate $", + "description": "To deactivate a gear use the command replacing $ by gear name." + } + }, + "activation": { + "already": "This gear is already {{statusDescription}}.", + "success": "Successfully {{statusDescription}} *{{gearDescription}}*", + "error": "Could not {{statusDescription}} *{{gearDescription}}*", + "activate": "activate", + "deactivate": "deactivate", + "active": "active", + "inactive": "inactive", + "actived": "activated", + "deactivated": "deactivated" + } +} diff --git a/src/internal-gears/gear-hubot-gears-status/locales/pt-BR/translation.json b/src/internal-gears/gear-hubot-gears-status/locales/pt-BR/translation.json new file mode 100644 index 0000000..49ddf01 --- /dev/null +++ b/src/internal-gears/gear-hubot-gears-status/locales/pt-BR/translation.json @@ -0,0 +1,27 @@ +{ + "category": { + "name": "Geral", + "description": "Comandos gerais" + }, + "command": { + "activate": { + "trigger": "ativar $", + "description": "Para ativar uma engrenagem use o comando substituindo $ pelo nome da engrenagem." + }, + "deactivate": { + "trigger": "desativar $", + "description": "Para desativar uma engrenagem use o comando substituindo $ pelo nome da engrenagem." + } + }, + "activation": { + "already": "Esta engrenagem já está {{statusDescription}}.", + "success": "*{{gearDescription}}* {{statusDescription}} com sucesso", + "error": "Não consegui {{statusDescription}} *{{gearDescription}}*", + "activate": "ativar", + "deactivate": "desativar", + "active": "ativada", + "inactive": "inativada", + "actived": "ativado", + "deactivated": "desativado" + } +} diff --git a/src/internal-gears/gear-hubot-gears-status/src/handlers/status.js b/src/internal-gears/gear-hubot-gears-status/src/handlers/status.js new file mode 100644 index 0000000..735440e --- /dev/null +++ b/src/internal-gears/gear-hubot-gears-status/src/handlers/status.js @@ -0,0 +1,76 @@ +'use strict'; + +const db = require('../../../../lib/db'); +const trigger = require('../../../../message-handler/trigger'); + +exports.handle = handle; + +function handle(hubot, message, task, params) { + const action = getAction(message, hubot); + + if (action && isGearChangeStatusMessage(action, hubot, message)) { + hubot.isAdminUser(message.user) + .then(isAdmin => changeStatus(isAdmin, action, hubot, message, params[0])); + + return true; + } + + return false; +} + +function changeStatus(isAdmin, action, hubot, message, gearDescription) { + if (isAdmin) { + const gear = hubot.findGear(gearDescription); + + if (gear && !gear.isInternal) { + if (gear.active === action.status) { + hubot.speak(message, 'hubot-gears-status:activation.already', { statusDescription: action.already }); + } else { + changeGearStatus(action, hubot, gear.description) + .then(() => hubot.speak(message, sucessMessage(action, hubot, gear.description)), + () => hubot.speak(message, errorMessage(action, hubot, gear.description))); + } + } + } +} + +function changeGearStatus(action, hubot, gear) { + hubot.findGear(gear).active = action.status; + + return db.getDb().run('UPDATE gears SET active = ? WHERE description = ?', action.status, gear); +} + +function isGearChangeStatusMessage(action, hubot, message) { + return hubot.gears.find(gear => trigger.check(message, `${action.description} ${gear.description}`).ok) !== null; +} + +function sucessMessage(action, hubot, gearDescription) { + const messages = { statusDescription: action.success, gearDescription }; + return hubot.speech().append('hubot-gears-status:activation.success', messages).end(); +} + +function errorMessage(action, hubot, gearDescription) { + const messages = { statusDescription: action.description, gearDescription }; + return hubot.speech().append('hubot-gears-status:activation.error', messages).end(); +} + +function getAction(message, hubot) { + const active = hubot.i18n('hubot-gears-status:activation.active'); + const inactive = hubot.i18n('hubot-gears-status:activation.inactive'); + const activate = hubot.i18n('hubot-gears-status:activation.activate'); + const deactivate = hubot.i18n('hubot-gears-status:activation.deactivate'); + const activated = hubot.i18n('hubot-gears-status:activation.actived'); + const deactivated = hubot.i18n('hubot-gears-status:activation.deactivated'); + + const messageWithoutBotName = hubot.removeBotNameFromMessage(message); + + if (messageWithoutBotName.startsWith(activate)) { + return { description: activate, status: true, already: active, success: activated }; + } + + if (messageWithoutBotName.startsWith(deactivate)) { + return { description: deactivate, status: false, already: inactive, success: deactivated }; + } + + return null; +} diff --git a/src/internal-gears/gear-hubot-language/config/categories.json b/src/internal-gears/gear-hubot-language/config/categories.json new file mode 100644 index 0000000..14a1c38 --- /dev/null +++ b/src/internal-gears/gear-hubot-language/config/categories.json @@ -0,0 +1,8 @@ +[ + { + "key": "hubot", + "name": "hubot-language:category.name", + "description": "hubot-language:category.description", + "visible": true + } +] diff --git a/src/internal-gears/gear-hubot-language/config/tasks.json b/src/internal-gears/gear-hubot-language/config/tasks.json new file mode 100644 index 0000000..eef7a12 --- /dev/null +++ b/src/internal-gears/gear-hubot-language/config/tasks.json @@ -0,0 +1,8 @@ +[ + { + "handler": "language", + "trigger": "hubot-language:command.change.trigger", + "category": "hubot", + "description": "hubot-language:command.change.description" + } +] diff --git a/src/internal-gears/gear-hubot-language/locales/en-US/translation.json b/src/internal-gears/gear-hubot-language/locales/en-US/translation.json new file mode 100644 index 0000000..b95bd63 --- /dev/null +++ b/src/internal-gears/gear-hubot-language/locales/en-US/translation.json @@ -0,0 +1,15 @@ +{ + "category": { + "name": "General", + "description": "General commands" + }, + "command": { + "change": { + "trigger": "change the language to $", + "description": "To change the language use the command replacing $ by language. Ex: change the language to pt-br" + } + }, + + "sucess": "Language changed to *{{language}}*", + "error": "Ops. I don't know the language *{{language}}*" +} diff --git a/src/internal-gears/gear-hubot-language/locales/pt-BR/translation.json b/src/internal-gears/gear-hubot-language/locales/pt-BR/translation.json new file mode 100644 index 0000000..858a212 --- /dev/null +++ b/src/internal-gears/gear-hubot-language/locales/pt-BR/translation.json @@ -0,0 +1,15 @@ +{ + "category": { + "name": "Geral", + "description": "Comandos gerais" + }, + "command": { + "change": { + "trigger": "alterar idioma para $", + "description": "Para alterar o idioma use o comando substituindo $ pelo idioma." + } + }, + "sucess": "Idioma alterado para *{{language}}*", + "error": "Ops. Eu não conheço o idioma *{{language}}*" +} + diff --git a/src/message-handler/handlers/language-handler.js b/src/internal-gears/gear-hubot-language/src/handlers/language.js similarity index 53% rename from src/message-handler/handlers/language-handler.js rename to src/internal-gears/gear-hubot-language/src/handlers/language.js index e394cf7..6ccd442 100644 --- a/src/message-handler/handlers/language-handler.js +++ b/src/internal-gears/gear-hubot-language/src/handlers/language.js @@ -1,13 +1,13 @@ 'use strict'; -const db = require('../../lib/db'); -const i18n = require('../../lib/i18n'); -const trigger = require('../trigger'); +const db = require('../../../../lib/db'); +const i18n = require('../../../../lib/i18n'); +const trigger = require('../../../../message-handler/trigger'); exports.handle = handle; function handle(hubot, message) { - const acceptance = trigger.check(message.text, hubot.i18n('locale.change')); + const acceptance = trigger.check(message.text, hubot.i18n('hubot-language:command.change.trigger')); if (acceptance.ok) { const language = acceptance.params[0]; @@ -19,9 +19,9 @@ function handle(hubot, message) { db.getDb().run('UPDATE config SET language = ?', language); - hubot.speak(message, 'locale.sucess', { language }); + hubot.speak(message, 'hubot-language:sucess', { language }); } else { - hubot.speak(message, 'locale.error', { language }); + hubot.speak(message, 'hubot-language:error', { language }); } return true; diff --git a/src/message-handler/handlers/gear-configure-handler.js b/src/message-handler/handlers/gear-configure-handler.js deleted file mode 100644 index deb862d..0000000 --- a/src/message-handler/handlers/gear-configure-handler.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -const trigger = require('../trigger'); -const conversation = require('../conversation'); - -exports.handle = handle; - -function handle(hubot, message, core) { - if (isGearConfigureMessage(hubot, message)) { - core.isAdminUser(message.user).then((isAdmin) => { - if (isAdmin) { - const param = { - user: message.user, - gear: gearDescription(message, hubot), - interactions: discoverConfig(hubot, message) - }; - - conversation.startConversation(hubot, param, message); - } - }); - } -} - -function isGearConfigureMessage(hubot, message) { - return hubot.gears.find((gear) => { - const configureMessage = hubot.i18n('configureGear', { gear: gear.description }); - - return trigger.check(message.text, configureMessage).ok; - }) != null; -} - -function discoverConfig(hubot, message) { - return hubot.gears.find(g => g.description === gearDescription(message, hubot)).configs; -} - -function gearDescription(message, hubot) { - return message.text.replace(hubot.i18n('configure'), ''); -} diff --git a/src/message-handler/handlers/gear-status-handler.js b/src/message-handler/handlers/gear-status-handler.js deleted file mode 100644 index 3ba7ade..0000000 --- a/src/message-handler/handlers/gear-status-handler.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; - -const db = require('../../lib/db'); -const trigger = require('../trigger'); - -exports.handle = handle; - -function handle(hubot, message, core) { - const action = getAction(message, hubot); - - if (action && isGearChangeStatusMessage(action, hubot, message)) { - core.isAdminUser(message.user) - .then(isAdmin => changeStatus(isAdmin, action, hubot, message)); - - return true; - } - - return false; -} - -function changeStatus(isAdmin, action, hubot, message) { - if (isAdmin) { - const gear = discoverGear(action, hubot, message); - - if (gear) { - if (gear.active === action.status) { - hubot.speak(message, 'gears.already', { statusDescription: action.already }); - } else { - changeGearStatus(action, hubot, gear.description) - .then(() => hubot.speak(message, sucessMessage(action, hubot, gear.description)), - () => hubot.speak(message, errorMessage(action, hubot, gear.description))); - } - } - } -} - -function changeGearStatus(action, hubot, gear) { - hubot.findGear(gear).active = action.status; - - return db.getDb().run('UPDATE gears SET active = ? WHERE description = ?', action.status, gear); -} - -function isGearChangeStatusMessage(action, hubot, message) { - return hubot.gears.find(gear => trigger.check(message.text, `${action.description} ${gear.description}`).ok) !== null; -} - -function discoverGear(action, hubot, message) { - const gearDescription = message.text.replace(`${action.description} `, ''); - return hubot.findGear(gearDescription); -} - -function sucessMessage(action, hubot, gearDescription) { - return hubot.speech().append('gears.activation.success', { statusDescription: action.success, gearDescription }) - .end(); -} - -function errorMessage(action, hubot, gearDescription) { - return hubot.speech().append('gears.activation.error', { statusDescription: action.description, gearDescription }) - .end(); -} - -function getAction(message, hubot) { - const active = hubot.i18n('gears.activation.active'); - const inactive = hubot.i18n('gears.activation.inactive'); - const activate = hubot.i18n('gears.activation.activate'); - const deactivate = hubot.i18n('gears.activation.deactivate'); - const activated = hubot.i18n('gears.activation.actived'); - const deactivated = hubot.i18n('gears.activation.deactivated'); - - if (message.text.startsWith(activate)) { - return { description: activate, status: true, already: active, success: activated }; - } - - if (message.text.startsWith(deactivate)) { - return { description: deactivate, status: false, already: inactive, success: deactivated }; - } - - return null; -} diff --git a/src/message-handler/handlers/gears-tasks-handler.js b/src/message-handler/handlers/gears-tasks-handler.js index 444b66e..f34b6ef 100644 --- a/src/message-handler/handlers/gears-tasks-handler.js +++ b/src/message-handler/handlers/gears-tasks-handler.js @@ -20,7 +20,11 @@ function handle(hubot, message, core) { } function tryExecuteTask(hubot, core, gear, message, task) { - const acceptance = trigger.check(message.text, hubot.i18n(task.trigger)); + if (!shouldCallTask(message, core)) return false; + + const messageWithoutBotName = hubot.removeBotNameFromMessage(message); + + const acceptance = trigger.check(messageWithoutBotName, hubot.i18n(task.trigger)); if (acceptance.ok) { if (gear.active) { @@ -28,9 +32,13 @@ function tryExecuteTask(hubot, core, gear, message, task) { return true; } - const hubotClone = getHubotClone(core); const handler = getHandler(gear, task); - handler.handle(hubotClone, message, task, acceptance.params); + + if (gear.isInternal) { + handler.handle(hubot, message, task, acceptance.params); + } else { + handler.handle(getHubotClone(core), message, task, acceptance.params); + } } else { hubot.speak(message, 'feature.disabled'); } @@ -41,6 +49,21 @@ function tryExecuteTask(hubot, core, gear, message, task) { return false; } +function shouldCallTask(message, core) { + let triggerOk = false; + const isToBotMessage = message.text.startsWith(`${core.name} `); + + if (core.isPrivateConversation(message)) { + triggerOk = true; + } + + if (!core.isPrivateConversation(message) && isToBotMessage) { + triggerOk = true; + } + + return triggerOk; +} + function incorretMessageSource(hubot, task, message) { if (task.onlyInChannel && !hubot.isFromChannel(message)) { hubot.speak(message, 'feature.onlyChannel'); diff --git a/src/message-handler/message-handler.js b/src/message-handler/message-handler.js index eb3baa9..6a7503d 100644 --- a/src/message-handler/message-handler.js +++ b/src/message-handler/message-handler.js @@ -1,11 +1,8 @@ 'use strict'; const firstRun = require('./handlers/first-run-handler'); -const gearStatus = require('./handlers/gear-status-handler'); const gearsTasks = require('./handlers/gears-tasks-handler'); const conversation = require('./handlers/conversation-handler'); -const gearConfigure = require('./handlers/gear-configure-handler'); -const language = require('./handlers/language-handler'); exports.callTasks = callTasks; @@ -25,9 +22,6 @@ function getHandlers() { return [ firstRun, conversation, - gearStatus, - gearConfigure, - gearsTasks, - language + gearsTasks ]; } diff --git a/test/lib/assembler.js b/test/lib/assembler.js index 9a40979..2cd0a9e 100644 --- a/test/lib/assembler.js +++ b/test/lib/assembler.js @@ -127,5 +127,5 @@ describe('The Hubot Assembler', () => { }); function getAssembler() { - return new Assembler(); + return new Assembler(global.__nodeModules, false); } From b9c6f59aa23b23af0c32bd9c1e7872bfeb24357a Mon Sep 17 00:00:00 2001 From: robsonbittencourt Date: Wed, 24 May 2017 21:33:11 -0300 Subject: [PATCH 4/5] Fix --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ef51dd..8dff41d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ deploy: after_deploy: - if [[ "$TRAVIS_BRANCH" == "master" ]]; then - docker login -e="$DOCKER_EMAIL" -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" ; + docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" ; export VERSION=$(node -p -e "JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version") From f7bee026070e475158d4d0154dfe322f02d51bab Mon Sep 17 00:00:00 2001 From: robsonbittencourt Date: Sat, 27 May 2017 16:09:11 -0300 Subject: [PATCH 5/5] Update dependencies --- .travis.yml | 2 +- package.json | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8dff41d..31fc00e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ sudo: required language: node_js node_js: - - 7.1.0 + - 7.7.2 services: - docker diff --git a/package.json b/package.json index 0ee67b6..8b2a1c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hubot.js", - "version": "1.0.0", + "version": "1.1.0", "description": "A small robot written in Javascript (He does not like coffeescript)", "main": "index.js", "bin": { @@ -30,23 +30,23 @@ "dependencies": { "slackbots": "^0.5.3", "string": "^3.3.3", - "winston": "^2.3.0", - "q": "^1.4.1", - "sqlite": "^2.2.3", - "pm2": "^2.1.6", + "winston": "^2.3.1", + "q": "^2.0.3", + "sqlite": "^2.8.0", + "pm2": "^2.4.6", "dialog": "^0.2.0", - "yargs": "^6.4.0", + "yargs": "^8.0.1", "i18next": "^7.1.0", "i18next-sync-fs-backend": "^0.1.0", - "gear-help": "^2.0.0", - "gear-jenkins": "^2.0.0" + "gear-help": "^2.1.0", + "gear-jenkins": "^2.1.0" }, "devDependencies": { "chai": "^3.5.0", - "coveralls": "^2.11.15", - "eslint": "^3.11.1", + "coveralls": "^2.13.1", + "eslint": "^3.19.0", "eslint-config-hubot-js": "^1.0.4", "istanbul": "^0.4.5", - "mocha": "^3.2.0" + "mocha": "^3.4.1" } }