Skip to content

Commit

Permalink
Merge pull request #215 from haosdent/irc
Browse files Browse the repository at this point in the history
IRC prototype.

I'll accept the PR, but I'll make it disable by default.. at least until we fully test it.
  • Loading branch information
engelgabriel committed Jun 22, 2015
2 parents 6d08687 + d28ba96 commit adfc84a
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 1 deletion.
1 change: 1 addition & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ rocketchat:markdown
rocketchat:me
rocketchat:mentions
rocketchat:oembed
rocketchat:irc
tap:i18n
tmeasday:crypto-md5
tmeasday:errors
Expand Down
1 change: 1 addition & 0 deletions .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
ronhoffmann:[email protected]
[email protected]
[email protected]
Expand Down
1 change: 1 addition & 0 deletions packages/rocketchat-irc/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Move to [rocketchat-irc](https://github.com/haosdent/Rocket.Chat/tree/irc/packages/rocketchat-irc).
165 changes: 165 additions & 0 deletions packages/rocketchat-irc/irc.server.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
net = Npm.require('net')

ircClientMap = {}

bind = (f) ->
g = Meteor.bindEnvironment (self, args...) -> f.apply(self, args)
(args...) -> g @, args...

async = (f, args...) ->
Meteor.wrapAsync(f)(args...)

class IrcClient
constructor: (@loginReq) ->
@user = @loginReq.user
ircClientMap[@user._id] = this
@ircPort = 6667
@ircHost = 'irc.freenode.net'
@msgBuf = []
@isConnected = false
@socket = new net.Socket
@socket.setNoDelay
@socket.setEncoding 'utf-8'
@onConnect = bind @onConnect
@onReceiveRawMessage = bind @onReceiveRawMessage
@socket.on 'data', @onReceiveRawMessage
@socket.on 'close', @onClose
@receiveMessageRegex = /^:(\S+)!~\S+ PRIVMSG (\S+) :(.+)$/
@memberListRegex = /^:\S+ \d+ \S+ = #(\S+) :(.*)$/
@initRoomList()

connect: (@loginCb) =>
@socket.connect @ircPort, @ircHost, @onConnect

onConnect: () =>
console.log @user.username, 'connect success.'
@socket.write "NICK #{@user.username}\r\n"
@socket.write "USER #{@user.username} 0 * :Real Name\r\n"
# message order could not make sure here
@isConnected = true
@socket.write msg for msg in @msgBuf
@loginCb null, @loginReq

onClose: (data) =>
console.log @user.username, 'connection close.'

onReceiveRawMessage: (data) =>
data = data.toString().split('\n')
for line in data
line = line.trim()
console.log "[#{@ircHost}:#{@ircPort}]:", line
if line.indexOf('PING') == 0
@socket.write line.replace('PING :', 'PONG ')
continue

matchResult = @receiveMessageRegex.exec line
if matchResult
@onReceiveMessage matchResult[1], matchResult[2], matchResult[3]
continue

matchResult = @memberListRegex.exec line
if matchResult
@onReceiveMemberList matchResult[1], matchResult[2].split ' '
continue

onReceiveMessage: (name, target, content) ->
console.log '[irc] onReceiveMessage -> '.yellow, 'sourceUserName:', name, 'target:', target, 'content:', content
if target[0] == '#'
room = ChatRoom.findOne {name: target.substring 1}
else
room = ChatRoom.findOne {usernames: { $all: [target, name]}, t: 'd'}, { fields: { usernames: 1, t: 1 } }

sourceUser = Meteor.users.findOne {username: message.u.username}, fields: username: 1
unless sourceUser
Meteor.call 'registerUser',
email: '',
password: name,
name: name
Meteor.call 'receiveMessage',
u:
username: name
rid: room._id
msg: content

onReceiveMemberList: (room, members) ->
console.log '[irc] onReceiveMemberList -> '.yellow, 'room:', room, 'members:', members

sendRawMessage: (msg) ->
console.log '[irc] sendRawMessage -> '.yellow, msg
if @isConnected
@socket.write msg
else
@msgBuf.push msg

sendMessage: (room, message) ->
console.log '[irc] sendMessage -> '.yellow, 'userName:', message.u.username, 'arguments:', arguments
target = ''
if room.t == 'c'
target = "##{room.name}"
else if room.t == 'd'
for name in room.usernames
if message.u.username != name
target = name
break
msg = "PRIVMSG #{target} :#{message.msg}\r\n"
@sendRawMessage msg

initRoomList: () ->
roomsCursor = ChatRoom.find {usernames: { $in: [@user.username]}, t: 'c'}, { fields: { name: 1 }}
rooms = roomsCursor.fetch()
for room in rooms
@joinRoom(room)

joinRoom: (room) ->
msg = "JOIN ##{room.name}\r\n"
@sendRawMessage msg
msg = "NAMES ##{room.name}\r\n"
@sendRawMessage msg

leaveRoom: (room) ->
msg = "PART ##{room.name}\r\n"
@sendRawMessage msg


IrcClient.getByUid = (uid) ->
return ircClientMap[uid]

IrcClient.create = (login) ->
unless login.user._id of ircClientMap
ircClient = new IrcClient login
return async ircClient.connect
return login


class IrcLoginer
constructor: (login) ->
console.log '[irc] validateLogin -> '.yellow, login
return IrcClient.create login


class IrcSender
constructor: (message) ->
room = ChatRoom.findOne message.rid, { fields: { name: 1, usernames: 1, t: 1 } }
ircClient = IrcClient.getByUid message.u._id
ircClient.sendMessage room, message
return message


class IrcRoomJoiner
constructor: (user, room) ->
ircClient = IrcClient.getByUid user._id
ircClient.joinRoom room
return room


class IrcRoomLeaver
constructor: (user, room) ->
ircClient = IrcClient.getByUid user._id
ircClient.leaveRoom room
return room


RocketChat.callbacks.add 'beforeValidateLogin', IrcLoginer, RocketChat.callbacks.priority.LOW
RocketChat.callbacks.add 'beforeSaveMessage', IrcSender, RocketChat.callbacks.priority.LOW
RocketChat.callbacks.add 'beforeJoinRoom', IrcRoomJoiner, RocketChat.callbacks.priority.LOW
RocketChat.callbacks.add 'beforeLeaveRoom', IrcRoomLeaver, RocketChat.callbacks.priority.LOW
21 changes: 21 additions & 0 deletions packages/rocketchat-irc/package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Package.describe({
name: 'rocketchat:irc',
version: '0.0.1',
summary: 'RocketChat libraries',
git: ''
});

Package.onUse(function(api) {
api.versionsFrom('1.0');

api.use([
'coffeescript',
'rocketchat:[email protected]'
]);

api.addFiles('irc.server.coffee', 'server');

api.export(['Irc'], ['server']);
});

Package.onTest(function(api) {});
3 changes: 3 additions & 0 deletions server/lib/accounts.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Accounts.onCreateUser (options, user) ->


Accounts.validateLoginAttempt (login) ->
login = RocketChat.callbacks.run 'beforeValidateLogin', login
if login.allowed isnt true
return login.allowed

Expand All @@ -58,5 +59,7 @@ Accounts.validateLoginAttempt (login) ->
return false

Meteor.users.update {_id: login.user._id}, {$set: {lastLogin: new Date}}
Meteor.defer ->
RocketChat.callbacks.run 'afterValidateLogin', login

return true
5 changes: 5 additions & 0 deletions server/methods/joinRoom.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ Meteor.methods

# verify if user is already in room
# if room.usernames.indexOf(user.username) is -1
console.log '[methods] joinRoom -> '.green, 'userId:', Meteor.userId(), 'arguments:', arguments

now = new Date()

user = Meteor.users.findOne Meteor.userId()

RocketChat.callbacks.run 'beforeJoinRoom', user, room

update =
$push:
usernames:
Expand Down Expand Up @@ -42,4 +45,6 @@ Meteor.methods
_id: user._id
username: user.username

RocketChat.callbacks.run 'afterJoinRoom', user, room

return true
4 changes: 4 additions & 0 deletions server/methods/leaveRoom.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Meteor.methods

room = ChatRoom.findOne rid

RocketChat.callbacks.run 'beforeLeaveRoom', Meteor.user(), room

update =
$pull:
usernames: Meteor.user().username
Expand Down Expand Up @@ -45,3 +47,5 @@ Meteor.methods
ChatSubscription.remove { rid: rid, 'u._id': Meteor.userId() }

ChatRoom.update rid, update

RocketChat.callbacks.run 'afterLeaveRoom', Meteor.user(), room
126 changes: 126 additions & 0 deletions server/methods/receiveMessage.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
Meteor.methods
receiveMessage: (message) ->
if message.u?._id?
else if message.u?.username?
message.u = Meteor.users.findOne {username: message.u.username}, fields: username: 1
else
return false

room = ChatRoom.findOne message.rid, { fields: { usernames: 1, t: 1 } }

if not room
return false

console.log '[methods] receiveMessage -> '.green, 'userId:', message.u._id, 'arguments:', arguments

message.ts = new Date()

if urls = message.msg.match /([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\w]+)?\??([-\+=&!:;%@\/\.\,\w]+)?#?([\w]+)?)?/g
message.urls = urls

message = RocketChat.callbacks.run 'beforeReceiveMessage', message

###
Defer other updated as their return is not interesting to the user
###
Meteor.defer ->

###
Update all the room activity tracker fields
###
ChatRoom.update
# only subscriptions to the same room
rid: message.rid
,
# update the last message timestamp
$set:
lm: message.ts
# increate the messages counter
$inc:
msgs: 1


# increment unread couter if direct messages
if room.t is 'd'
###
Update the other subscriptions
###
ChatSubscription.update
# only subscriptions to the same room
rid: message.rid
# not the msg owner
'u._id':
$ne: message.u._id
,
$set:
# alert de user
alert: true
# open the room for the user
open: true
# increment unread couter
$inc:
unread: 1

else
message.mentions?.forEach (mention) ->
console.log mention
###
Update all other subscriptions of mentioned users to alert their owners and incrementing
the unread counter for mentions and direct messages
###
ChatSubscription.update
# only subscriptions to the same room
rid: message.rid
# the mentioned user
'u._id': mention._id
,
$set:
# alert de user
alert: true
# open the room for the user
open: true
# increment unread couter
$inc:
unread: 1

###
Update all other subscriptions to alert their owners but witout incrementing
the unread counter, as it is only for mentions and direct messages
###
ChatSubscription.update
# only subscriptions to the same room
rid: message.rid
# only the ones that have not been alerted yet
alert: false
# not the msg owner
'u._id':
$ne: message.u._id
,
$set:
# alert de user
alert: true
# open the room for the user
open: true
,
# make sure we alert all matching subscription
multi: true

###
Save the message. If there was already a typing record, update it.
###
ChatMessage.upsert
rid: message.rid
t: 't'
$and: [{ 'u._id': message.u._id }]
,
$set: message
$unset:
t: 1
expireAt: 1

Meteor.defer ->

message._id = Random.id()
RocketChat.callbacks.run 'afterReceiveMessage', message


3 changes: 2 additions & 1 deletion server/methods/registerUser.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ Meteor.methods
$set:
name: formData.name

Accounts.sendVerificationEmail(userId, userData.email);
if userData.email
Accounts.sendVerificationEmail(userId, userData.email);

0 comments on commit adfc84a

Please sign in to comment.