* develop: (103 commits) Tokenize message on message render to prevent re processing using branding image from main app Remove unecessary logs Do not load all settings to process.env Fix preview of images in mobile re order settings code formatting Enforce data in body params Get integration name from body Set user role in integration update too Added new color variables to the theme editor embarrassing mistake make sample data into array fix livechat triggers not triggering added livechat branding Added infinite scroll to files list Allow searching for logged in user in userAutocomplete Added "Jump to" and infinite scroll to message search results Enable triggers in messages to users Rename integration api routes, add apis remove, info and sample ...pull/1898/head 0.11.0
commit
6e51f5b374
@ -1,27 +0,0 @@ |
||||
Meteor.methods |
||||
saveRoomName: (rid, name) -> |
||||
if not Meteor.userId() |
||||
throw new Meteor.Error 203, t('User_logged_out') |
||||
|
||||
room = ChatRoom.findOne rid |
||||
|
||||
if room.u._id isnt Meteor.userId() or room.t not in ['c', 'p'] |
||||
throw new Meteor.Error 403, t('Not allowed') |
||||
|
||||
if RocketChat.settings.get 'UTF8_Names_Slugify' |
||||
name = _.slugify name |
||||
|
||||
if name is room.name |
||||
return |
||||
|
||||
ChatRoom.update rid, |
||||
$set: |
||||
name: name |
||||
|
||||
ChatSubscription.update |
||||
rid: rid |
||||
, |
||||
$set: |
||||
name: name |
||||
|
||||
return name |
@ -0,0 +1,13 @@ |
||||
Meteor.startup -> |
||||
RocketChat.ChannelSettings.addOption |
||||
id: 'mail-messages' |
||||
template: 'channelSettingsMailMessages' |
||||
validation: -> |
||||
return RocketChat.authz.hasAllPermission('mail-messages') |
||||
|
||||
RocketChat.callbacks.add 'roomExit', (mainNode) -> |
||||
messagesBox = $('.messages-box') |
||||
if messagesBox.get(0)? |
||||
instance = Blaze.getView(messagesBox.get(0))?.templateInstance() |
||||
instance?.resetSelection(false) |
||||
, RocketChat.callbacks.priority.MEDIUM, 'room-exit-mail-messages' |
@ -0,0 +1,24 @@ |
||||
.flex-tab { |
||||
.mail-message { |
||||
form { |
||||
margin-top: 20px; |
||||
|
||||
.input-line.double-col { |
||||
margin-bottom: 20px; |
||||
|
||||
label { |
||||
line-height: 15px; |
||||
} |
||||
|
||||
div { |
||||
line-height: 15px; |
||||
i.octicon { |
||||
font-size: 13px; |
||||
opacity: 0.4; |
||||
vertical-align: top; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
Template.channelSettingsMailMessages.events |
||||
'click button.mail-messages': (e, t) -> |
||||
Session.set 'channelSettingsMailMessages', Session.get('openedRoom') |
||||
RocketChat.TabBar.setTemplate('mailMessagesInstructions') |
||||
view = Blaze.getView($('.messages-box')[0]) |
||||
view?.templateInstance?().resetSelection?(true) |
||||
|
||||
Template.channelSettingsMailMessages.onCreated -> |
||||
view = Blaze.getView($('.messages-box')[0]) |
||||
view?.templateInstance?().resetSelection?(false) |
@ -0,0 +1,8 @@ |
||||
<template name="channelSettingsMailMessages"> |
||||
<li> |
||||
<label>{{_ "Mail_Messages"}}</label> |
||||
<div> |
||||
<button type="button" class="button primary mail-messages">{{_ "Choose_messages"}}</button> |
||||
</div> |
||||
</li> |
||||
</template> |
@ -0,0 +1,123 @@ |
||||
Template.mailMessagesInstructions.helpers |
||||
name: -> |
||||
return Meteor.user().name |
||||
email: -> |
||||
return Meteor.user().emails?[0]?.address |
||||
roomName: -> |
||||
return ChatRoom.findOne(Session.get('openedRoom'))?.name |
||||
erroredEmails: -> |
||||
return Template.instance()?.erroredEmails.get().join(', ') |
||||
autocompleteSettings: -> |
||||
return { |
||||
limit: 10 |
||||
# inputDelay: 300 |
||||
rules: [ |
||||
{ |
||||
# @TODO maybe change this 'collection' and/or template |
||||
collection: 'CachedChannelList' |
||||
subscription: 'userAutocomplete' |
||||
field: 'username' |
||||
template: Template.userSearch |
||||
noMatchTemplate: Template.userSearchEmpty |
||||
matchAll: true |
||||
filter: |
||||
exceptions: Template.instance().selectedUsers.get() |
||||
selector: (match) -> |
||||
return { username: match } |
||||
sort: 'username' |
||||
} |
||||
] |
||||
} |
||||
selectedUsers: -> |
||||
return Template.instance().selectedUsers.get() |
||||
|
||||
Template.mailMessagesInstructions.events |
||||
'click .cancel': (e, t) -> |
||||
t.reset() |
||||
|
||||
'click .send': (e, t) -> |
||||
t.$('.error').hide() |
||||
$btn = t.$('button.send') |
||||
oldBtnValue = $btn.html() |
||||
$btn.html(TAPi18n.__('Sending')) |
||||
|
||||
selectedMessages = $('.messages-box .message.selected') |
||||
|
||||
error = false |
||||
if selectedMessages.length is 0 |
||||
t.$('.error-select').show() |
||||
error = true |
||||
|
||||
if t.$('input[name=to_emails]').val().trim() |
||||
rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/ |
||||
emails = t.$('input[name=to_emails]').val().trim().split(',') |
||||
erroredEmails = [] |
||||
for email in emails |
||||
unless rfcMailPatternWithName.test email.trim() |
||||
erroredEmails.push email.trim() |
||||
|
||||
t.erroredEmails.set erroredEmails |
||||
if erroredEmails.length > 0 |
||||
t.$('.error-invalid-emails').show() |
||||
error = true |
||||
else if not t.selectedUsers.get().length |
||||
t.$('.error-missing-to').show() |
||||
error = true |
||||
|
||||
if error |
||||
$btn.html(oldBtnValue) |
||||
else |
||||
data = |
||||
rid: Session.get('openedRoom') |
||||
to_users: t.selectedUsers.get() |
||||
to_emails: t.$('input[name=to_emails]').val().trim() |
||||
subject: t.$('input[name=subject]').val().trim() |
||||
messages: selectedMessages.map((i, message) -> return message.id).toArray() |
||||
language: localStorage.getItem('userLanguage') |
||||
|
||||
Meteor.call 'mailMessages', data, (err, result) -> |
||||
$btn.html(oldBtnValue) |
||||
if err? |
||||
return toastr.error(err.reason or err.message) |
||||
|
||||
console.log(result) |
||||
toastr.success(TAPi18n.__('Your_email_has_been_queued_for_sending')) |
||||
t.reset() |
||||
|
||||
'click .select-all': (e, t) -> |
||||
t.$('.error-select').hide() |
||||
|
||||
view = Blaze.getView($('.messages-box')[0]) |
||||
view?.templateInstance?().selectedMessages = _.pluck(ChatMessage.find({rid: Session.get('openedRoom')})?.fetch(), '_id') |
||||
$(".messages-box .message").addClass('selected') |
||||
|
||||
'autocompleteselect #to_users': (event, instance, doc) -> |
||||
instance.selectedUsers.set instance.selectedUsers.get().concat doc.username |
||||
event.currentTarget.value = '' |
||||
event.currentTarget.focus() |
||||
|
||||
'click .remove-to-user': (e, instance) -> |
||||
self = @ |
||||
|
||||
users = Template.instance().selectedUsers.get() |
||||
users = _.reject Template.instance().selectedUsers.get(), (_id) -> |
||||
return _id is self.valueOf() |
||||
|
||||
Template.instance().selectedUsers.set(users) |
||||
|
||||
$('#to_users').focus() |
||||
|
||||
Template.mailMessagesInstructions.onCreated -> |
||||
@autoCompleteCollection = new Mongo.Collection null |
||||
@selectedUsers = new ReactiveVar [] |
||||
@erroredEmails = new ReactiveVar [] |
||||
|
||||
@reset = => |
||||
@selectedUsers.set [] |
||||
RocketChat.TabBar.setTemplate('channelSettings') |
||||
view = Blaze.getView($('.messages-box')[0]) |
||||
view?.templateInstance?().resetSelection?(false) |
||||
|
||||
@autorun => |
||||
if Session.get('channelSettingsMailMessages') isnt Session.get('openedRoom') |
||||
this.reset() |
@ -0,0 +1,55 @@ |
||||
<template name="mailMessagesInstructions"> |
||||
<div class="content"> |
||||
<div class="list-view mail-message"> |
||||
<div class="status"> |
||||
<h2>{{_ "Mail_Messages"}}</h2> |
||||
</div> |
||||
<p>{{_ "Mail_Messages_Instructions"}}</p> |
||||
<form> |
||||
<fieldset> |
||||
<div class="input-line double-col"> |
||||
<label>{{_ "From"}}</label> |
||||
<div>{{name}}</div> |
||||
<div>{{email}}</div> |
||||
</div> |
||||
<div class="input-line double-col"> |
||||
<label>{{_ "To_users"}}</label> |
||||
<div> |
||||
{{> inputAutocomplete settings=autocompleteSettings id="to_users" name="to_users" class="search" autocomplete="off"}} |
||||
<ul class="selected-users"> |
||||
{{#each selectedUsers}} |
||||
<li>{{.}} <i class="icon-cancel remove-to-user"></i></li> |
||||
{{/each}} |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
<div class="input-line double-col"> |
||||
<label>{{_ "Additional_emails"}}</label> |
||||
<div> |
||||
<input type="text" name="to_emails" value="" /> |
||||
</div> |
||||
</div> |
||||
<div class="input-line double-col"> |
||||
<label>{{_ "Subject"}}</label> |
||||
<div> |
||||
<input type="text" name="subject" value="{{_ "Mail_Messages_Subject" roomName}}" /> |
||||
</div> |
||||
</div> |
||||
</fieldset> |
||||
</form> |
||||
<div class="error error-missing-to alert alert-danger" style="display: none"> |
||||
{{_ "Mail_Message_Missing_to"}} |
||||
</div> |
||||
<div class="error error-invalid-emails alert alert-danger" style="display: none"> |
||||
{{_ "Mail_Message_Invalid_emails" erroredEmails}} |
||||
</div> |
||||
<div class="error error-select alert alert-danger" style="display: none"> |
||||
{{{_ "Mail_Message_No_messages_selected_select_all"}}} |
||||
</div> |
||||
<p style="margin-top: 30px"> |
||||
<button type="button" class="button secondary cancel">{{_ "Cancel"}}</button> |
||||
<button type="button" class="button primary send">{{_ "Send"}}</button> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -0,0 +1,18 @@ |
||||
{ |
||||
"Additional_emails" : "Additional E-mails", |
||||
"Body" : "Body", |
||||
"Cancel" : "Cancel", |
||||
"Choose_messages" : "Choose messages", |
||||
"From" : "From", |
||||
"Mail_Message_Invalid_emails" : "You have provided one or more invalid e-mails: %s", |
||||
"Mail_Message_Missing_to" : "You must select one or more users or provide one or more e-mail addresses, separated by commas.", |
||||
"Mail_Message_No_messages_selected_select_all" : "You haven't selected any messages. Would you like to <a href='#' class='select-all'>select all</a> visible messages?", |
||||
"Mail_Messages" : "Mail Messages", |
||||
"Mail_Messages_Instructions" : "Choose which messages you want to send via e-mail by clicking the messages", |
||||
"Mail_Messages_Subject" : "Here's a selected portion of %s messages", |
||||
"Send" : "Send", |
||||
"Sending" : "Sending...", |
||||
"Subject" : "Subject", |
||||
"To_users" : "To Users", |
||||
"Your_email_has_been_queued_for_sending" : "Your email has been queued for sending" |
||||
} |
@ -0,0 +1,51 @@ |
||||
Package.describe({ |
||||
name: 'rocketchat:channel-settings-mail-messages', |
||||
version: '0.0.1', |
||||
summary: 'Channel Settings - Mail Messages', |
||||
git: '' |
||||
}); |
||||
|
||||
Package.onUse(function(api) { |
||||
api.versionsFrom('1.0'); |
||||
|
||||
api.use([ |
||||
'coffeescript', |
||||
'templating', |
||||
'reactive-var', |
||||
'less@2.5.0', |
||||
'rocketchat:lib@0.0.1', |
||||
'rocketchat:channel-settings', |
||||
'momentjs:moment' |
||||
]); |
||||
|
||||
api.addFiles([ |
||||
'client/lib/startup.coffee', |
||||
'client/stylesheets/mail-messages.less', |
||||
'client/views/channelSettingsMailMessages.html', |
||||
'client/views/channelSettingsMailMessages.coffee', |
||||
'client/views/mailMessagesInstructions.html', |
||||
'client/views/mailMessagesInstructions.coffee' |
||||
], 'client'); |
||||
|
||||
|
||||
api.addFiles([ |
||||
'server/lib/startup.coffee', |
||||
'server/methods/mailMessages.coffee' |
||||
], 'server'); |
||||
|
||||
// TAPi18n
|
||||
var _ = Npm.require('underscore'); |
||||
var fs = Npm.require('fs'); |
||||
tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-channel-settings-mail-messages/i18n'), function(filename) { |
||||
if (fs.statSync('packages/rocketchat-channel-settings-mail-messages/i18n/' + filename).size > 16) { |
||||
return 'i18n/' + filename; |
||||
} |
||||
})); |
||||
api.use('tap:i18n@1.6.1'); |
||||
api.imply('tap:i18n'); |
||||
api.addFiles(tapi18nFiles); |
||||
}); |
||||
|
||||
Package.onTest(function(api) { |
||||
|
||||
}); |
@ -0,0 +1,3 @@ |
||||
Meteor.startup -> |
||||
permission = { _id: 'mail-messages', roles : [ 'admin' ] } |
||||
RocketChat.models.Permissions.upsert( permission._id, { $setOnInsert : permission }) |
@ -0,0 +1,55 @@ |
||||
Meteor.methods |
||||
'mailMessages': (data) -> |
||||
if not Meteor.userId() |
||||
throw new Meteor.Error('invalid-user', "[methods] mailMessages -> Invalid user") |
||||
|
||||
check(data, Match.ObjectIncluding({ rid: String, to_users: [ String ], to_emails: String, subject: String, messages: [ String ], language: String })) |
||||
|
||||
room = Meteor.call 'canAccessRoom', data.rid, Meteor.userId() |
||||
unless room |
||||
throw new Meteor.Error('invalid-room', "[methods] mailMessages -> Invalid room") |
||||
|
||||
unless RocketChat.authz.hasPermission(Meteor.userId(), 'mail-messages') |
||||
throw new Meteor.Error 'not-authorized' |
||||
|
||||
rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/ |
||||
|
||||
emails = _.compact(data.to_emails.trim().split(',')) |
||||
missing = [] |
||||
if data.to_users.length > 0 |
||||
for username in data.to_users |
||||
user = RocketChat.models.Users.findOneByUsername(username) |
||||
if user?.emails?[0]?.address |
||||
emails.push user.emails[0].address |
||||
else |
||||
missing.push username |
||||
console.log emails |
||||
for email in emails |
||||
unless rfcMailPatternWithName.test email.trim() |
||||
throw new Meteor.Error('invalid-email', "[methods] mailMessages -> Invalid e-mail #{email}") |
||||
|
||||
user = Meteor.user() |
||||
name = user.name |
||||
email = user.emails?[0]?.address |
||||
|
||||
if data.language isnt 'en' |
||||
localeFn = Meteor.call 'loadLocale', data.language |
||||
if localeFn |
||||
Function(localeFn)() |
||||
|
||||
html = "" |
||||
RocketChat.models.Messages.findByRoomIdAndMessageIds(data.rid, data.messages, { sort: { ts: 1 } }).forEach (message) -> |
||||
dateTime = moment(message.ts).locale(data.language).format('L LT') |
||||
html += "<p style='margin-bottom: 5px'><b>#{message.u.username}</b> <span style='color: #aaa; font-size: 12px'>#{dateTime}</span><br />" + RocketChat.Message.parse(message, data.language) + "</p>" |
||||
|
||||
Meteor.defer -> |
||||
Email.send |
||||
to: emails |
||||
from: RocketChat.settings.get('From_Email') |
||||
replyTo: email |
||||
subject: data.subject |
||||
html: html |
||||
|
||||
console.log 'Sending email to ' + emails.join(', ') |
||||
|
||||
return { success: true, missing: missing } |
@ -0,0 +1,29 @@ |
||||
RocketChat.ChannelSettings = new class |
||||
options = new ReactiveVar {} |
||||
|
||||
### |
||||
# Adds an option in Channel Settings |
||||
# @config (object) |
||||
# id: option id (required) |
||||
# template (string): template name to render (required) |
||||
# validation (function): if option should be displayed |
||||
### |
||||
addOption = (config) -> |
||||
unless config?.id |
||||
throw new Meteor.Error "ChannelSettings-addOption-error", "Option id was not informed." |
||||
|
||||
Tracker.nonreactive -> |
||||
opts = options.get() |
||||
opts[config.id] = config |
||||
options.set opts |
||||
|
||||
getOptions = -> |
||||
allOptions = _.toArray options.get() |
||||
allowedOptions = _.compact _.map allOptions, (option) -> |
||||
if not option.validation? or option.validation() |
||||
return option |
||||
|
||||
return _.sortBy allowedOptions, 'order' |
||||
|
||||
addOption: addOption |
||||
getOptions: getOptions |
@ -1,23 +1,109 @@ |
||||
Template.channelSettings.helpers |
||||
canEdit: -> |
||||
return RocketChat.authz.hasAllPermission('edit-room', @rid) |
||||
editing: (field) -> |
||||
return Template.instance().editing.get() is field |
||||
notDirect: -> |
||||
return ChatRoom.findOne(@rid)?.t isnt 'd' |
||||
roomType: -> |
||||
return ChatRoom.findOne(@rid)?.t |
||||
channelSettings: -> |
||||
return RocketChat.ChannelSettings.getOptions() |
||||
roomTypeDescription: -> |
||||
roomType = ChatRoom.findOne(@rid)?.t |
||||
if roomType is 'c' |
||||
return t('Channel') |
||||
else if roomType is 'p' |
||||
return t('Private_Group') |
||||
roomName: -> |
||||
return ChatRoom.findOne(@rid)?.name |
||||
roomTopic: -> |
||||
return ChatRoom.findOne(@rid)?.topic |
||||
archived: -> |
||||
return ChatRoom.findOne(@rid)?.archived |
||||
|
||||
Template.channelSettings.events |
||||
'keydown input[type=text]': (e, t) -> |
||||
if e.keyCode is 13 |
||||
e.preventDefault() |
||||
t.saveSetting() |
||||
|
||||
'click [data-edit]': (e, t) -> |
||||
e.preventDefault() |
||||
t.editing.set($(e.currentTarget).data('edit')) |
||||
setTimeout (-> t.$('input.editing').focus().select()), 100 |
||||
|
||||
'click .cancel': (e, t) -> |
||||
e.preventDefault() |
||||
t.editing.set() |
||||
|
||||
'click .save': (e, t) -> |
||||
e.preventDefault() |
||||
t.saveSetting() |
||||
|
||||
'click .archive': (e, t) -> |
||||
e.preventDefault() |
||||
|
||||
Meteor.call 'archiveRoom', t.data.rid, true, (err, results) -> |
||||
return toastr.error err.reason if err |
||||
toastr.success TAPi18n.__ 'Room_archived' |
||||
|
||||
settings = |
||||
roomType: t.$('input[name=roomType]:checked').val() |
||||
'click .unarchive': (e, t) -> |
||||
e.preventDefault() |
||||
|
||||
Meteor.call 'saveRoomSettings', t.data.rid, settings, (err, results) -> |
||||
Meteor.call 'unarchiveRoom', t.data.rid, true, (err, results) -> |
||||
return toastr.error err.reason if err |
||||
toastr.success TAPi18n.__ 'Settings_updated' |
||||
toastr.success TAPi18n.__ 'Room_unarchived' |
||||
|
||||
Template.channelSettings.onCreated -> |
||||
@editing = new ReactiveVar |
||||
|
||||
@validateRoomType = => |
||||
type = @$('input[name=roomType]:checked').val() |
||||
if type not in ['c', 'p'] |
||||
toastr.error t('Invalid_room_type', type) |
||||
return true |
||||
|
||||
@validateRoomName = => |
||||
rid = Template.currentData()?.rid |
||||
room = ChatRoom.findOne rid |
||||
|
||||
if not RocketChat.authz.hasAllPermission('edit-room', @rid) or room.t not in ['c', 'p'] |
||||
toastr.error t('Not_allowed') |
||||
return false |
||||
|
||||
name = $('input[name=roomName]').val() |
||||
if not /^[0-9a-z-_]+$/.test name |
||||
toastr.error t('Invalid_room_name', name) |
||||
return false |
||||
|
||||
return true |
||||
|
||||
@validateRoomTopic = => |
||||
return true |
||||
|
||||
# switch room.t |
||||
# when 'c' |
||||
# FlowRouter.go 'channel', name: name |
||||
# when 'p' |
||||
# FlowRouter.go 'group', name: name |
||||
@saveSetting = => |
||||
switch @editing.get() |
||||
when 'roomName' |
||||
if @validateRoomName() |
||||
Meteor.call 'saveRoomSettings', @data?.rid, 'roomName', @$('input[name=roomName]').val(), (err, result) -> |
||||
if err |
||||
if err.error in [ 'duplicate-name', 'name-invalid' ] |
||||
return toastr.error TAPi18n.__(err.reason, err.details.channelName) |
||||
return toastr.error TAPi18n.__(err.reason) |
||||
toastr.success TAPi18n.__ 'Room_name_changed_successfully' |
||||
when 'roomTopic' |
||||
if @validateRoomTopic() |
||||
Meteor.call 'saveRoomSettings', @data?.rid, 'roomTopic', @$('input[name=roomTopic]').val(), (err, result) -> |
||||
if err |
||||
return toastr.error TAPi18n.__(err.reason) |
||||
toastr.success TAPi18n.__ 'Room_topic_changed_successfully' |
||||
when 'roomType' |
||||
if @validateRoomType() |
||||
Meteor.call 'saveRoomSettings', @data?.rid, 'roomType', @$('input[name=roomType]:checked').val(), (err, result) -> |
||||
if err |
||||
if err.error is 'invalid-room-type' |
||||
return toastr.error TAPi18n.__(err.reason, err.details.roomType) |
||||
return toastr.error TAPi18n.__(err.reason) |
||||
toastr.success TAPi18n.__ 'Room_type_changed_successfully' |
||||
@editing.set() |
||||
|
@ -1,25 +1,63 @@ |
||||
<template name="channelSettings"> |
||||
<div class="control"> |
||||
<div class="header"> |
||||
<h2>{{_ "Room_Settings"}}</h2> |
||||
</div> |
||||
</div> |
||||
<div class="channel-settings scrollable"> |
||||
<form> |
||||
<fieldset> |
||||
{{#if notDirect}} |
||||
<div class="input-line double-col"> |
||||
<label>{{_ "Room_Type"}}</label> |
||||
<div class="content"> |
||||
<div class="list-view channel-settings"> |
||||
<div class="status"> |
||||
<h2>{{_ "Room_Info"}}</h2> |
||||
</div> |
||||
<form> |
||||
<ul class="list clearfix"> |
||||
{{#if notDirect}} |
||||
<li> |
||||
<label>{{_ "Name"}}</label> |
||||
<div> |
||||
{{#if editing 'roomName'}} |
||||
<input type="text" name="roomName" value="{{roomName}}" class="editing" /> <button type="button" class="button secondary cancel">{{_ "Cancel"}}</button> <button type="button" class="button primary save">{{_ "Save"}}</button> |
||||
{{else}} |
||||
<span>{{roomName}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomName"></i>{{/if}}</span> |
||||
{{/if}} |
||||
</div> |
||||
</li> |
||||
{{/if}} |
||||
<li> |
||||
<label>{{_ "Topic"}}</label> |
||||
<div> |
||||
<label><input type="radio" name="roomType" value="c" checked="{{$eq roomType 'c'}}" /> {{_ "Channel"}}</label> |
||||
<label><input type="radio" name="roomType" value="p" checked="{{$eq roomType 'p'}}" /> {{_ "Private_Group"}}</label> |
||||
{{#if editing 'roomTopic'}} |
||||
<input type="text" name="roomTopic" value="{{roomTopic}}" class="editing" /> <button type="button" class="button secondary cancel">{{_ "Cancel"}}</button> <button type="button" class="button primary save">{{_ "Save"}}</button> |
||||
{{else}} |
||||
<span>{{roomTopic}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomTopic"></i>{{/if}}</span> |
||||
{{/if}} |
||||
</div> |
||||
</div> |
||||
{{/if}} |
||||
</fieldset> |
||||
<div class="submit"> |
||||
<button class="button save"><i class="icon-send"></i><span>{{_ "Save_changes"}}</span></button> |
||||
</div> |
||||
</form> |
||||
</li> |
||||
{{#if notDirect}} |
||||
<li> |
||||
<label>{{_ "Type"}}</label> |
||||
<div> |
||||
{{#if editing 'roomType'}} |
||||
<label><input type="radio" name="roomType" class="editing" value="c" checked="{{$eq roomType 'c'}}" /> {{_ "Channel"}}</label> |
||||
<label><input type="radio" name="roomType" value="p" checked="{{$eq roomType 'p'}}" /> {{_ "Private_Group"}}</label> |
||||
<button type="button" class="button secondary cancel">{{_ "Cancel"}}</button> |
||||
<button type="button" class="button primary save">{{_ "Save"}}</button> |
||||
{{else}} |
||||
<span>{{roomTypeDescription}}{{#if canEdit}} <i class="octicon octicon-pencil" data-edit="roomType"></i>{{/if}}</span> |
||||
{{/if}} |
||||
</div> |
||||
</li> |
||||
{{/if}} |
||||
{{#if notDirect}} |
||||
<li> |
||||
<label>{{_ "Archive_Unarchive"}}</label> |
||||
{{#if archived}} |
||||
<button class="button unarchive"><span>{{_ "Unarchive"}}</span></button> |
||||
{{else}} |
||||
<button class="button archive"><span>{{_ "Archive"}}</span></button> |
||||
{{/if}} |
||||
</li> |
||||
{{/if}} |
||||
{{#each channelSettings}} |
||||
{{> Template.dynamic template=template data=data}} |
||||
{{/each}} |
||||
</ul> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
@ -1,8 +1,15 @@ |
||||
{ |
||||
"Channel" : "Channel", |
||||
"Private_Group" : "Private Group", |
||||
"Room_Type" : "Room Type", |
||||
"Room_Settings" : "Room Settings", |
||||
"room_changed_privacy" : "Room type changed to: <em>__room_type__</em> by <em>__user_by__</em>", |
||||
"room_changed_topic" : "Room topic changed to: <em>__room_topic__</em> by <em>__user_by__</em>" |
||||
} |
||||
"Archive_Unarchive": "Archive / Unarchive", |
||||
"Cancel": "Cancel", |
||||
"Channel": "Channel", |
||||
"Private_Group": "Private Group", |
||||
"Name": "Name", |
||||
"Save": "Save", |
||||
"Topic": "Topic", |
||||
"Type": "Type", |
||||
"Room_Info": "Room Info", |
||||
"room_changed_privacy": "Room type changed to: <em>__room_type__</em> by <em>__user_by__</em>", |
||||
"room_changed_topic": "Room topic changed to: <em>__room_topic__</em> by <em>__user_by__</em>", |
||||
"Room_topic_changed_successfully": "Room topic changed successfully", |
||||
"Room_type_changed_successfully": "Room type changed successfully" |
||||
} |
||||
|
@ -1,47 +0,0 @@ |
||||
RocketChat.changeRoomType = (rid, roomType) -> |
||||
console.log '[function] RocketChat.changeRoomType'.green, rid, roomType |
||||
|
||||
unless Match.test rid, String |
||||
throw new Meteor.Error 'invalid-rid' |
||||
|
||||
if roomType not in ['c', 'p'] |
||||
throw new Meteor.Error 'invalid-room-type' |
||||
|
||||
return RocketChat.models.Rooms.setTypeById(rid, roomType) and RocketChat.models.Subscriptions.updateTypeByRoomId(rid, roomType) |
||||
|
||||
|
||||
# username = s.trim username |
||||
# if not user or not username |
||||
# return false |
||||
|
||||
# if not /^[0-9a-zA-Z-_.]+$/.test username |
||||
# return false |
||||
|
||||
# # User already has desired username, return |
||||
# if user.username is username |
||||
# return user |
||||
|
||||
# # Check username availability |
||||
# unless RocketChat.checkUsernameAvailability username |
||||
# return false |
||||
|
||||
# previousUsername = user.username |
||||
|
||||
# # Username is available; if coming from old username, update all references |
||||
# if previousUsername |
||||
# RocketChat.models.Messages.updateAllUsernamesByUserId user._id, username |
||||
|
||||
# RocketChat.models.Messages.findByMention(previousUsername).forEach (msg) -> |
||||
# updatedMsg = msg.msg.replace(new RegExp("@#{previousUsername}", "ig"), "@#{username}") |
||||
# RocketChat.models.Messages.updateUsernameAndMessageOfMentionByIdAndOldUsername msg._id, previousUsername, username, updatedMsg |
||||
|
||||
# RocketChat.models.Rooms.replaceUsername previousUsername, username |
||||
# RocketChat.models.Rooms.replaceUsernameOfUserByUserId user._id, username |
||||
|
||||
# RocketChat.models.Subscriptions.setUserUsernameByUserId user._id, username |
||||
# RocketChat.models.Subscriptions.setNameForDirectRoomsWithOldName previousUsername, username |
||||
|
||||
# # Set new username |
||||
# Meteor.users.update { _id: user._id }, { $set: { username: username } } |
||||
# user.username = username |
||||
# return user |
@ -0,0 +1,29 @@ |
||||
RocketChat.saveRoomName = (rid, name) -> |
||||
if not Meteor.userId() |
||||
throw new Meteor.Error('invalid-user', "[methods] sendMessage -> Invalid user") |
||||
|
||||
room = RocketChat.models.Rooms.findOneById rid |
||||
|
||||
if room.t not in ['c', 'p'] |
||||
throw new Meteor.Error 403, 'Not allowed' |
||||
|
||||
unless RocketChat.authz.hasPermission(Meteor.userId(), 'edit-room', rid) |
||||
#if room.u._id isnt Meteor.userId() and not hasPermission |
||||
throw new Meteor.Error 403, 'Not allowed' |
||||
|
||||
if not /^[0-9a-z-_]+$/.test name |
||||
throw new Meteor.Error 'name-invalid', 'Invalid_room_name', { channelName: name } |
||||
|
||||
name = _.slugify name |
||||
|
||||
if name is room.name |
||||
return |
||||
|
||||
# avoid duplicate names |
||||
if RocketChat.models.Rooms.findOneByName name |
||||
throw new Meteor.Error 'duplicate-name', 'Duplicate_channel_name', { channelName: name } |
||||
|
||||
RocketChat.models.Rooms.setNameById rid, name |
||||
RocketChat.models.Subscriptions.updateNameByRoomId rid, name |
||||
|
||||
return name |
@ -0,0 +1,5 @@ |
||||
RocketChat.saveRoomTopic = (rid, roomTopic) -> |
||||
unless Match.test rid, String |
||||
throw new Meteor.Error 'invalid-rid' |
||||
|
||||
return RocketChat.models.Rooms.setTopicById(rid, roomTopic) |
@ -0,0 +1,8 @@ |
||||
RocketChat.saveRoomType = (rid, roomType) -> |
||||
unless Match.test rid, String |
||||
throw new Meteor.Error 'invalid-rid' |
||||
|
||||
if roomType not in ['c', 'p'] |
||||
throw new Meteor.Error 'invalid-room-type', 'Invalid_room_type', { roomType: roomType } |
||||
|
||||
return RocketChat.models.Rooms.setTypeById(rid, roomType) and RocketChat.models.Subscriptions.updateTypeByRoomId(rid, roomType) |
@ -1,24 +1,31 @@ |
||||
Meteor.methods |
||||
saveRoomSettings: (rid, settings) -> |
||||
saveRoomSettings: (rid, setting, value) -> |
||||
unless Match.test rid, String |
||||
throw new Meteor.Error 'invalid-rid' |
||||
throw new Meteor.Error 'invalid-rid', 'Invalid room' |
||||
|
||||
unless Match.test settings, Match.ObjectIncluding { roomType: String } |
||||
throw new Meteor.Error 'invalid-settings' |
||||
if setting not in ['roomName', 'roomTopic', 'roomType'] |
||||
throw new Meteor.Error 'invalid-settings', 'Invalid settings provided' |
||||
|
||||
unless RocketChat.authz.hasPermission(Meteor.userId(), 'edit-room', rid) |
||||
throw new Meteor.Error 503, 'Not authorized' |
||||
|
||||
room = RocketChat.models.Rooms.findOneById rid |
||||
if room? |
||||
if settings.roomType isnt room.t |
||||
RocketChat.changeRoomType(rid, settings.roomType) |
||||
|
||||
if settings.roomType is 'c' |
||||
message = TAPi18n.__('Channel') |
||||
else |
||||
message = TAPi18n.__('Private_Group') |
||||
|
||||
RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_privacy', rid, message, Meteor.user() |
||||
switch setting |
||||
when 'roomName' |
||||
name = RocketChat.saveRoomName rid, value |
||||
RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser rid, name, Meteor.user() |
||||
when 'roomTopic' |
||||
if value isnt room.topic |
||||
RocketChat.saveRoomTopic(rid, value) |
||||
RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', rid, value, Meteor.user() |
||||
when 'roomType' |
||||
if value isnt room.t |
||||
RocketChat.saveRoomType(rid, value) |
||||
if value is 'c' |
||||
message = TAPi18n.__('Channel') |
||||
else |
||||
message = TAPi18n.__('Private_Group') |
||||
RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_privacy', rid, message, Meteor.user() |
||||
|
||||
return true |
||||
|
@ -1,2 +1,5 @@ |
||||
RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser = (type, roomId, message, user, extraData) -> |
||||
return @createWithTypeRoomIdMessageAndUser type, roomId, message, user, extraData |
||||
|
||||
RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser = (roomId, roomName, user, extraData) -> |
||||
return @createWithTypeRoomIdMessageAndUser 'r', roomId, roomName, user, extraData |
||||
|
@ -0,0 +1,167 @@ |
||||
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] |
||||
|
||||
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 = |
||||
username: username |
||||
channel: channel if channel isnt '' |
||||
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,100 @@ |
||||
<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"}} ({{_ "optional"}})</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 public channel</strong>"}}}</div> |
||||
</div> |
||||
</div> |
||||
<div class="input-line double-col"> |
||||
<label>{{_ "Trigger_Words"}} ({{_ "optional"}})</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">{{_ "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 ({{_ "optional"}})</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> |
@ -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: |
@ -0,0 +1,69 @@ |
||||
Meteor.methods |
||||
addOutgoingIntegration: (integration) -> |
||||
if integration.channel?.trim? and integration.channel.trim() is '' |
||||
delete integration.channel |
||||
|
||||
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? and integration.channel[0] not in ['@', '#'] |
||||
throw new Meteor.Error 'invalid_channel', '[methods] addOutgoingIntegration -> channel should start with # or @' |
||||
|
||||
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.channel? |
||||
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') and not RocketChat.authz.hasPermission(@userId, 'manage-integrations', 'bot') |
||||
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,81 @@ |
||||
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 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,110 @@ |
||||
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) -> |
||||
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 |
||||
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) -> |
||||
if not result? or result.statusCode isnt 200 |
||||
if result.statusCode is 410 |
||||
RocketChat.models.Integrations.remove _id: trigger._id |
||||
return |
||||
|
||||
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 = [] |
||||
|
||||
switch room.t |
||||
when 'd' |
||||
id = room._id.replace(message.u._id, '') |
||||
|
||||
username = _.without room.usernames, message.u.username |
||||
username = username[0] |
||||
|
||||
if triggers['@'+id]? |
||||
triggersToExecute.push trigger for key, trigger of triggers['@'+id] |
||||
|
||||
if id isnt username and triggers['@'+username]? |
||||
triggersToExecute.push trigger for key, trigger of triggers['@'+username] |
||||
|
||||
when 'c' |
||||
if triggers.__any? |
||||
triggersToExecute.push trigger for key, trigger of triggers.__any |
||||
|
||||
else |
||||
if triggers['#'+room._id]? |
||||
triggersToExecute.push trigger for key, trigger of triggers['#'+room._id] |
||||
|
||||
if room._id isnt room.name and triggers['#'+room.name]? |
||||
triggersToExecute.push trigger for key, trigger of triggers['#'+room.name] |
||||
|
||||
|
||||
for triggerToExecute in triggersToExecute |
||||
ExecuteTrigger triggerToExecute, message, room |
||||
|
||||
return message |
||||
|
||||
|
||||
RocketChat.callbacks.add 'afterSaveMessage', ExecuteTriggers, RocketChat.callbacks.priority.LOW |
@ -0,0 +1,26 @@ |
||||
RocketChat.Message = |
||||
parse: (msg, language) -> |
||||
messageType = RocketChat.MessageTypes.getType(msg) |
||||
if messageType?.render? |
||||
return messageType.render(msg) |
||||
else if messageType?.template? |
||||
# render template |
||||
else if messageType?.message? |
||||
if not language and localStorage?.getItem('userLanguage') |
||||
language = localStorage.getItem('userLanguage') |
||||
if messageType.data?(msg)? |
||||
return TAPi18n.__(messageType.message, messageType.data(msg), language) |
||||
else |
||||
return TAPi18n.__(messageType.message, {}, language) |
||||
else |
||||
if msg.u?.username is RocketChat.settings.get('Chatops_Username') |
||||
msg.html = msg.msg |
||||
return msg.html |
||||
|
||||
msg.html = msg.msg |
||||
if _.trim(msg.html) isnt '' |
||||
msg.html = _.escapeHTML msg.html |
||||
|
||||
# message = RocketChat.callbacks.run 'renderMessage', msg |
||||
msg.html = msg.html.replace /\n/gm, '<br/>' |
||||
return msg.html |
@ -1,60 +1,62 @@ |
||||
Template.room.helpers({ |
||||
title: function() { |
||||
Template.livechatWindow.helpers({ |
||||
title() { |
||||
var ref; |
||||
if (!Template.instance().subscriptionsReady()) { |
||||
return ''; |
||||
} |
||||
return ((ref = Settings.findOne('Livechat_title')) != null ? ref.value : void 0) || 'Rocket.Chat'; |
||||
}, |
||||
color: function() { |
||||
color() { |
||||
var ref; |
||||
if (!Template.instance().subscriptionsReady()) { |
||||
return 'transparent'; |
||||
} |
||||
return ((ref = Settings.findOne('Livechat_title_color')) != null ? ref.value : void 0) || '#C1272D'; |
||||
}, |
||||
popoutActive: function() { |
||||
popoutActive() { |
||||
return FlowRouter.getQueryParam('mode') === 'popout'; |
||||
}, |
||||
showMessages: function() { |
||||
return Session.get('triggered') || Meteor.userId(); |
||||
showRegisterForm() { |
||||
if (Session.get('triggered') || Meteor.userId()) { |
||||
return false; |
||||
} |
||||
var form = Settings.findOne('Livechat_registration_form'); |
||||
return form.value; |
||||
}, |
||||
livechatStartedEnabled: function() { |
||||
livechatStartedEnabled() { |
||||
return Template.instance().startedEnabled.get() !== null; |
||||
}, |
||||
livechatEnabled: function() { |
||||
livechatEnabled() { |
||||
return Template.instance().startedEnabled.get(); |
||||
} |
||||
}); |
||||
|
||||
Template.room.events({ |
||||
'click .title': function() { |
||||
Template.livechatWindow.events({ |
||||
'click .title'() { |
||||
parentCall('toggleWindow'); |
||||
}, |
||||
'click .popout': function(event) { |
||||
'click .popout'(event) { |
||||
event.stopPropagation(); |
||||
parentCall('openPopout'); |
||||
} |
||||
}); |
||||
|
||||
Template.room.onCreated(function() { |
||||
self = this; |
||||
|
||||
self.startedEnabled = new ReactiveVar(null); |
||||
Template.livechatWindow.onCreated(function() { |
||||
this.startedEnabled = new ReactiveVar(null); |
||||
|
||||
self.subscribe('settings', ['Livechat_title', 'Livechat_title_color', 'Livechat_enabled']); |
||||
this.subscribe('settings', ['Livechat_title', 'Livechat_title_color', 'Livechat_enabled', 'Livechat_registration_form']); |
||||
|
||||
var initialCheck = true; |
||||
|
||||
self.autorun(function() { |
||||
if (self.subscriptionsReady()) { |
||||
this.autorun(() => { |
||||
if (this.subscriptionsReady()) { |
||||
var enabled = Settings.findOne('Livechat_enabled'); |
||||
if (enabled !== undefined) { |
||||
if (!enabled.value && initialCheck) { |
||||
parentCall('removeWidget'); |
||||
} |
||||
initialCheck = false; |
||||
self.startedEnabled.set(enabled.value); |
||||
this.startedEnabled.set(enabled.value); |
||||
} |
||||
} |
||||
}); |
@ -1,50 +0,0 @@ |
||||
Template.register.helpers |
||||
error: -> |
||||
return Template.instance().error.get() |
||||
|
||||
title: -> |
||||
return '' unless Template.instance().subscriptionsReady() |
||||
return Settings.findOne('Livechat_title')?.value or 'Rocket.Chat' |
||||
|
||||
color: -> |
||||
return 'transparent' unless Template.instance().subscriptionsReady() |
||||
return Settings.findOne('Livechat_title_color')?.value or '#C1272D' |
||||
|
||||
welcomeMessage: -> |
||||
return "" |
||||
|
||||
Template.register.events |
||||
'submit #livechat-registration': (e, instance) -> |
||||
e.preventDefault() |
||||
|
||||
$name = instance.$('input[name=name]') |
||||
$email = instance.$('input[name=email]') |
||||
|
||||
unless $name.val().trim() and $email.val().trim() |
||||
return instance.showError TAPi18n.__('Please_fill_name_and_email') |
||||
else |
||||
Meteor.call 'registerGuest', visitor.getToken(), $name.val(), $email.val(), (error, result) -> |
||||
if error? |
||||
return instance.showError error.reason |
||||
|
||||
Meteor.loginWithToken result.token, (error) -> |
||||
if error |
||||
return instance.showError error.reason |
||||
|
||||
'click .error': (e, instance) -> |
||||
instance.hideError() |
||||
|
||||
Template.register.onCreated -> |
||||
@subscribe 'settings', ['Livechat_title', 'Livechat_title_color'] |
||||
@error = new ReactiveVar |
||||
|
||||
@showError = (msg) => |
||||
$('.error').addClass('show') |
||||
@error.set msg |
||||
return |
||||
|
||||
@hideError = => |
||||
$('.error').removeClass('show') |
||||
@error.set() |
||||
return |
||||
|
@ -0,0 +1,68 @@ |
||||
Template.register.helpers({ |
||||
error() { |
||||
return Template.instance().error.get(); |
||||
}, |
||||
welcomeMessage() { |
||||
return ""; |
||||
}, |
||||
hasDepartments() { |
||||
return Department.find().count() > 1; |
||||
}, |
||||
departments() { |
||||
return Department.find(); |
||||
} |
||||
}); |
||||
|
||||
Template.register.events({ |
||||
'submit #livechat-registration' (e, instance) { |
||||
var $email, $name; |
||||
e.preventDefault(); |
||||
$name = instance.$('input[name=name]'); |
||||
$email = instance.$('input[name=email]'); |
||||
if (!($name.val().trim() && $email.val().trim())) { |
||||
return instance.showError(TAPi18n.__('Please_fill_name_and_email')); |
||||
} else { |
||||
var departmentId = instance.$('select[name=department]').val(); |
||||
if (!departmentId) { |
||||
var department = Department.findOne(); |
||||
if (department) { |
||||
departmentId = department._id; |
||||
} |
||||
} |
||||
|
||||
var guest = { |
||||
token: visitor.getToken(), |
||||
name: $name.val(), |
||||
email: $email.val(), |
||||
department: departmentId |
||||
}; |
||||
Meteor.call('livechat:registerGuest', guest, function(error, result) { |
||||
if (error != null) { |
||||
return instance.showError(error.reason); |
||||
} |
||||
Meteor.loginWithToken(result.token, function(error) { |
||||
if (error) { |
||||
return instance.showError(error.reason); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
}, |
||||
'click .error' (e, instance) { |
||||
return instance.hideError(); |
||||
} |
||||
}); |
||||
|
||||
Template.register.onCreated(function() { |
||||
this.subscribe('livechat:availableDepartments'); |
||||
|
||||
this.error = new ReactiveVar; |
||||
this.showError = (msg) => { |
||||
$('.error').addClass('show'); |
||||
this.error.set(msg); |
||||
}; |
||||
this.hideError = () => { |
||||
$('.error').removeClass('show'); |
||||
this.error.set(); |
||||
}; |
||||
}); |
@ -0,0 +1 @@ |
||||
this.AgentUsers = new Mongo.Collection('agentUsers'); |
@ -0,0 +1 @@ |
||||
this.LivechatDepartmentAgents = new Mongo.Collection('rocketchat_livechat_department_agents'); |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue