You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
12 KiB
411 lines
12 KiB
# # #
|
|
# Assign values
|
|
#
|
|
|
|
# Package availability
|
|
IRC_AVAILABILITY = RocketChat.settings.get('IRC_Enabled');
|
|
|
|
# Cache prep
|
|
net = Npm.require('net')
|
|
Lru = Npm.require('lru-cache')
|
|
MESSAGE_CACHE_SIZE = RocketChat.settings.get('IRC_Message_Cache_Size');
|
|
ircReceiveMessageCache = Lru MESSAGE_CACHE_SIZE
|
|
ircSendMessageCache = Lru MESSAGE_CACHE_SIZE
|
|
|
|
# IRC server
|
|
IRC_PORT = RocketChat.settings.get('IRC_Port');
|
|
IRC_HOST = RocketChat.settings.get('IRC_Host');
|
|
|
|
ircClientMap = {}
|
|
|
|
|
|
# # #
|
|
# Core functionality
|
|
#
|
|
|
|
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 = IRC_PORT
|
|
@ircHost = IRC_HOST
|
|
@msgBuf = []
|
|
|
|
@isConnected = false
|
|
@isDistroyed = false
|
|
@socket = new net.Socket
|
|
@socket.setNoDelay
|
|
@socket.setEncoding 'utf-8'
|
|
@socket.setKeepAlive true
|
|
@onConnect = bind @onConnect
|
|
@onClose = bind @onClose
|
|
@onTimeout = bind @onTimeout
|
|
@onError = bind @onError
|
|
@onReceiveRawMessage = bind @onReceiveRawMessage
|
|
@socket.on 'data', @onReceiveRawMessage
|
|
@socket.on 'close', @onClose
|
|
@socket.on 'timeout', @onTimeout
|
|
@socket.on 'error', @onError
|
|
|
|
@isJoiningRoom = false
|
|
@receiveMemberListBuf = {}
|
|
@pendingJoinRoomBuf = []
|
|
|
|
@successLoginMessageRegex = /RocketChat.settings.get('IRC_RegEx_successLogin');/
|
|
@failedLoginMessageRegex = /RocketChat.settings.get('IRC_RegEx_failedLogin');/
|
|
@receiveMessageRegex = /RocketChat.settings.get('IRC_RegEx_receiveMessage');/
|
|
@receiveMemberListRegex = /RocketChat.settings.get('IRC_RegEx_receiveMemberList');/
|
|
@endMemberListRegex = /RocketChat.settings.get('IRC_RegEx_endMemberList');/
|
|
@addMemberToRoomRegex = /RocketChat.settings.get('IRC_RegEx_addMemberToRoom');/
|
|
@removeMemberFromRoomRegex = /RocketChat.settings.get('IRC_RegEx_removeMemberFromRoom');/
|
|
@quitMemberRegex = /RocketChat.settings.get('IRC_RegEx_quitMember');/
|
|
|
|
connect: (@loginCb) =>
|
|
@socket.connect @ircPort, @ircHost, @onConnect
|
|
@initRoomList()
|
|
|
|
disconnect: () ->
|
|
@isDistroyed = true
|
|
@socket.destroy()
|
|
|
|
onConnect: () =>
|
|
console.log '[irc] onConnect -> '.yellow, @user.username, 'connect success.'
|
|
@socket.write "NICK #{@user.username}\r\n"
|
|
@socket.write "USER #{@user.username} 0 * :#{@user.name}\r\n"
|
|
# message order could not make sure here
|
|
@isConnected = true
|
|
@socket.write msg for msg in @msgBuf
|
|
|
|
onClose: (data) =>
|
|
console.log '[irc] onClose -> '.yellow, @user.username, 'connection close.'
|
|
@isConnected = false
|
|
if @isDistroyed
|
|
delete ircClientMap[@user._id]
|
|
else
|
|
@connect()
|
|
|
|
onTimeout: () =>
|
|
console.log '[irc] onTimeout -> '.yellow, @user.username, 'connection timeout.', arguments
|
|
|
|
onError: () =>
|
|
console.log '[irc] onError -> '.yellow, @user.username, 'connection error.', arguments
|
|
|
|
onReceiveRawMessage: (data) =>
|
|
data = data.toString().split('\n')
|
|
for line in data
|
|
line = line.trim()
|
|
console.log "[#{@ircHost}:#{@ircPort}]:", line
|
|
# Send heartbeat package to irc server
|
|
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 = @receiveMemberListRegex.exec line
|
|
if matchResult
|
|
@onReceiveMemberList matchResult[1], matchResult[2].split ' '
|
|
continue
|
|
|
|
matchResult = @endMemberListRegex.exec line
|
|
if matchResult
|
|
@onEndMemberList matchResult[1]
|
|
continue
|
|
|
|
matchResult = @addMemberToRoomRegex.exec line
|
|
if matchResult
|
|
@onAddMemberToRoom matchResult[1], matchResult[2]
|
|
continue
|
|
|
|
matchResult = @removeMemberFromRoomRegex.exec line
|
|
if matchResult
|
|
@onRemoveMemberFromRoom matchResult[1], matchResult[2]
|
|
continue
|
|
|
|
matchResult = @quitMemberRegex.exec line
|
|
if matchResult
|
|
@onQuitMember matchResult[1]
|
|
continue
|
|
|
|
matchResult = @successLoginMessageRegex.exec line
|
|
if matchResult
|
|
@onSuccessLoginMessage()
|
|
continue
|
|
|
|
matchResult = @failedLoginMessageRegex.exec line
|
|
if matchResult
|
|
@onFailedLoginMessage()
|
|
continue
|
|
|
|
onSuccessLoginMessage: () ->
|
|
console.log '[irc] onSuccessLoginMessage -> '.yellow
|
|
if @loginCb
|
|
@loginCb null, @loginReq
|
|
|
|
onFailedLoginMessage: () ->
|
|
console.log '[irc] onFailedLoginMessage -> '.yellow
|
|
@loginReq.allowed = false
|
|
@disconnect()
|
|
if @loginCb
|
|
@loginCb null, @loginReq
|
|
|
|
onReceiveMessage: (source, target, content) ->
|
|
now = new Date
|
|
timestamp = now.getTime()
|
|
|
|
cacheKey = [source, target, content].join ','
|
|
console.log '[irc] ircSendMessageCache.get -> '.yellow, 'key:', cacheKey, 'value:', ircSendMessageCache.get(cacheKey), 'ts:', (timestamp - 1000)
|
|
if ircSendMessageCache.get(cacheKey) > (timestamp - 1000)
|
|
return
|
|
else
|
|
ircSendMessageCache.set cacheKey, timestamp
|
|
|
|
console.log '[irc] onReceiveMessage -> '.yellow, 'source:', source, 'target:', target, 'content:', content
|
|
source = @createUserWhenNotExist source
|
|
if target[0] == '#'
|
|
room = RocketChat.models.Rooms.findOneByName target.substring(1)
|
|
else
|
|
room = @createDirectRoomWhenNotExist(source, @user)
|
|
|
|
message =
|
|
msg: content
|
|
ts: now
|
|
cacheKey = "#{source.username}#{timestamp}"
|
|
ircReceiveMessageCache.set cacheKey, true
|
|
console.log '[irc] ircReceiveMessageCache.set -> '.yellow, 'key:', cacheKey
|
|
RocketChat.sendMessage source, message, room
|
|
|
|
onReceiveMemberList: (roomName, members) ->
|
|
@receiveMemberListBuf[roomName] = @receiveMemberListBuf[roomName].concat members
|
|
|
|
onEndMemberList: (roomName) ->
|
|
newMembers = @receiveMemberListBuf[roomName]
|
|
console.log '[irc] onEndMemberList -> '.yellow, 'room:', roomName, 'members:', newMembers.join ','
|
|
room = RocketChat.models.Rooms.findOneByNameAndType roomName, 'c'
|
|
unless room
|
|
return
|
|
|
|
oldMembers = room.usernames
|
|
appendMembers = _.difference newMembers, oldMembers
|
|
removeMembers = _.difference oldMembers, newMembers
|
|
|
|
for member in appendMembers
|
|
@createUserWhenNotExist member
|
|
|
|
RocketChat.models.Rooms.removeUsernamesById room._id, removeMembers
|
|
RocketChat.models.Rooms.addUsernamesById room._id, appendMembers
|
|
|
|
@isJoiningRoom = false
|
|
roomName = @pendingJoinRoomBuf.shift()
|
|
if roomName
|
|
@joinRoom
|
|
t: 'c'
|
|
name: roomName
|
|
|
|
sendRawMessage: (msg) ->
|
|
console.log '[irc] sendRawMessage -> '.yellow, msg.slice(0, -2)
|
|
if @isConnected
|
|
@socket.write msg
|
|
else
|
|
@msgBuf.push msg
|
|
|
|
sendMessage: (room, message) ->
|
|
console.log '[irc] sendMessage -> '.yellow, 'userName:', message.u.username
|
|
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
|
|
|
|
cacheKey = [@user.username, target, message.msg].join ','
|
|
console.log '[irc] ircSendMessageCache.set -> '.yellow, 'key:', cacheKey, 'ts:', message.ts.getTime()
|
|
ircSendMessageCache.set cacheKey, message.ts.getTime()
|
|
msg = "PRIVMSG #{target} :#{message.msg}\r\n"
|
|
@sendRawMessage msg
|
|
|
|
initRoomList: ->
|
|
roomsCursor = RocketChat.models.Rooms.findByTypeContainigUsername 'c', @user.username,
|
|
fields:
|
|
name: 1
|
|
t: 1
|
|
|
|
rooms = roomsCursor.fetch()
|
|
for room in rooms
|
|
@joinRoom(room)
|
|
|
|
joinRoom: (room) ->
|
|
if room.t isnt 'c' or room.name == 'general'
|
|
return
|
|
|
|
if @isJoiningRoom
|
|
@pendingJoinRoomBuf.push room.name
|
|
else
|
|
console.log '[irc] joinRoom -> '.yellow, 'roomName:', room.name, 'pendingJoinRoomBuf:', @pendingJoinRoomBuf.join ','
|
|
msg = "JOIN ##{room.name}\r\n"
|
|
@receiveMemberListBuf[room.name] = []
|
|
@sendRawMessage msg
|
|
@isJoiningRoom = true
|
|
|
|
leaveRoom: (room) ->
|
|
if room.t isnt 'c'
|
|
return
|
|
msg = "PART ##{room.name}\r\n"
|
|
@sendRawMessage msg
|
|
|
|
getMemberList: (room) ->
|
|
if room.t isnt 'c'
|
|
return
|
|
msg = "NAMES ##{room.name}\r\n"
|
|
@receiveMemberListBuf[room.name] = []
|
|
@sendRawMessage msg
|
|
|
|
onAddMemberToRoom: (member, roomName) ->
|
|
if @user.username == member
|
|
return
|
|
|
|
console.log '[irc] onAddMemberToRoom -> '.yellow, 'roomName:', roomName, 'member:', member
|
|
@createUserWhenNotExist member
|
|
|
|
RocketChat.models.Rooms.addUsernameByName roomName, member
|
|
|
|
onRemoveMemberFromRoom: (member, roomName)->
|
|
console.log '[irc] onRemoveMemberFromRoom -> '.yellow, 'roomName:', roomName, 'member:', member
|
|
RocketChat.models.Rooms.removeUsernameByName roomName, member
|
|
|
|
onQuitMember: (member) ->
|
|
console.log '[irc] onQuitMember ->'.yellow, 'username:', member
|
|
RocketChat.models.Rooms.removeUsernameFromAll member
|
|
|
|
Meteor.users.update {name: member},
|
|
$set:
|
|
status: 'offline'
|
|
|
|
createUserWhenNotExist: (name) ->
|
|
user = Meteor.users.findOne {name: name}
|
|
unless user
|
|
console.log '[irc] createNotExistUser ->'.yellow, 'userName:', name
|
|
Meteor.call 'registerUser',
|
|
email: "#{name}@rocketchat.org"
|
|
pass: 'rocketchat'
|
|
name: name
|
|
Meteor.users.update {name: name},
|
|
$set:
|
|
status: 'online'
|
|
username: name
|
|
user = Meteor.users.findOne {name: name}
|
|
return user
|
|
|
|
|
|
createDirectRoomWhenNotExist: (source, target) ->
|
|
console.log '[irc] createDirectRoomWhenNotExist -> '.yellow, 'source:', source, 'target:', target
|
|
rid = [source._id, target._id].sort().join('')
|
|
now = new Date()
|
|
RocketChat.models.Rooms.upsert
|
|
_id: rid
|
|
,
|
|
$set:
|
|
usernames: [source.username, target.username]
|
|
$setOnInsert:
|
|
t: 'd'
|
|
msgs: 0
|
|
ts: now
|
|
|
|
RocketChat.models.Subscriptions.upsert
|
|
rid: rid
|
|
$and: [{'u._id': target._id}]
|
|
,
|
|
$setOnInsert:
|
|
name: source.username
|
|
t: 'd'
|
|
open: false
|
|
alert: false
|
|
unread: 0
|
|
u:
|
|
_id: target._id
|
|
username: target.username
|
|
return {
|
|
t: 'd'
|
|
_id: rid
|
|
}
|
|
|
|
IrcClient.getByUid = (uid) ->
|
|
return ircClientMap[uid]
|
|
|
|
IrcClient.create = (login) ->
|
|
unless login.user?
|
|
return 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) ->
|
|
name = message.u.username
|
|
timestamp = message.ts.getTime()
|
|
cacheKey = "#{name}#{timestamp}"
|
|
if ircReceiveMessageCache.get cacheKey
|
|
return message
|
|
|
|
room = RocketChat.models.Rooms.findOneById 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
|
|
|
|
|
|
class IrcLogoutCleanUper
|
|
constructor: (user) ->
|
|
ircClient = IrcClient.getByUid user._id
|
|
ircClient.disconnect()
|
|
return user
|
|
|
|
|
|
# # #
|
|
# Make magic happen
|
|
#
|
|
|
|
# Only proceed if the package has been enabled
|
|
if IRC_AVAILABILITY == true
|
|
RocketChat.callbacks.add 'beforeValidateLogin', IrcLoginer, RocketChat.callbacks.priority.LOW, 'irc-loginer'
|
|
RocketChat.callbacks.add 'beforeSaveMessage', IrcSender, RocketChat.callbacks.priority.LOW, 'irc-sender'
|
|
RocketChat.callbacks.add 'beforeJoinRoom', IrcRoomJoiner, RocketChat.callbacks.priority.LOW, 'irc-room-joiner'
|
|
RocketChat.callbacks.add 'beforeCreateChannel', IrcRoomJoiner, RocketChat.callbacks.priority.LOW, 'irc-room-joiner-create-channel'
|
|
RocketChat.callbacks.add 'beforeLeaveRoom', IrcRoomLeaver, RocketChat.callbacks.priority.LOW, 'irc-room-leaver'
|
|
RocketChat.callbacks.add 'afterLogoutCleanUp', IrcLogoutCleanUper, RocketChat.callbacks.priority.LOW, 'irc-clean-up'
|
|
else
|
|
return
|
|
|