diff --git a/src/xmpp.coffee b/src/xmpp.coffee index f21ac69..498ba39 100644 --- a/src/xmpp.coffee +++ b/src/xmpp.coffee @@ -19,6 +19,9 @@ class XmppBot extends Adapter # Key is the room JID, value is the private JID @roomToPrivateJID = {} + # http://stackoverflow.com/a/646643 + String::startsWith ?= (s) -> @slice(0, s.length) == s + run: -> options = username: process.env.HUBOT_XMPP_USERNAME @@ -150,6 +153,44 @@ class XmppBot extends Adapter to: "#{room.jid}/#{@robot.name}", type: 'unavailable') + # Send query for users in the room and once the server response is parsed, + # apply the callback against the retrieved data. + # callback should be of the form `(usersInRoom) -> console.log usersInRoom` + # where usersInRoom is an array of username strings. + # For normal use, no need to pass requestId: it's there for testing purposes. + getUsersInRoom: (room, callback, requestId) -> + # (pseudo) random string to keep track of the current request + # Useful in case of concurrent requests + unless requestId + requestId = 'get_users_in_room_' + Date.now() + Math.random().toString(36).slice(2) + + # http://xmpp.org/extensions/xep-0045.html#disco-roomitems + @client.send do => + @robot.logger.debug "Fetching users in the room #{room.jid}" + message = new ltx.Element('iq', + from : @options.username, + id: requestId, + to : room.jid, + type: 'get') + message.c('query', + xmlns : 'http://jabber.org/protocol/disco#items') + return message + + # Listen to the event with the current request id, one time only + @once "completedRequest#{requestId}", callback + + # XMPP invite to a room, directly - http://xmpp.org/extensions/xep-0249.html + sendInvite: (room, invitee, reason) -> + @client.send do => + @robot.logger.debug "Inviting #{invitee} to #{room.jid}" + message = new ltx.Element('message', + to : invitee) + message.c('x', + xmlns : 'jabber:x:conference', + jid: room.jid, + reason: reason) + return message + read: (stanza) => if stanza.attrs.type is 'error' @robot.logger.error '[xmpp error]' + stanza @@ -177,6 +218,15 @@ class XmppBot extends Adapter @robot.logger.debug "[sending pong] #{pong}" @client.send pong + else if ((stanza.attrs.id.startsWith 'get_users_in_room') && stanza.children[0].children) + roomJID = stanza.attrs.from + userItems = stanza.children[0].children + + # Note that this contains usernames and NOT the full user JID. + usersInRoom = (item.attrs.name for item in userItems) + @robot.logger.debug "[users in room] #{roomJID} has #{usersInRoom}" + + @emit "completedRequest#{stanza.attrs.id}", usersInRoom readMessage: (stanza) => # ignore non-messages diff --git a/test/adapter-test.coffee b/test/adapter-test.coffee index 542b42c..51f837d 100644 --- a/test/adapter-test.coffee +++ b/test/adapter-test.coffee @@ -173,6 +173,19 @@ describe 'XmppBot', -> done() bot.readIq stanza + it 'should parse room query iqs for users in the room', (done) -> + stanza.attrs.id = 'get_users_in_room_8139nj32ma' + stanza.attrs.from = 'test@example.com' + userItems = [ + { attrs: {name: 'mark'} }, + { attrs: {name: 'anup'} } + ] + stanza.children = [ {children: userItems} ] + bot.on "completedRequest#{stanza.attrs.id}", (usersInRoom) -> + assert.deepEqual usersInRoom, (item.attrs.name for item in userItems) + done() + bot.readIq stanza + describe '#readMessage()', -> stanza = '' bot = Bot.use() @@ -337,6 +350,70 @@ describe 'XmppBot', -> done() bot.topic envelope, 'one', 'two' + describe '#getUsersInRoom()', -> + bot = Bot.use() + + bot.client = + stub: 'xmpp client' + + bot.robot = + name: 'bot' + logger: + debug: () -> + + bot.options = + username: 'bot@example.com' + + room = + jid: 'test@example.com' + password: false + + it 'should call @client.send()', (done) -> + bot.client.send = (message) -> + assert.equal message.attrs.from, bot.options.username + assert.equal (message.attrs.id.startsWith 'get_users_in_room'), true + assert.equal message.attrs.to, room.jid + assert.equal message.attrs.type, 'get' + assert.equal message.children[0].name, 'query' + assert.equal message.children[0].attrs.xmlns, 'http://jabber.org/protocol/disco#items' + done() + bot.getUsersInRoom room, () -> + + it 'should call callback on receiving users', (done) -> + users = ['mark', 'anup'] + requestId = 'get_users_in_room_8139nj32ma' + bot.client.send = () -> + callback = (usersInRoom) -> + assert.deepEqual usersInRoom, users + done() + bot.getUsersInRoom room, callback, requestId + bot.emit "completedRequest#{requestId}", users + + describe '#sendInvite()', -> + bot = Bot.use() + + bot.client = + stub: 'xmpp client' + + bot.robot = + name: 'bot' + logger: + debug: () -> + + room = + jid: 'test@example.com' + password: false + invitee = 'anup@example.com' + reason = 'Inviting to test' + + it 'should call @client.send()', (done) -> + bot.client.send = (message) -> + assert.equal message.attrs.to, invitee + assert.equal message.children[0].attrs.jid, room.jid + assert.equal message.children[0].attrs.reason, reason + done() + bot.sendInvite room, invitee, reason + describe '#error()', -> bot = Bot.use() bot.robot =