Livechat SMS support (#2939)

* Add package to send SMS

* Add support to livechat send and receive messages via SMS

* Fix livechat typing indicator and first message after trigger

* rename migration number.
pull/2940/head
Diego Sampaio 10 years ago committed by Gabriel Engel
parent d315b7b24c
commit 1fccfc229a
  1. 1
      .meteor/versions
  2. 5
      packages/rocketchat-lib/i18n/en.i18n.json
  3. 10
      packages/rocketchat-livechat/app/client/lib/_visitor.coffee
  4. 9
      packages/rocketchat-livechat/app/client/lib/chatMessages.coffee
  5. 56
      packages/rocketchat-livechat/app/client/lib/fromApp/Notifications.coffee
  6. 19
      packages/rocketchat-livechat/app/client/lib/msgTyping.coffee
  7. 4
      packages/rocketchat-livechat/app/client/startup/room.coffee
  8. 2
      packages/rocketchat-livechat/app/client/views/messages.js
  9. 3
      packages/rocketchat-livechat/config.js
  10. 9
      packages/rocketchat-livechat/package.js
  11. 54
      packages/rocketchat-livechat/server/api.js
  12. 121
      packages/rocketchat-livechat/server/lib/Livechat.js
  13. 9
      packages/rocketchat-livechat/server/lib/getNextAgent.js
  14. 72
      packages/rocketchat-livechat/server/methods/registerGuest.js
  15. 52
      packages/rocketchat-livechat/server/methods/sendMessageLivechat.js
  16. 34
      packages/rocketchat-livechat/server/models/Users.js
  17. 37
      packages/rocketchat-livechat/server/sendMessageBySMS.js
  18. 1
      packages/rocketchat-sms/.npm/package/.gitignore
  19. 7
      packages/rocketchat-sms/.npm/package/README
  20. 312
      packages/rocketchat-sms/.npm/package/npm-shrinkwrap.json
  21. 0
      packages/rocketchat-sms/README.md
  22. 23
      packages/rocketchat-sms/SMS.js
  23. 21
      packages/rocketchat-sms/package.js
  24. 37
      packages/rocketchat-sms/services/twilio.js
  25. 43
      packages/rocketchat-sms/settings.js
  26. 4
      server/methods/canAccessRoom.coffee
  27. 14
      server/startup/migrations/v045.js

@ -178,6 +178,7 @@ rocketchat:slashcommands-join@0.0.1
rocketchat:slashcommands-kick@0.0.1
rocketchat:slashcommands-leave@0.0.1
rocketchat:slashcommands-mute@0.0.1
rocketchat:sms@0.0.1
rocketchat:spotify@0.0.1
rocketchat:statistics@0.0.1
rocketchat:streamer@0.3.2

@ -9,6 +9,7 @@
"Access_online_demo" : "Access the online demo",
"Access_Online_Demo" : "Access the Online Demo",
"Access_Token_URL" : "Access Token URL",
"Account_SID" : "Account SID",
"Accounts" : "Accounts",
"Accounts_AllowDeleteOwnAccount" : "Allow users to delete own account",
"Accounts_AllowedDomainsList" : "Allowed Domains List",
@ -143,6 +144,7 @@
"are_typing" : "are typing",
"Are_you_sure" : "Are you sure?",
"Are_you_sure_you_want_to_delete_your_account" : "Are you sure you want to delete your account?",
"Auth_Token" : "Auth Token",
"Authorization_URL" : "Authorization URL",
"Authorize" : "Authorize",
"Auto_Load_Images" : "Auto Load Images",
@ -339,6 +341,7 @@
"From_Email" : "From Email",
"From_email_is_required" : "From email is required",
"From_email_warning" : "<b>Warning</b>: The field <b>From</b> is subject to your mail server settings.",
"From_Number" : "From Number",
"General" : "General",
"Get_to_know_the_team" : "Get to know the Rocket.Team",
"github_no_public_email" : "You don't have any email as public email in your GitHub account",
@ -823,6 +826,7 @@
"Send_Message" : "Send Message",
"Send_your_JSON_payloads_to_this_URL" : "Send your JSON payloads to this URL.",
"Sending" : "Sending...",
"Service" : "Service",
"Set_as_moderator" : "Set as moderator",
"Set_as_owner" : "Set as owner",
"Settings" : "Settings",
@ -843,6 +847,7 @@
"Site_Url_Description" : "Example: https://chat.domain.com/",
"Skip" : "Skip",
"Smileys_and_People" : "Smileys & People",
"SMS_Enabled" : "SMS Enabled",
"SMTP" : "SMTP",
"SMTP_Host" : "SMTP Host",
"SMTP_Password" : "SMTP Password",

@ -1,6 +1,7 @@
@visitor = new class
token = new ReactiveVar null
room = new ReactiveVar null
roomToSubscribe = new ReactiveVar null
register = ->
if not localStorage.getItem 'visitorToken'
@ -22,7 +23,16 @@
return roomId
getRoomToSubscribe = ->
return roomToSubscribe.get()
setRoomToSubscribe = (rid) ->
room.set(rid)
return roomToSubscribe.set(rid)
register: register
getToken: getToken
setRoom: setRoom
getRoom: getRoom
setRoomToSubscribe: setRoomToSubscribe
getRoomToSubscribe: getRoomToSubscribe

@ -74,7 +74,7 @@ class @ChatMessages
input.value = ''
rid ?= visitor.getRoom(true)
sendMessage = ->
sendMessage = (callback) ->
msgObject = { _id: Random.id(), rid: rid, msg: msg, token: visitor.getToken() }
MsgTyping.stop(rid)
#Check if message starts with /command
@ -91,6 +91,8 @@ class @ChatMessages
if error
ChatMessage.update msgObject._id, { $set: { error: true } }
showError error.reason
else
callback?(result)
if not Meteor.userId()
Meteor.call 'livechat:registerGuest', { token: visitor.getToken() }, (error, result) ->
@ -101,7 +103,10 @@ class @ChatMessages
if error
return showError error.reason
sendMessage()
sendMessage (message) ->
ChatMessage.update message._id, _.omit(message, '_id')
if message.rid?
visitor.setRoomToSubscribe(message.rid)
else
sendMessage()

@ -0,0 +1,56 @@
@Notifications = new class
constructor: ->
@debug = false
@streamAll = new Meteor.Streamer 'notify-all'
@streamRoom = new Meteor.Streamer 'notify-room'
@streamUser = new Meteor.Streamer 'notify-user'
if @debug is true
@onAll -> console.log "RocketChat.Notifications: onAll", arguments
@onUser -> console.log "RocketChat.Notifications: onAll", arguments
notifyRoom: (room, eventName, args...) ->
console.log "RocketChat.Notifications: notifyRoom", arguments if @debug is true
args.unshift "#{room}/#{eventName}"
@streamRoom.emit.apply @streamRoom, args
notifyUser: (userId, eventName, args...) ->
console.log "RocketChat.Notifications: notifyUser", arguments if @debug is true
args.unshift "#{userId}/#{eventName}"
@streamUser.emit.apply @streamUser, args
notifyUsersOfRoom: (room, eventName, args...) ->
console.log "RocketChat.Notifications: notifyUsersOfRoom", arguments if @debug is true
onlineUsers = RoomManager.onlineUsers.get()
room = ChatRoom.findOne(room)
for username in room?.usernames or []
if onlineUsers[username]?
argsToSend = ["#{onlineUsers[username]._id}/#{eventName}"].concat args
@streamUser.emit.apply @streamUser, argsToSend
onAll: (eventName, callback) ->
@streamAll.on eventName, callback
onRoom: (room, eventName, callback) ->
if @debug is true
@streamRoom.on room, -> console.log "RocketChat.Notifications: onRoom #{room}", arguments
@streamRoom.on "#{room}/#{eventName}", callback
onUser: (eventName, callback) ->
@streamUser.on "#{Meteor.userId()}/#{eventName}", callback
unAll: (callback) ->
@streamAll.removeListener 'notify', callback
unRoom: (room, eventName, callback) ->
@streamRoom.removeListener "#{room}/#{eventName}", callback
unUser: (callback) ->
@streamUser.removeListener Meteor.userId(), callback

@ -1,5 +1,4 @@
@MsgTyping = do ->
stream = new Meteor.Streamer 'typing'
timeout = 15000
timeouts = {}
renew = true
@ -11,20 +10,20 @@
addStream = (room) ->
if _.isEmpty usersTyping[room]?.users
usersTyping[room] = { users: {} }
stream.on room, (typing) ->
unless typing?.username is Meteor.user()?.username
if typing.start
Notifications.onRoom room, 'typing', (username, typing) ->
unless username is Meteor.user()?.username
if typing is true
users = usersTyping[room].users
users[typing.username] = Meteor.setTimeout ->
delete users[typing.username]
users[username] = Meteor.setTimeout ->
delete users[username]
usersTyping[room].users = users
dep.changed()
, timeout
usersTyping[room].users = users
dep.changed()
else if typing.stop
else
users = usersTyping[room].users
delete users[typing.username]
delete users[username]
usersTyping[room].users = users
dep.changed()
@ -41,7 +40,7 @@
renew = false
selfTyping.set true
stream.emit 'typing', { room: room, username: Meteor.user()?.username, start: true }
Notifications.notifyRoom room, 'typing', Meteor.user()?.username, true
clearTimeout timeouts[room]
timeouts[room] = Meteor.setTimeout ->
stop(room)
@ -53,7 +52,7 @@
if timeouts?[room]?
clearTimeout(timeouts[room])
timeouts[room] = null
stream.emit 'typing', { room: room, username: Meteor.user()?.username, stop: true }
Notifications.notifyRoom room, 'typing', Meteor.user()?.username, false
get = (room) ->
dep.depend()

@ -1,7 +1,7 @@
msgStream = new Meteor.Streamer 'room-messages'
Tracker.autorun ->
if visitor.getRoom()?
msgStream.on visitor.getRoom(), (msg) ->
if visitor.getRoomToSubscribe()?
msgStream.on visitor.getRoomToSubscribe(), (msg) ->
if msg.t is 'command'
if msg.msg is 'survey'
unless $('body #survey').length

@ -46,7 +46,7 @@ Template.messages.onCreated(function() {
var room;
room = ChatRoom.findOne();
if (room != null) {
visitor.setRoom(room._id);
visitor.setRoomToSubscribe(room._id);
RoomHistoryManager.getMoreIfIsEmpty(room._id);
}
});

@ -2,6 +2,7 @@ Meteor.startup(function() {
RocketChat.settings.addGroup('Livechat');
RocketChat.settings.add('Livechat_title' , 'Rocket.Chat', { type: 'string', group: 'Livechat', public: true });
RocketChat.settings.add('Livechat_title_color' , '#C1272D', { type: 'string', group: 'Livechat', public: true });
RocketChat.settings.add('Livechat_enabled' , false, { type: 'boolean', group: 'Livechat', public: true });
RocketChat.settings.add('Livechat_enabled' , false, { type: 'boolean', group: 'Livechat', public: true });
RocketChat.settings.add('Livechat_registration_form' , true, { type: 'boolean', group: 'Livechat', public: true, i18nLabel: 'Show_preregistration_form' });
RocketChat.settings.add('Livechat_guest_count' , 1, { type: 'int', group: 'Livechat' });
});

@ -5,7 +5,7 @@ Package.describe({
});
Package.registerBuildPlugin({
name: 'builLivechat',
name: 'Livechat',
use: [],
sources: [
'plugin/build-livechat.js'
@ -26,6 +26,7 @@ Package.onUse(function(api) {
api.use('kadira:flow-router', 'client');
api.use('templating', 'client');
api.use('mongo');
api.use('rocketchat:sms');
api.use('less@2.5.1');
api.addFiles('livechat.js', 'server');
@ -113,7 +114,8 @@ Package.onUse(function(api) {
api.addFiles('server/models/LivechatTrigger.js', 'server');
// server lib
api.addFiles('server/lib/getNextAgent.js', 'server');
api.addFiles('server/lib/Livechat.js', 'server');
api.addFiles('server/sendMessageBySMS.js', 'server');
// publications
api.addFiles('server/publications/availableDepartments.js', 'server');
@ -127,6 +129,9 @@ Package.onUse(function(api) {
api.addFiles('server/publications/visitorPageVisited.js', 'server');
api.addFiles('server/publications/visitorRoom.js', 'server');
// api
api.addFiles('server/api.js', 'server');
// livechat app
api.addAssets('assets/demo.html', 'client');
api.addAssets('assets/rocket-livechat.js', 'client');

@ -0,0 +1,54 @@
/* globals Restivus */
const Api = new Restivus({
apiPath: 'livechat-api/',
useDefaultAuth: true,
prettyJson: true
});
Api.addRoute('sms-incoming/:service', {
post() {
const SMSService = RocketChat.SMS.getService(this.urlParams.service);
const sms = SMSService.parse(this.bodyParams);
var visitor = RocketChat.models.Users.findOneVisitorByPhone(sms.from);
let sendMessage = {
message: {
_id: Random.id()
}
};
if (visitor) {
const rooms = RocketChat.models.Rooms.findByVisitorToken(visitor.profile.token).fetch();
if (rooms && rooms.length > 0) {
sendMessage.message.rid = rooms[0]._id;
} else {
sendMessage.message.rid = Random.id();
}
sendMessage.message.token = visitor.profile.token;
} else {
sendMessage.message.rid = Random.id();
sendMessage.message.token = Random.id();
let userId = RocketChat.Livechat.registerGuest({
token: sendMessage.message.token,
phone: {
number: sms.from
}
});
visitor = RocketChat.models.Users.findOneById(userId);
sendMessage.roomInfo = {
sms: true
};
}
sendMessage.message.msg = sms.body;
sendMessage.guest = visitor;
return RocketChat.Livechat.sendMessage(sendMessage);
}
});

@ -0,0 +1,121 @@
RocketChat.Livechat = {
getNextAgent(department) {
if (department) {
return RocketChat.models.LivechatDepartmentAgents.getNextAgentForDepartment(department);
} else {
return RocketChat.models.Users.getNextAgent();
}
},
sendMessage({ guest, message, roomInfo }) {
var agent, room;
room = RocketChat.models.Rooms.findOneById(message.rid);
if (room == null) {
// if no department selected verify if there is only one active and use it
if (!guest.department) {
var departments = RocketChat.models.LivechatDepartment.findEnabledWithAgents();
if (departments.count() === 1) {
guest.department = departments.fetch()[0]._id;
}
}
agent = RocketChat.Livechat.getNextAgent(guest.department);
if (!agent) {
throw new Meteor.Error('no-agent-online', 'Sorry, no online agents');
}
let roomData = _.extend({
_id: message.rid,
name: guest.username,
msgs: 1,
lm: new Date(),
usernames: [agent.username, guest.username],
t: 'l',
ts: new Date(),
v: {
token: message.token
}
}, roomInfo);
let subscriptionData = {
rid: message.rid,
name: guest.username,
alert: true,
open: true,
unread: 1,
answered: false,
u: {
_id: agent.agentId,
username: agent.username
},
t: 'l',
desktopNotifications: 'all',
mobilePushNotifications: 'all',
emailNotifications: 'all'
};
RocketChat.models.Rooms.insert(roomData);
RocketChat.models.Subscriptions.insert(subscriptionData);
}
room = Meteor.call('canAccessRoom', message.rid, guest._id);
if (!room) {
throw new Meteor.Error('cannot-acess-room');
}
return RocketChat.sendMessage(guest, message, room);
},
registerGuest({ token, name, email, department, phone, loginToken } = {}) {
check(token, String);
const user = RocketChat.models.Users.getVisitorByToken(token, { fields: { _id: 1 } });
if (user) {
throw new Meteor.Error('token-already-exists', 'Token already exists');
}
const username = RocketChat.models.Users.getNextVisitorUsername();
var userData = {
username: username,
globalRoles: ['livechat-guest'],
department: department,
type: 'visitor'
};
if (this.connection) {
userData.userAgent = this.connection.httpHeaders['user-agent'];
userData.ip = this.connection.httpHeaders['x-real-ip'] || this.connection.clientAddress;
userData.host = this.connection.httpHeaders.host;
}
const userId = Accounts.insertUserDoc({}, userData);
let updateUser = {
name: name || username,
profile: {
guest: true,
token: token
}
};
if (phone) {
updateUser.profile.phones = [ phone ];
}
if (email && email.trim() !== '') {
updateUser.emails = [{ address: email }];
}
if (loginToken) {
updateUser.services = {
resume: {
loginTokens: [ loginToken ]
}
};
}
Meteor.users.update(userId, {
$set: updateUser
});
return userId;
}
};

@ -1,9 +0,0 @@
/* exported getNextAgent */
this.getNextAgent = function(department) {
if (department) {
return RocketChat.models.LivechatDepartmentAgents.getNextAgentForDepartment(department);
} else {
return RocketChat.models.Users.getNextAgent();
}
};

@ -1,74 +1,14 @@
Meteor.methods({
'livechat:registerGuest': function({ token, name, email, department } = {}) {
var qt, user, userData, userExists, userId, inc = 0;
check(token, String);
user = Meteor.users.findOne({
'profile.token': token
}, {
fields: {
_id: 1
}
});
if (user != null) {
throw new Meteor.Error('token-already-exists', 'Token already exists');
}
while (true) {
qt = Meteor.users.find({
'profile.guest': true
}).count() + 1;
user = 'guest-' + (qt + inc++);
userExists = Meteor.users.findOne({
'username': user
}, {
fields: {
_id: 1
}
});
if (!userExists) {
break;
}
}
userData = {
username: user,
globalRoles: ['livechat-guest'],
department: department,
type: 'visitor'
};
userData.userAgent = this.connection.httpHeaders['user-agent'];
userData.ip = this.connection.httpHeaders['x-real-ip'] || this.connection.clientAddress;
userData.host = this.connection.httpHeaders.host;
userId = Accounts.insertUserDoc({}, userData);
const updateUser = {
name: name || user,
'profile.guest': true,
'profile.token': token
};
if (email && email.trim() !== '') {
updateUser.emails = [{ address: email }];
}
var stampedToken = Accounts._generateStampedLoginToken();
var hashStampedToken = Accounts._hashStampedToken(stampedToken);
updateUser.services = {
resume: {
loginTokens: [ hashStampedToken ]
}
};
Meteor.users.update(userId, {
$set: updateUser
let userId = RocketChat.Livechat.registerGuest({
token: token,
name: name,
email: email,
department: department,
loginToken: hashStampedToken
});
// update visited page history to not expire

@ -1,6 +1,6 @@
Meteor.methods({
sendMessageLivechat: function(message) {
var guest, agent, room;
var guest;
check(message.rid, String);
check(message.token, String);
@ -12,54 +12,6 @@ Meteor.methods({
}
});
room = RocketChat.models.Rooms.findOneById(message.rid);
if (room == null) {
// if no department selected verify if there is only one active and use it
if (!guest.department) {
var departments = RocketChat.models.LivechatDepartment.findEnabledWithAgents();
if (departments.count() === 1) {
guest.department = departments.fetch()[0]._id;
}
}
agent = getNextAgent(guest.department);
if (!agent) {
throw new Meteor.Error('no-agent-online', 'Sorry, no online agents');
}
RocketChat.models.Rooms.insert({
_id: message.rid,
name: guest.username,
msgs: 1,
lm: new Date(),
usernames: [agent.username, guest.username],
t: 'l',
ts: new Date(),
v: {
token: message.token
}
});
RocketChat.models.Subscriptions.insert({
rid: message.rid,
name: guest.username,
alert: true,
open: true,
unread: 1,
answered: false,
u: {
_id: agent.agentId,
username: agent.username
},
t: 'l',
desktopNotifications: 'all',
mobilePushNotifications: 'all',
emailNotifications: 'all'
});
}
room = Meteor.call('canAccessRoom', message.rid, guest._id);
if (!room) {
throw new Meteor.Error('cannot-acess-room');
}
return RocketChat.sendMessage(guest, message, room);
return RocketChat.Livechat.sendMessage({ guest: guest, message: message });
}
});

@ -137,3 +137,37 @@ RocketChat.models.Users.updateLivechatDataByToken = function(token, key, value)
return this.upsert(query, update);
};
/**
* Find a visitor by their phone number
* @return {object} User from db
*/
RocketChat.models.Users.findOneVisitorByPhone = function(phone) {
const query = {
'profile.phones.number': phone
};
return this.findOne(query);
};
/**
* Get the next visitor name
* @return {string} The next visitor name
*/
RocketChat.models.Users.getNextVisitorUsername = function() {
const settingsRaw = RocketChat.models.Settings.model.rawCollection();
const findAndModify = Meteor.wrapAsync(settingsRaw.findAndModify, settingsRaw);
const query = {
_id: 'Livechat_guest_count'
};
const update = {
$inc: {
value: 1
}
};
const livechatCount = findAndModify(query, null, update);
return 'guest-' + (livechatCount.value + 1);
};

@ -0,0 +1,37 @@
RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
// skips this callback if the message was edited
if (message.editedAt) {
return message;
}
if (!RocketChat.SMS.enabled) {
return message;
}
// only send the sms by SMS if it is a livechat room with SMS set to true
if (typeof room.t === 'undefined' || room.t !== 'l' || !room.sms || !room.v || !room.v.token) {
return message;
}
// if the message has a token, it was sent from the visitor, so ignore it
if (message.token) {
return message;
}
const SMSService = RocketChat.SMS.getService(RocketChat.settings.get('SMS_Service'));
if (!SMSService) {
return message;
}
const visitor = RocketChat.models.Users.getVisitorByToken(room.v.token);
if (!visitor || !visitor.profile || !visitor.profile.phones || visitor.profile.phones.length === 0) {
return message;
}
SMSService.send(visitor.profile.phones[0].number, message.msg);
return message;
}, RocketChat.callbacks.priority.LOW);

@ -0,0 +1,7 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

@ -0,0 +1,312 @@
{
"dependencies": {
"twilio": {
"version": "2.9.1",
"dependencies": {
"request": {
"version": "2.55.0",
"dependencies": {
"bl": {
"version": "0.9.5",
"dependencies": {
"readable-stream": {
"version": "1.0.34",
"dependencies": {
"core-util-is": {
"version": "1.0.2"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.31"
},
"inherits": {
"version": "2.0.1"
}
}
}
}
},
"caseless": {
"version": "0.9.0"
},
"forever-agent": {
"version": "0.6.1"
},
"form-data": {
"version": "0.2.0",
"dependencies": {
"async": {
"version": "0.9.2"
}
}
},
"json-stringify-safe": {
"version": "5.0.1"
},
"mime-types": {
"version": "2.0.14",
"dependencies": {
"mime-db": {
"version": "1.12.0"
}
}
},
"node-uuid": {
"version": "1.4.7"
},
"qs": {
"version": "2.4.2"
},
"tunnel-agent": {
"version": "0.4.2"
},
"tough-cookie": {
"version": "2.2.2"
},
"http-signature": {
"version": "0.10.1",
"dependencies": {
"assert-plus": {
"version": "0.1.5"
},
"asn1": {
"version": "0.1.11"
},
"ctype": {
"version": "0.5.3"
}
}
},
"oauth-sign": {
"version": "0.6.0"
},
"hawk": {
"version": "2.3.1",
"dependencies": {
"hoek": {
"version": "2.16.3"
},
"boom": {
"version": "2.10.1"
},
"cryptiles": {
"version": "2.0.5"
},
"sntp": {
"version": "1.0.9"
}
}
},
"aws-sign2": {
"version": "0.5.0"
},
"stringstream": {
"version": "0.0.5"
},
"combined-stream": {
"version": "0.0.7",
"dependencies": {
"delayed-stream": {
"version": "0.0.5"
}
}
},
"isstream": {
"version": "0.1.2"
},
"har-validator": {
"version": "1.8.0",
"dependencies": {
"bluebird": {
"version": "2.10.2"
},
"chalk": {
"version": "1.1.3",
"dependencies": {
"ansi-styles": {
"version": "2.2.1"
},
"escape-string-regexp": {
"version": "1.0.5"
},
"has-ansi": {
"version": "2.0.0",
"dependencies": {
"ansi-regex": {
"version": "2.0.0"
}
}
},
"strip-ansi": {
"version": "3.0.1",
"dependencies": {
"ansi-regex": {
"version": "2.0.0"
}
}
},
"supports-color": {
"version": "2.0.0"
}
}
},
"commander": {
"version": "2.9.0",
"dependencies": {
"graceful-readlink": {
"version": "1.0.1"
}
}
},
"is-my-json-valid": {
"version": "2.13.1",
"dependencies": {
"generate-function": {
"version": "2.0.0"
},
"generate-object-property": {
"version": "1.2.0",
"dependencies": {
"is-property": {
"version": "1.0.2"
}
}
},
"jsonpointer": {
"version": "2.0.0"
},
"xtend": {
"version": "4.0.1"
}
}
}
}
}
}
},
"underscore": {
"version": "1.8.3"
},
"jsonwebtoken": {
"version": "5.4.1",
"dependencies": {
"jws": {
"version": "3.1.3",
"dependencies": {
"base64url": {
"version": "1.0.6",
"dependencies": {
"concat-stream": {
"version": "1.4.10",
"dependencies": {
"inherits": {
"version": "2.0.1"
},
"typedarray": {
"version": "0.0.6"
},
"readable-stream": {
"version": "1.1.14",
"dependencies": {
"core-util-is": {
"version": "1.0.2"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.31"
}
}
}
}
},
"meow": {
"version": "2.0.0",
"dependencies": {
"camelcase-keys": {
"version": "1.0.0",
"dependencies": {
"camelcase": {
"version": "1.2.1"
},
"map-obj": {
"version": "1.0.1"
}
}
},
"indent-string": {
"version": "1.2.2",
"dependencies": {
"get-stdin": {
"version": "4.0.1"
},
"repeating": {
"version": "1.1.3",
"dependencies": {
"is-finite": {
"version": "1.0.1",
"dependencies": {
"number-is-nan": {
"version": "1.0.0"
}
}
}
}
}
}
},
"minimist": {
"version": "1.2.0"
},
"object-assign": {
"version": "1.0.0"
}
}
}
}
},
"jwa": {
"version": "1.1.3",
"dependencies": {
"buffer-equal-constant-time": {
"version": "1.0.1"
},
"ecdsa-sig-formatter": {
"version": "1.0.5",
"dependencies": {
"base64-url": {
"version": "1.2.2"
}
}
}
}
}
}
},
"ms": {
"version": "0.7.1"
}
}
},
"jwt-simple": {
"version": "0.1.0"
},
"q": {
"version": "0.9.7"
},
"scmp": {
"version": "0.0.3"
},
"deprecate": {
"version": "0.1.0"
},
"string.prototype.startswith": {
"version": "0.2.0"
}
}
}
}
}

@ -0,0 +1,23 @@
/* globals RocketChat */
RocketChat.SMS = {
enabled: false,
services: {},
accountSid: null,
authToken: null,
fromNumber: null,
registerService(name, service) {
this.services[name] = service;
},
getService(name) {
if (!this.services[name]) {
throw new Meteor.Error('error-sms-service-not-configured');
}
return new this.services[name](this.accountSid, this.authToken, this.fromNumber);
}
};
RocketChat.settings.get('SMS_Enabled', function(key, value) {
RocketChat.SMS.enabled = value;
});

@ -0,0 +1,21 @@
Package.describe({
name: 'rocketchat:sms',
version: '0.0.1',
summary: '',
git: '',
documentation: 'README.md'
});
Package.onUse(function(api) {
api.versionsFrom('1.2.1');
api.use('ecmascript');
api.use('rocketchat:lib');
api.addFiles('settings.js', 'server');
api.addFiles('SMS.js', 'server');
api.addFiles('services/twilio.js', 'server');
});
Npm.depends({
'twilio': '2.9.1'
});

@ -0,0 +1,37 @@
/* globals RocketChat */
class Twilio {
constructor() {
this.accountSid = RocketChat.settings.get('SMS_Twilio_Account_SID');
this.authToken = RocketChat.settings.get('SMS_Twilio_authToken');
this.fromNumber = RocketChat.settings.get('SMS_Twilio_fromNumber');
}
parse(data) {
return {
from: data.From,
to: data.To,
body: data.Body,
extra: {
toCountry: data.ToCountry,
toState: data.ToState,
toCity: data.ToCity,
toZip: data.ToZip,
fromCountry: data.FromCountry,
fromState: data.FromState,
fromCity: data.FromCity,
fromZip: data.FromZip
}
};
}
send(to, message) {
var client = Npm.require('twilio')(this.accountSid, this.authToken);
client.messages.create({
to: to,
from: this.fromNumber,
body: message,
});
}
}
RocketChat.SMS.registerService('twilio', Twilio);

@ -0,0 +1,43 @@
Meteor.startup(function() {
RocketChat.settings.addGroup('SMS', function() {
this.add('SMS_Enabled', false, {
type: 'boolean'
});
this.add('SMS_Service', 'twilio', {
type: 'select',
values: [{
key: 'twilio',
i18nLabel: 'Twilio'
}],
i18nLabel: 'Service'
});
this.section('Twilio', function() {
this.add('SMS_Twilio_fromNumber', '', {
type: 'string',
enableQuery: {
_id: 'SMS_Service',
value: 'twilio'
},
i18nLabel: 'From_Number'
});
this.add('SMS_Twilio_Account_SID', '', {
type: 'string',
enableQuery: {
_id: 'SMS_Service',
value: 'twilio'
},
i18nLabel: 'Account_SID'
});
this.add('SMS_Twilio_authToken', '', {
type: 'string',
enableQuery: {
_id: 'SMS_Service',
value: 'twilio'
},
i18nLabel: 'Auth_Token'
});
});
});
});

@ -8,7 +8,7 @@ Meteor.methods
unless rid
throw new Meteor.Error 'invalid-room', '[methods] canAccessRoom -> Cannot access empty room'
room = RocketChat.models.Rooms.findOneById rid, { fields: { usernames: 1, t: 1, name: 1, muted: 1 } }
room = RocketChat.models.Rooms.findOneById rid, { fields: { usernames: 1, t: 1, name: 1, muted: 1, sms: 1, v: 1 } }
if room
if room.usernames.indexOf(user.username) isnt -1
@ -19,6 +19,6 @@ Meteor.methods
if canAccess isnt true
return false
else
return _.pick room, ['_id', 't', 'name', 'usernames', 'muted']
return _.pick room, ['_id', 't', 'name', 'usernames', 'muted', 'sms', 'v']
else
throw new Meteor.Error 'invalid-room', '[methods] canAccessRoom -> Room ID is invalid'

@ -0,0 +1,14 @@
RocketChat.Migrations.add({
version: 45,
up: function() {
// finds the latest created visitor
var lastVisitor = RocketChat.models.Users.find({ type: 'visitor' }, { fields: { username: 1 }, sort: { createdAt: -1 }, limit: 1 }).fetch();
if (lastVisitor && lastVisitor.length > 0) {
var lastNumber = lastVisitor[0].username.replace(/^guest\-/, '');
RocketChat.settings.add('Livechat_guest_count' , (parseInt(lastNumber) + 1), { type: 'int', group: 'Livechat' });
}
}
});
Loading…
Cancel
Save