Merge pull request #1744 from RocketChat/improvements/outgoing-hooks

Improvements/outgoing hooks
pull/1747/head
Rodrigo Nascimento 10 years ago
commit a83faef69e
  1. 4
      packages/rocketchat-authorization/server/startup.coffee
  2. 10
      packages/rocketchat-integrations/client/route.coffee
  3. 50
      packages/rocketchat-integrations/client/views/integrations.html
  4. 7
      packages/rocketchat-integrations/client/views/integrationsIncoming.coffee
  5. 2
      packages/rocketchat-integrations/client/views/integrationsIncoming.html
  6. 4
      packages/rocketchat-integrations/client/views/integrationsNew.html
  7. 170
      packages/rocketchat-integrations/client/views/integrationsOutgoing.coffee
  8. 101
      packages/rocketchat-integrations/client/views/integrationsOutgoing.html
  9. 15
      packages/rocketchat-integrations/package.js
  10. 33
      packages/rocketchat-integrations/server/api/api.coffee
  11. 19
      packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee
  12. 4
      packages/rocketchat-integrations/server/methods/incoming/deleteIncomingIntegration.coffee
  13. 12
      packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.coffee
  14. 73
      packages/rocketchat-integrations/server/methods/outgoing/addOutgoingIntegration.coffee
  15. 13
      packages/rocketchat-integrations/server/methods/outgoing/deleteOutgoingIntegration.coffee
  16. 84
      packages/rocketchat-integrations/server/methods/outgoing/updateOutgoingIntegration.coffee
  17. 94
      packages/rocketchat-integrations/server/triggers.coffee
  18. 2
      packages/rocketchat-lib/server/functions/sendMessage.coffee
  19. 5
      packages/rocketchat-slashcommands-invite/server.coffee
  20. 2
      packages/rocketchat-ui-message/message/message.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

@ -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

@ -7,26 +7,46 @@
<div class="section">
<div class="admin-integrations-new-panel">
{{#each integrations}}
<a href="{{pathFor "admin-integrations-incoming" id=_id}}">
<div class="admin-integrations-new-item">
<i class="icon-login"></i>
<div class="admin-integrations-new-item-body">
<div class="admin-integrations-new-item-title">
Incoming WebHook {{#if name}}- {{name}}{{/if}}
</div>
<div class="admin-integrations-new-item-description">
{{{_ "Post_to_s_as_s" channel username}}}
</div>
<div class="admin-integrations-new-item-description">
{{{_ "Created_at_s_by_s" (dateFormated _createdAt) _createdBy.username}}}
{{#if $eq type 'webhook-incoming'}}
<a href="{{pathFor "admin-integrations-incoming" id=_id}}">
<div class="admin-integrations-new-item">
<i class="icon-login"></i>
<div class="admin-integrations-new-item-body">
<div class="admin-integrations-new-item-title">
Incoming WebHook {{#if name}}- {{name}}{{/if}}
</div>
<div class="admin-integrations-new-item-description">
{{{_ "Post_to_s_as_s" channel username}}}
</div>
<div class="admin-integrations-new-item-description">
{{{_ "Created_at_s_by_s" (dateFormated _createdAt) _createdBy.username}}}
</div>
</div>
<i class="icon-angle-right"></i>
</div>
<i class="icon-angle-right"></i>
</div>
</a>
</a>
{{/if}}
{{else}}
<h1>{{_ "There_is_no_integrations"}}</h1>
{{/each}}
{{#each integrations}}
{{#if $eq type 'webhook-outgoing'}}
<a href="{{pathFor "admin-integrations-outgoing" id=_id}}">
<div class="admin-integrations-new-item">
<i class="icon-login"></i>
<div class="admin-integrations-new-item-body">
<div class="admin-integrations-new-item-title">
Outgoing WebHook {{#if name}}- {{name}}{{/if}}
</div>
<div class="admin-integrations-new-item-description">
{{{_ "Created_at_s_by_s" (dateFormated _createdAt) _createdBy.username}}}
</div>
</div>
<i class="icon-angle-right"></i>
</div>
</a>
{{/if}}
{{/each}}
</div>
</div>
</div>

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

@ -52,7 +52,7 @@
<div>
<input type="text" name="emoji" value="{{data.emoji}}" placeholder="{{_ 'Optional'}}" />
<div class="settings-description">{{_ "You_can_use_an_emoji_as_avatar"}}</div>
<div class="settings-description">{{_ "Example_s" ":ghost:"}}</div>
<div class="settings-description">{{{_ "Example_s" ":ghost:"}}}</div>
</div>
</div>
{{#if data.token}}

@ -20,7 +20,7 @@
<i class="icon-angle-right"></i>
</div>
</a>
<!-- <a href="{{pathFor "admin-integrations-incoming"}}">
<a href="{{pathFor "admin-integrations-outgoing"}}">
<div class="admin-integrations-new-item">
<i class="icon-logout"></i>
<div class="admin-integrations-new-item-body">
@ -33,7 +33,7 @@
</div>
<i class="icon-angle-right"></i>
</div>
</a> -->
</a>
</div>
</div>
</div>

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

@ -0,0 +1,101 @@
<template name="integrationsOutgoing">
<div class="permissions-manager">
{{#if hasPermission}}
<a href="{{pathFor "admin-integrations"}}"><i class="icon-angle-left"></i> {{_ "Back_to_integrations"}}</a><br><br>
<div class="rocket-form">
<div class="section">
<div class="section-content">
<div class="input-line double-col">
<label>{{_ "Name"}} ({{_ "optional"}})</label>
<div>
<input type="text" name="name" value="{{data.name}}" placeholder="{{_ 'Optional'}}" />
<div class="settings-description">{{_ "You_should_name_it_to_easily_manage_your_integrations"}}</div>
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Channel"}}</label>
<div>
<input type="text" name="channel" value="{{data.channel}}" placeholder="{{_ 'User_or_channel_name'}}" />
<div class="settings-description">{{_ "Optional channel to listen on"}}</div>
<div class="settings-description">{{{_ "Start_with_s_for_user_or_s_for_channel_Eg_s_or_s" "@" "#" "@john" "#general"}}}</div>
<div class="settings-description">{{{_ "Leave empty to listen <strong>any</strong> channel"}}}</div>
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Trigger_Words"}}</label>
<div>
<input type="text" name="triggerWords" value="{{join data.triggerWords ','}}" />
<div class="settings-description">{{_ "When a line starts with one of these words, post to the URL(s) below"}}</div>
<div class="settings-description">{{_ "Optional if a channel is chosen"}}</div>
<div class="settings-description">{{_ "Separate multiple words with commas"}}</div>
</div>
</div>
<div class="input-line double-col">
<label>{{_ "URLs"}}</label>
<div>
<textarea name="urls" style="height: 100px;">{{join data.urls "\n"}}</textarea>
<div class="settings-description">{{_ "Enter as many URLs as you like, one per line, please"}}</div>
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Post_as"}}</label>
<div>
<input type="text" name="username" value="{{data.username}}" />
<div class="settings-description">{{_ "Choose_the_username_that_this_integration_will_post_as"}}</div>
<div class="settings-description">{{_ "Should_exists_a_user_with_this_username"}}</div>
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Alias"}} ({{_ "optional"}})</label>
<div>
<input type="text" name="alias" value="{{data.alias}}" placeholder="{{_ 'Optional'}}" />
<div class="settings-description">{{_ "Choose_the_alias_that_will_appear_before_the_username_in_messages"}}</div>
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Avatar_URL"}} ({{_ "optional"}})</label>
<div>
<input type="url" name="avatar" value="{{data.avatar}}" placeholder="{{_ 'Optional'}}" />
<div class="settings-description">{{_ "You_can_change_a_different_avatar_too"}}</div>
<div class="settings-description">{{_ "Should_be_a_URL_of_an_image"}}</div>
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Emoji"}} ({{_ "optional"}})</label>
<div>
<input type="text" name="emoji" value="{{data.emoji}}" placeholder="{{_ 'Optional'}}" />
<div class="settings-description">{{_ "You_can_use_an_emoji_as_avatar"}}</div>
<div class="settings-description">{{{_ "Example_s" ":ghost:"}}}</div>
</div>
</div>
<div class="input-line double-col">
<label>Token</label>
<div>
<input type="text" name="token" value="{{data.token}}" />
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Responding"}}</label>
<div>
<div class="settings-description">{{{_ "If the handler wishes to post a response back into the Slack channel, the following JSON should be returned as the body of the response:"}}}</div>
<pre><code class="hljs json json-example">{{{exampleJson}}}</code></pre>
<div class="settings-description">{{{_ "Empty bodies or bodies with an empty text property will simply be ignored. Non-200 responses will be retried a reasonable number of times. A response will be posted using the alias and avatar specified above. You can override these informations as in the example above."}}}</div>
</div>
</div>
<div class="input-line message-example">
{{#nrr nrrargs 'message' example}}{{/nrr}}
</div>
</div>
</div>
<div class="submit">
{{#if data.token}}
<button class="button red delete"><i class="icon-trash"></i><span>{{_ "Delete"}}</span></button>
{{/if}}
<button class="button save"><i class="icon-send"></i><span>{{_ "Save_changes"}}</span></button>
</div>
</div>
{{else}}
{{_ "Not_authorized"}}
{{/if}}
</div>
</template>

@ -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) {

@ -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

@ -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

@ -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:

@ -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:

@ -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

@ -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

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

@ -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

@ -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

@ -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

@ -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

Loading…
Cancel
Save