diff --git a/packages/rocketchat-authorization/server/startup.coffee b/packages/rocketchat-authorization/server/startup.coffee index 663f7354970ad..7e347bdeffb50 100644 --- a/packages/rocketchat-authorization/server/startup.coffee +++ b/packages/rocketchat-authorization/server/startup.coffee @@ -103,14 +103,14 @@ Meteor.startup -> roles : ['admin']} { _id: 'manage-integrations', - roles : ['admin']} + roles : ['admin', 'bot']} ] #alanning:roles roles = _.pluck(Roles.getAllRoles().fetch(), 'name'); for permission in permissions - RocketChat.models.Permissions.upsert( permission._id, {$setOnInsert : permission }) + RocketChat.models.Permissions.upsert( permission._id, {$set: permission }) for role in permission.roles unless role in roles Roles.createRole role diff --git a/packages/rocketchat-integrations/client/route.coffee b/packages/rocketchat-integrations/client/route.coffee index 4a87624ca342c..96c69e02cc183 100644 --- a/packages/rocketchat-integrations/client/route.coffee +++ b/packages/rocketchat-integrations/client/route.coffee @@ -24,3 +24,13 @@ FlowRouter.route '/admin/integrations/incoming/:id?', pageTitle: t('Integration_Incoming_WebHook') pageTemplate: 'integrationsIncoming' params: params + + +FlowRouter.route '/admin/integrations/outgoing/:id?', + name: 'admin-integrations-outgoing' + action: (params) -> + BlazeLayout.render 'main', + center: 'pageSettingsContainer' + pageTitle: t('Integration_Outgoing_WebHook') + pageTemplate: 'integrationsOutgoing' + params: params diff --git a/packages/rocketchat-integrations/client/views/integrations.html b/packages/rocketchat-integrations/client/views/integrations.html index d1075af7756be..580b06e6ab17c 100644 --- a/packages/rocketchat-integrations/client/views/integrations.html +++ b/packages/rocketchat-integrations/client/views/integrations.html @@ -7,26 +7,46 @@
{{#each integrations}} - -
diff --git a/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee b/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee index f61b55199dc08..8e8f09dd0e121 100644 --- a/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee +++ b/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee @@ -108,7 +108,7 @@ Template.integrationsIncoming.events closeOnConfirm: false html: false , -> - Meteor.call "deleteIntegration", params.id, (err, data) -> + Meteor.call "deleteIncomingIntegration", params.id, (err, data) -> swal title: t('Deleted') text: t('Your_entry_has_been_deleted') @@ -141,16 +141,15 @@ Template.integrationsIncoming.events params = Template.instance().data.params?() if params?.id? - Meteor.call "updateIntegration", params.id, integration, (err, data) -> + Meteor.call "updateIncomingIntegration", params.id, integration, (err, data) -> if err? return toastr.error TAPi18n.__(err.error) toastr.success TAPi18n.__("Integration_updated") else - integration.type = 'webhook-incoming' integration.username = username - Meteor.call "addIntegration", integration, (err, data) -> + Meteor.call "addIncomingIntegration", integration, (err, data) -> if err? return toastr.error TAPi18n.__(err.error) diff --git a/packages/rocketchat-integrations/client/views/integrationsIncoming.html b/packages/rocketchat-integrations/client/views/integrationsIncoming.html index 75e690c9ce35f..42ffe785ae739 100644 --- a/packages/rocketchat-integrations/client/views/integrationsIncoming.html +++ b/packages/rocketchat-integrations/client/views/integrationsIncoming.html @@ -52,7 +52,7 @@
{{_ "You_can_use_an_emoji_as_avatar"}}
-
{{_ "Example_s" ":ghost:"}}
+
{{{_ "Example_s" ":ghost:"}}}
{{#if data.token}} diff --git a/packages/rocketchat-integrations/client/views/integrationsNew.html b/packages/rocketchat-integrations/client/views/integrationsNew.html index 496cf5112bbca..f4425ff8c3d2b 100644 --- a/packages/rocketchat-integrations/client/views/integrationsNew.html +++ b/packages/rocketchat-integrations/client/views/integrationsNew.html @@ -20,7 +20,7 @@ - + diff --git a/packages/rocketchat-integrations/client/views/integrationsOutgoing.coffee b/packages/rocketchat-integrations/client/views/integrationsOutgoing.coffee new file mode 100644 index 0000000000000..7a46f17ffb204 --- /dev/null +++ b/packages/rocketchat-integrations/client/views/integrationsOutgoing.coffee @@ -0,0 +1,170 @@ +Template.integrationsOutgoing.onCreated -> + @record = new ReactiveVar + username: 'rocket.cat' + token: Random.id(24) + + +Template.integrationsOutgoing.helpers + + join: (arr, sep) -> + if not arr?.join? + return arr + + return arr.join sep + + hasPermission: -> + return RocketChat.authz.hasAllPermission 'manage-integrations' + + data: -> + params = Template.instance().data.params?() + + if params?.id? + data = ChatIntegrations.findOne({_id: params.id}) + if data? + if not data.token? + data.token = Random.id(24) + return data + + return Template.instance().record.curValue + + example: -> + record = Template.instance().record.get() + return {} = + _id: Random.id() + alias: record.alias + emoji: record.emoji + avatar: record.avatar + msg: 'Response text' + bot: + i: Random.id() + groupable: false + attachments: [{ + title: "Rocket.Chat" + title_link: "https://rocket.chat" + text: "Rocket.Chat, the best open source chat" + image_url: "https://rocket.chat/images/mockup.png" + color: "#764FA5" + }] + ts: new Date + u: + _id: Random.id() + username: record.username + + exampleJson: -> + record = Template.instance().record.get() + data = + username: record.alias + icon_emoji: record.emoji + icon_url: record.avatar + text: 'Response text' + attachments: [{ + title: "Rocket.Chat" + title_link: "https://rocket.chat" + text: "Rocket.Chat, the best open source chat" + image_url: "https://rocket.chat/images/mockup.png" + color: "#764FA5" + }] + + for key, value of data + delete data[key] if value in [null, ""] + + return hljs.highlight('json', JSON.stringify(data, null, 2)).value + + +Template.integrationsOutgoing.events + "blur input": (e, t) -> + t.record.set + name: $('[name=name]').val().trim() + alias: $('[name=alias]').val().trim() + emoji: $('[name=emoji]').val().trim() + avatar: $('[name=avatar]').val().trim() + channel: $('[name=channel]').val().trim() + username: $('[name=username]').val().trim() + triggerWords: $('[name=triggerWords]').val().trim() + urls: $('[name=urls]').val().trim() + token: $('[name=token]').val().trim() + + + "click .submit > .delete": -> + params = Template.instance().data.params() + + swal + title: t('Are_you_sure') + text: t('You_will_not_be_able_to_recover') + type: 'warning' + showCancelButton: true + confirmButtonColor: '#DD6B55' + confirmButtonText: t('Yes_delete_it') + cancelButtonText: t('Cancel') + closeOnConfirm: false + html: false + , -> + Meteor.call "deleteOutgoingIntegration", params.id, (err, data) -> + swal + title: t('Deleted') + text: t('Your_entry_has_been_deleted') + type: 'success' + timer: 1000 + showConfirmButton: false + + FlowRouter.go "admin-integrations" + + "click .submit > .save": -> + name = $('[name=name]').val().trim() + alias = $('[name=alias]').val().trim() + emoji = $('[name=emoji]').val().trim() + avatar = $('[name=avatar]').val().trim() + channel = $('[name=channel]').val().trim() + username = $('[name=username]').val().trim() + triggerWords = $('[name=triggerWords]').val().trim() + urls = $('[name=urls]').val().trim() + token = $('[name=token]').val().trim() + + if username is '' + return toastr.error TAPi18n.__("The_username_is_required") + + triggerWords = triggerWords.split(',') + for triggerWord, index in triggerWords + triggerWords[index] = triggerWord.trim() + delete triggerWords[index] if triggerWord.trim() is '' + + triggerWords = _.without triggerWords, [undefined] + + if triggerWords.length is 0 and channel.trim() is '' + return toastr.error TAPi18n.__("You should inform at least one trigger word if you do not inform a channel") + + urls = urls.split('\n') + for url, index in urls + urls[index] = url.trim() + delete urls[index] if url.trim() is '' + + urls = _.without urls, [undefined] + + if urls.length is 0 + return toastr.error TAPi18n.__("You_should_inform_one_url_at_least") + + integration = + channel: channel + username: username + alias: alias if alias isnt '' + emoji: emoji if emoji isnt '' + avatar: avatar if avatar isnt '' + name: name if name isnt '' + triggerWords: triggerWords if triggerWords isnt '' + urls: urls if urls isnt '' + token: token if token isnt '' + + params = Template.instance().data.params?() + if params?.id? + Meteor.call "updateOutgoingIntegration", params.id, integration, (err, data) -> + if err? + return toastr.error TAPi18n.__(err.error) + + toastr.success TAPi18n.__("Integration_updated") + else + Meteor.call "addOutgoingIntegration", integration, (err, data) -> + if err? + return toastr.error TAPi18n.__(err.error) + + toastr.success TAPi18n.__("Integration_added") + FlowRouter.go "admin-integrations-outgoing", {id: data._id} diff --git a/packages/rocketchat-integrations/client/views/integrationsOutgoing.html b/packages/rocketchat-integrations/client/views/integrationsOutgoing.html new file mode 100644 index 0000000000000..1b009cd93c09a --- /dev/null +++ b/packages/rocketchat-integrations/client/views/integrationsOutgoing.html @@ -0,0 +1,101 @@ + diff --git a/packages/rocketchat-integrations/package.js b/packages/rocketchat-integrations/package.js index fe6df9670d812..06aebfe354d39 100644 --- a/packages/rocketchat-integrations/package.js +++ b/packages/rocketchat-integrations/package.js @@ -13,6 +13,7 @@ Package.onUse(function(api) { api.use('underscore'); api.use('simple:highlight.js'); api.use('rocketchat:lib@0.0.1'); + api.use('alanning:roles@1.2.12'); api.use('kadira:flow-router', 'client'); api.use('templating', 'client'); @@ -29,6 +30,8 @@ Package.onUse(function(api) { api.addFiles('client/views/integrationsNew.coffee', 'client'); api.addFiles('client/views/integrationsIncoming.html', 'client'); api.addFiles('client/views/integrationsIncoming.coffee', 'client'); + api.addFiles('client/views/integrationsOutgoing.html', 'client'); + api.addFiles('client/views/integrationsOutgoing.coffee', 'client'); // stylesheets api.addAssets('client/stylesheets/integrations.less', 'server'); @@ -40,13 +43,19 @@ Package.onUse(function(api) { api.addFiles('server/publications/integrations.coffee', 'server'); // methods - api.addFiles('server/methods/addIntegration.coffee', 'server'); - api.addFiles('server/methods/updateIntegration.coffee', 'server'); - api.addFiles('server/methods/deleteIntegration.coffee', 'server'); + api.addFiles('server/methods/incoming/addIncomingIntegration.coffee', 'server'); + api.addFiles('server/methods/incoming/updateIncomingIntegration.coffee', 'server'); + api.addFiles('server/methods/incoming/deleteIncomingIntegration.coffee', 'server'); + api.addFiles('server/methods/outgoing/addOutgoingIntegration.coffee', 'server'); + api.addFiles('server/methods/outgoing/updateOutgoingIntegration.coffee', 'server'); + api.addFiles('server/methods/outgoing/deleteOutgoingIntegration.coffee', 'server'); // api api.addFiles('server/api/api.coffee', 'server'); + + api.addFiles('server/triggers.coffee', 'server'); + var _ = Npm.require('underscore'); var fs = Npm.require('fs'); tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-integrations/i18n'), function(filename) { diff --git a/packages/rocketchat-integrations/server/api/api.coffee b/packages/rocketchat-integrations/server/api/api.coffee index 068df884161f0..c8043272fcc51 100644 --- a/packages/rocketchat-integrations/server/api/api.coffee +++ b/packages/rocketchat-integrations/server/api/api.coffee @@ -38,8 +38,9 @@ Api.addRoute ':integrationId/:userId/:token', authRequired: true, error: 'invalid-channel' rid = room._id - Meteor.runAsUser user._id, -> - Meteor.call 'joinRoom', room._id + if room.t is 'c' + Meteor.runAsUser user._id, -> + Meteor.call 'joinRoom', room._id when '@' roomUser = RocketChat.models.Users.findOne @@ -60,7 +61,7 @@ Api.addRoute ':integrationId/:userId/:token', authRequired: true, if not room Meteor.runAsUser user._id, -> - Meteor.call 'createDirectMessage', roomUser._id + Meteor.call 'createDirectMessage', roomUser.username room = RocketChat.models.Rooms.findOne(rid) else @@ -100,3 +101,29 @@ Api.addRoute ':integrationId/:userId/:token', authRequired: true, statusCode: 200 body: success: true + + +Api.addRoute 'manageintegrations/:integrationId/:userId/:token', authRequired: true, + post: -> + if @bodyParams?.payload? + @bodyParams = JSON.parse @bodyParams.payload + + integration = RocketChat.models.Integrations.findOne(@urlParams.integrationId) + user = RocketChat.models.Users.findOne(@userId) + + if not integration? + return {} = + statusCode: 400 + body: + success: false + error: 'Invalid integraiton id' + + switch @bodyParams.action + when 'addOutgoingIntegration' + Meteor.runAsUser user._id, => + Meteor.call 'addOutgoingIntegration', @bodyParams.data + + return {} = + statusCode: 200 + body: + success: true diff --git a/packages/rocketchat-integrations/server/methods/addIntegration.coffee b/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee similarity index 66% rename from packages/rocketchat-integrations/server/methods/addIntegration.coffee rename to packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee index 454b87d6bd236..67c33d156f6a7 100644 --- a/packages/rocketchat-integrations/server/methods/addIntegration.coffee +++ b/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee @@ -1,22 +1,22 @@ Meteor.methods - addIntegration: (integration) -> + addIncomingIntegration: (integration) -> if not RocketChat.authz.hasPermission @userId, 'manage-integrations' throw new Meteor.Error 'not_authorized' if not _.isString(integration.channel) - throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel must be string' + throw new Meteor.Error 'invalid_channel', '[methods] addIncomingIntegration -> channel must be string' if integration.channel.trim() is '' - throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel can\'t be empty' + throw new Meteor.Error 'invalid_channel', '[methods] addIncomingIntegration -> channel can\'t be empty' if integration.channel[0] not in ['@', '#'] - throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel should start with # or @' + throw new Meteor.Error 'invalid_channel', '[methods] addIncomingIntegration -> channel should start with # or @' if not _.isString(integration.username) - throw new Meteor.Error 'invalid_username', '[methods] addIntegration -> username must be string' + throw new Meteor.Error 'invalid_username', '[methods] addIncomingIntegration -> username must be string' if integration.username.trim() is '' - throw new Meteor.Error 'invalid_username', '[methods] addIntegration -> username can\'t be empty' + throw new Meteor.Error 'invalid_username', '[methods] addIncomingIntegration -> username can\'t be empty' record = undefined channelType = integration.channel[0] @@ -37,12 +37,12 @@ Meteor.methods ] if record is undefined - throw new Meteor.Error 'channel_does_not_exists', "[methods] addIntegration -> The channel does not exists" + throw new Meteor.Error 'channel_does_not_exists', "[methods] addIncomingIntegration -> The channel does not exists" user = RocketChat.models.Users.findOne({username: integration.username}) if not user? - throw new Meteor.Error 'user_does_not_exists', "[methods] addIntegration -> The username does not exists" + throw new Meteor.Error 'user_does_not_exists', "[methods] addIncomingIntegration -> The username does not exists" stampedToken = Accounts._generateStampedLoginToken() hashStampedToken = Accounts._hashStampedToken(stampedToken) @@ -53,6 +53,7 @@ Meteor.methods hashedToken: hashStampedToken.hashedToken integration: true + integration.type = 'webhook-incoming' integration.token = hashStampedToken.hashedToken integration.userId = user._id integration._createdAt = new Date @@ -60,6 +61,8 @@ Meteor.methods RocketChat.models.Users.update {_id: user._id}, updateObj + Roles.addUsersToRoles user._id, 'bot', 'bot' + integration._id = RocketChat.models.Integrations.insert integration return integration diff --git a/packages/rocketchat-integrations/server/methods/deleteIntegration.coffee b/packages/rocketchat-integrations/server/methods/incoming/deleteIncomingIntegration.coffee similarity index 75% rename from packages/rocketchat-integrations/server/methods/deleteIntegration.coffee rename to packages/rocketchat-integrations/server/methods/incoming/deleteIncomingIntegration.coffee index d6f22c27701d2..692bb014d273b 100644 --- a/packages/rocketchat-integrations/server/methods/deleteIntegration.coffee +++ b/packages/rocketchat-integrations/server/methods/incoming/deleteIncomingIntegration.coffee @@ -1,12 +1,12 @@ Meteor.methods - deleteIntegration: (integrationId) -> + deleteIncomingIntegration: (integrationId) -> if not RocketChat.authz.hasPermission @userId, 'manage-integrations' throw new Meteor.Error 'not_authorized' integration = RocketChat.models.Integrations.findOne(integrationId) if not integration? - throw new Meteor.Error 'invalid_integration', '[methods] addIntegration -> integration not found' + throw new Meteor.Error 'invalid_integration', '[methods] deleteIncomingIntegration -> integration not found' updateObj = $pull: diff --git a/packages/rocketchat-integrations/server/methods/updateIntegration.coffee b/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee similarity index 67% rename from packages/rocketchat-integrations/server/methods/updateIntegration.coffee rename to packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee index e09c565efb094..25787a2898755 100644 --- a/packages/rocketchat-integrations/server/methods/updateIntegration.coffee +++ b/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee @@ -1,19 +1,19 @@ Meteor.methods - updateIntegration: (integrationId, integration) -> + updateIncomingIntegration: (integrationId, integration) -> if not RocketChat.authz.hasPermission @userId, 'manage-integrations' throw new Meteor.Error 'not_authorized' if not _.isString(integration.channel) - throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel must be string' + throw new Meteor.Error 'invalid_channel', '[methods] updateIncomingIntegration -> channel must be string' if integration.channel.trim() is '' - throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel can\'t be empty' + throw new Meteor.Error 'invalid_channel', '[methods] updateIncomingIntegration -> channel can\'t be empty' if integration.channel[0] not in ['@', '#'] - throw new Meteor.Error 'invalid_channel', '[methods] addIntegration -> channel should start with # or @' + throw new Meteor.Error 'invalid_channel', '[methods] updateIncomingIntegration -> channel should start with # or @' if not RocketChat.models.Integrations.findOne(integrationId)? - throw new Meteor.Error 'invalid_integration', '[methods] addIntegration -> integration not found' + throw new Meteor.Error 'invalid_integration', '[methods] updateIncomingIntegration -> integration not found' record = undefined channelType = integration.channel[0] @@ -34,7 +34,7 @@ Meteor.methods ] if record is undefined - throw new Meteor.Error 'channel_does_not_exists', "[methods] addIntegration -> The channel does not exists" + throw new Meteor.Error 'channel_does_not_exists', "[methods] updateIncomingIntegration -> The channel does not exists" RocketChat.models.Integrations.update integrationId, $set: diff --git a/packages/rocketchat-integrations/server/methods/outgoing/addOutgoingIntegration.coffee b/packages/rocketchat-integrations/server/methods/outgoing/addOutgoingIntegration.coffee new file mode 100644 index 0000000000000..c27c7c1244184 --- /dev/null +++ b/packages/rocketchat-integrations/server/methods/outgoing/addOutgoingIntegration.coffee @@ -0,0 +1,73 @@ +Meteor.methods + addOutgoingIntegration: (integration) -> + if not RocketChat.authz.hasPermission(@userId, 'manage-integrations') and not RocketChat.authz.hasPermission(@userId, 'manage-integrations', 'bot') + throw new Meteor.Error 'not_authorized' + + if integration.username.trim() is '' + throw new Meteor.Error 'invalid_username', '[methods] addOutgoingIntegration -> username can\'t be empty' + + if not Match.test integration.urls, [String] + throw new Meteor.Error 'invalid_urls', '[methods] addOutgoingIntegration -> urls must be an array' + + for url, index in integration.urls + delete integration.urls[index] if url.trim() is '' + + integration.urls = _.without integration.urls, [undefined] + + if integration.urls.length is 0 + throw new Meteor.Error 'invalid_urls', '[methods] addOutgoingIntegration -> urls is required' + + if integration.channel?.trim() isnt '' and integration.channel[0] not in ['@', '#'] + throw new Meteor.Error 'invalid_channel', '[methods] addOutgoingIntegration -> channel should start with # or @' + + if not integration.token? or integration.token?.trim() is '' + throw new Meteor.Error 'invalid_token', '[methods] addOutgoingIntegration -> token is required' + + if integration.triggerWords? + if not Match.test integration.triggerWords, [String] + throw new Meteor.Error 'invalid_triggerWords', '[methods] addOutgoingIntegration -> triggerWords must be an array' + + for triggerWord, index in integration.triggerWords + delete integration.triggerWords[index] if triggerWord.trim() is '' + + integration.triggerWords = _.without integration.triggerWords, [undefined] + + if integration.triggerWords.length is 0 and not integration.channel? + throw new Meteor.Error 'invalid_triggerWords', '[methods] addOutgoingIntegration -> triggerWords is required if channel is empty' + + + if integration.channel?.trim() isnt '' + record = undefined + channelType = integration.channel[0] + channel = integration.channel.substr(1) + + switch channelType + when '#' + record = RocketChat.models.Rooms.findOne + $or: [ + {_id: channel} + {name: channel} + ] + when '@' + record = RocketChat.models.Users.findOne + $or: [ + {_id: channel} + {username: channel} + ] + + if record is undefined + throw new Meteor.Error 'channel_does_not_exists', "[methods] addOutgoingIntegration -> The channel does not exists" + + user = RocketChat.models.Users.findOne({username: integration.username}) + + if not user? + throw new Meteor.Error 'user_does_not_exists', "[methods] addOutgoingIntegration -> The username does not exists" + + integration.type = 'webhook-outgoing' + integration.userId = user._id + integration._createdAt = new Date + integration._createdBy = RocketChat.models.Users.findOne @userId, {fields: {username: 1}} + + integration._id = RocketChat.models.Integrations.insert integration + + return integration diff --git a/packages/rocketchat-integrations/server/methods/outgoing/deleteOutgoingIntegration.coffee b/packages/rocketchat-integrations/server/methods/outgoing/deleteOutgoingIntegration.coffee new file mode 100644 index 0000000000000..b13af8bdd5144 --- /dev/null +++ b/packages/rocketchat-integrations/server/methods/outgoing/deleteOutgoingIntegration.coffee @@ -0,0 +1,13 @@ +Meteor.methods + deleteOutgoingIntegration: (integrationId) -> + if not RocketChat.authz.hasPermission @userId, 'manage-integrations' + throw new Meteor.Error 'not_authorized' + + integration = RocketChat.models.Integrations.findOne(integrationId) + + if not integration? + throw new Meteor.Error 'invalid_integration', '[methods] deleteOutgoingIntegration -> integration not found' + + RocketChat.models.Integrations.remove _id: integrationId + + return true diff --git a/packages/rocketchat-integrations/server/methods/outgoing/updateOutgoingIntegration.coffee b/packages/rocketchat-integrations/server/methods/outgoing/updateOutgoingIntegration.coffee new file mode 100644 index 0000000000000..d91c552aed199 --- /dev/null +++ b/packages/rocketchat-integrations/server/methods/outgoing/updateOutgoingIntegration.coffee @@ -0,0 +1,84 @@ +Meteor.methods + updateOutgoingIntegration: (integrationId, integration) -> + if not RocketChat.authz.hasPermission @userId, 'manage-integrations' + throw new Meteor.Error 'not_authorized' + + if integration.username.trim() is '' + throw new Meteor.Error 'invalid_username', '[methods] updateOutgoingIntegration -> username can\'t be empty' + + if not Match.test integration.urls, [String] + throw new Meteor.Error 'invalid_urls', '[methods] updateOutgoingIntegration -> urls must be an array' + + for url, index in integration.urls + delete integration.urls[index] if url.trim() is '' + + integration.urls = _.without integration.urls, [undefined] + + if integration.urls.length is 0 + throw new Meteor.Error 'invalid_urls', '[methods] updateOutgoingIntegration -> urls is required' + + if integration.channel?.trim() isnt '' and integration.channel[0] not in ['@', '#'] + throw new Meteor.Error 'invalid_channel', '[methods] updateOutgoingIntegration -> channel should start with # or @' + + if not integration.token? or integration.token?.trim() is '' + throw new Meteor.Error 'invalid_token', '[methods] updateOutgoingIntegration -> token is required' + + if integration.triggerWords? + if not Match.test integration.triggerWords, [String] + throw new Meteor.Error 'invalid_triggerWords', '[methods] updateOutgoingIntegration -> triggerWords must be an array' + + for triggerWord, index in integration.triggerWords + delete integration.triggerWords[index] if triggerWord.trim() is '' + + integration.triggerWords = _.without integration.triggerWords, [undefined] + + if integration.triggerWords.length is 0 and not integration.channel? + throw new Meteor.Error 'invalid_triggerWords', '[methods] updateOutgoingIntegration -> triggerWords is required if channel is empty' + + if not RocketChat.models.Integrations.findOne(integrationId)? + throw new Meteor.Error 'invalid_integration', '[methods] updateOutgoingIntegration -> integration not found' + + + if integration.channel?.trim() isnt '' + record = undefined + channelType = integration.channel[0] + channel = integration.channel.substr(1) + + switch channelType + when '#' + record = RocketChat.models.Rooms.findOne + $or: [ + {_id: channel} + {name: channel} + ] + when '@' + record = RocketChat.models.Users.findOne + $or: [ + {_id: channel} + {username: channel} + ] + + if record is undefined + throw new Meteor.Error 'channel_does_not_exists', "[methods] updateOutgoingIntegration -> The channel does not exists" + + user = RocketChat.models.Users.findOne({username: integration.username}) + + if not user? + throw new Meteor.Error 'user_does_not_exists', "[methods] updateOutgoingIntegration -> The username does not exists" + + RocketChat.models.Integrations.update integrationId, + $set: + name: integration.name + avatar: integration.avatar + emoji: integration.emoji + alias: integration.alias + channel: integration.channel + username: integration.username + userId: user._id + urls: integration.urls + token: integration.token + triggerWords: integration.triggerWords + _updatedAt: new Date + _updatedBy: RocketChat.models.Users.findOne @userId, {fields: {username: 1}} + + return RocketChat.models.Integrations.findOne(integrationId) diff --git a/packages/rocketchat-integrations/server/triggers.coffee b/packages/rocketchat-integrations/server/triggers.coffee new file mode 100644 index 0000000000000..9a610cbfc670d --- /dev/null +++ b/packages/rocketchat-integrations/server/triggers.coffee @@ -0,0 +1,94 @@ +triggers = {} + +RocketChat.models.Integrations.find({type: 'webhook-outgoing'}).observe + added: (record) -> + channel = record.channel or '__any' + triggers[channel] ?= {} + triggers[channel][record._id] = record + + changed: (record) -> + channel = record.channel or '__any' + triggers[channel] ?= {} + triggers[channel][record._id] = record + + removed: (record) -> + channel = record.channel or '__any' + delete triggers[channel][record._id] + + +ExecuteTriggerUrl = (url, trigger, message, room, tries=0) -> + console.log tries + word = undefined + if trigger.triggerWords?.length > 0 + for triggerWord in trigger.triggerWords + if message.msg.indexOf(triggerWord) is 0 + word = triggerWord + break + + # Stop if there are triggerWords but none match + if not word? + return + + data = + token: trigger.token + # team_id=T0001 + # team_domain=example + channel_id: room._id + channel_name: room.name + timestamp: message.ts + user_id: message.u._id + user_name: message.u.username + text: message.msg + + if word? + data.trigger_word = word + + opts = + data: data + npmRequestOptions: + rejectUnauthorized: !RocketChat.settings.get 'Allow_Invalid_SelfSigned_Certs' + strictSSL: !RocketChat.settings.get 'Allow_Invalid_SelfSigned_Certs' + headers: + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36' + + HTTP.call 'POST', url, opts, (error, result) -> + console.log error, result + if not result? or result.statusCode isnt 200 + if tries <= 6 + # Try again in 0.1s, 1s, 10s, 1m40s, 16m40s, 2h46m40s and 27h46m40s + Meteor.setTimeout -> + ExecuteTriggerUrl url, trigger, message, room, tries+1 + , Math.pow(10, tries+2) + return + + # TODO process return and insert message if necessary + + + +ExecuteTrigger = (trigger, message, room) -> + for url in trigger.urls + ExecuteTriggerUrl url, trigger, message, room + + +ExecuteTriggers = (message, room) -> + if not room? + return + + triggersToExecute = [] + + if triggers['#'+room._id]? + triggersToExecute.push trigger for key, trigger of triggers['#'+room._id] + + if triggers['#'+room.name]? + triggersToExecute.push trigger for key, trigger of triggers['#'+room.name] + + if triggers.__any? + triggersToExecute.push trigger for key, trigger of triggers.__any + + for triggerToExecute in triggersToExecute + ExecuteTrigger triggerToExecute, message, room + + return message + + +RocketChat.callbacks.add 'afterSaveMessage', ExecuteTriggers, RocketChat.callbacks.priority.LOW diff --git a/packages/rocketchat-lib/server/functions/sendMessage.coffee b/packages/rocketchat-lib/server/functions/sendMessage.coffee index 20620d1c9dc55..6b14009c97ccb 100644 --- a/packages/rocketchat-lib/server/functions/sendMessage.coffee +++ b/packages/rocketchat-lib/server/functions/sendMessage.coffee @@ -30,7 +30,7 @@ RocketChat.sendMessage = (user, message, room, options) -> ### Meteor.defer -> - RocketChat.callbacks.run 'afterSaveMessage', message + RocketChat.callbacks.run 'afterSaveMessage', message, room ### Update all the room activity tracker fields diff --git a/packages/rocketchat-slashcommands-invite/server.coffee b/packages/rocketchat-slashcommands-invite/server.coffee index 36ae07981e531..4b619023139d4 100644 --- a/packages/rocketchat-slashcommands-invite/server.coffee +++ b/packages/rocketchat-slashcommands-invite/server.coffee @@ -37,8 +37,9 @@ class Invite } return - Meteor.runAsUser user._id, -> - Meteor.call 'joinRoom', item.rid + Meteor.call 'addUserToRoom', + rid: item.rid + username: user.username RocketChat.slashCommands.add 'invite', Invite diff --git a/packages/rocketchat-ui-message/message/message.coffee b/packages/rocketchat-ui-message/message/message.coffee index 3e1b3aef09911..bf417c06df4ce 100644 --- a/packages/rocketchat-ui-message/message/message.coffee +++ b/packages/rocketchat-ui-message/message/message.coffee @@ -128,7 +128,7 @@ Template.message.onViewRendered = (context) -> else $currentNode.removeClass('new-day') - if previousDataset.groupable is 'false' + if previousDataset.groupable is 'false' or currentDataset.groupable is 'false' $currentNode.removeClass('sequential') else if previousDataset.username isnt currentDataset.username or parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000