IM Integration With XMPP4r : Part 2

In the first part, we talked less about XMPP4r and more about XMPP, this time it will be the other way around.
Now that we know that XMPP messages are XML bits of information exchanged between a client and a server via a TCP connection, we are more able to understand the purpose of XMPP4r.
What is XMPP4r?
Here is the most simple definition I came out with : XMPP4r is a ruby library that acts as a XMPP Client. Understand it that way and you won’t be confuse about what XMPP4r is supposed to do. Like any other jabber client (google talk, pidgin, etc), Xmpp4r sends, receives and manages XML messages called stanzas.
In a sense, XMPP4r is like GoogleTalk without the GUI. (And, of course, XMPP4r is not already implemented… you have to code the behavior of the client yourself). With GoogleTalk, you connect on a Jabber Server by pressing the connect button. With XMPP4r, you connect on a Jabber server that way :

client = Client.new(JID::new("[email protected]"))
client.connect
client.auth("my_extremely_secret_password")
client.send(Presence.new.set_type(:available))

The first line simply creates a new instance of the Client class. This instance represents the user itself. The 2nd line tries to establish a connection between the user and the Jabber server (somewhere.com). The 3rd line tries to authenticate the user using the following mechanisms.

  • SASL DIGEST-MD5
  • SASL PLAIN
  • Non-SASL digest

Now about the 4th line. Remember when I talked about stanzas in the first part? Well, at the 4th line, we sent our first stanza to our Jabber Server… a presence stanza. We did this because we want our server to know we are there. That way, everyone in our buddy list will know we are online and ready to chat!
Sending messages
So, what now? You are online, fine… but how can you exchange messages with people? That’s pretty simple…

msg = Message::new("[email protected]", "Hello... John?")
msg.type=:chat
client.send(msg)

Important note : You have to set the message type to “chat” because some clients will react differently depending of the message type. A Gajim user will hate you if you send him messages with a message type of “normal”, because Gajim popups a new window for every single “normal” messages it receives. On the other hand, it uses the same window for “chat” messages coming from the same user.
Ok so we know how to send a message to someone, but what if that someone is not in our buddy list? Well, simply put, it will not work. XMPP just doesn’t allow this and we all agree that it’s a good thing. Who like to be spammed?
Say we want to add [email protected] to our buddy list, we do :

pres = Presence.new.set_type(:subscribe).set_to("[email protected]")
client.send(pres)

Ok so we sent our request… what about the response? Fortunately, the line above won’t wait for a result. As you can imagine, it could get pretty long… we don’t control the answer of the person at the other end after all. This leads me to talk to you about a key feature of xmpp4r : callbacks.
Registering Callbacks
Let’s get back to our subscription request we sent to John earlier. Since we said that our line of code would NOT wait for a response, we need some other way to get that response when it will come. (that’s pretty much what callbacks are for, right?)
Because we sent a subscription request, the callback “add_update_callback” will be called as soon as the user at the other end will reply. If you want to be notified when this happens, you have to register to this callback :

client.add_update_callback do |presence|
  if presence.from == "[email protected]" && presence.ask == :subscribe
    client.send(presence.from, "I am so very happy you have accept my request John, you rock! I will spam you for the rest of my life, but I know you will understand because I feel we do 'connect'")
  end
end

Xmpp4r provides callbacks for a lot of purposes. Now if I want to be notified of the messages sent to us by others, what do I have to use? The answer is add_message_callback!

client.add_message_callback do |m|
if m.from == "[email protected]"
  #oh great! I have received a response from my new friend Johh! Better see what he says.
   m.body
# output : Listen, you moron. I don't know who you are nor why you are so enthusiast about speaking with someone you don't know, but I strongly suggest you to get a life. Stop or I remove you from my buddy list, FOREVER!
end

There is also a very useful callback that lets you know when the availability of someone in your buddy list change.

client.add_presence_callback do |old_presence, new_presence|
if new_presence.from == "[email protected]" && new_presence.show == :dnd
		msg = Message::new("[email protected]", "John... I know you don't want to be bothered (dnd = do not disturb) but I just wanted to say HI anyway! I read your previous message and I sill think we do 'connect'. I just think you have trouble assuming your sensibility"
		msg.type=:chat
		client.send(msg)
end

Note that the various callbacks in xmpp4r run within their own thread.
Well, this is the end of the 2nd part. Next part should be about the Roster helper and I also want to criticize Xmpp4r about his stability (lack of) and somewhat incomplete documentation.
Woooh! Silly me… here is an important update
Don Park and Nilu had problems with this tutorial… and it’s probably because I forgot to talk about an essential part : The subscription. To receive messages from others, you have to accept their subscription requests first! Here is how

  1. require ‘xmpp4r/roster’ (a roster is an object representing your buddy list)
  2. roster = Roster::Helper.new(client)
  3. Implement the add_subscription_request_callback that way (if you want to accept everyone) :
    roster.add_subscription_request_callback do |item,pres|
      roster.accept_subscription(pres.from)
    end

You should be fine now.. sorry about that
UPDATE October 21st 2008
Some interesting remarks in the comments deserved a proper update :
Nilu,
I’m sure you have your answer already, but let me explain the “running in their own thread” thing :
The callbacks are invoked in the context of the “parser thread” which is the one doing the actual “listening” of the the XML stream. When something happens in the parser thread (e.g. message stanza received), callbacks are invoked from there. So the code you have in your various callbacks are effectively executed in the parser thread context.
Like Leonardo wrote, you have to write Thread.stop in your main thread when you have set all your callbacks and other initialization stuff.
It might sound confusing, but you have to write Thread.stop to keep the current thread alive. Thread.stop will simply put the current thread into sleep mode and leave all the scheduling time to the other threads (the parser thread in our case). If you don’t write Thread.stop, everything ends when the main thread has nothing left to do.

Rubyize this : 5th edition

It’s been a while since the last edition of Rubyize this. I have waited all this time because I like when people become impatient. I have received 254 939 emails from desperate people asking me : “When will you post your 5th edition?”, “Is it hard to be that excellent?”, “You rock Frank. Warm kisses… – Vanessa” and such.
Hold on a second, gotta update my checklist :

  1. Invent myself a pleasing reality
  2. Impress people with fictitious numbers
  3. Write a bad intro for the 5th edition

2 days ago I received an email from Scott Patten, an active web developer based in Vancouver. He is currently helping to organize RubyCamp Vancouver 2008, which is a free one-day gathering for Rubyists and Railers. In his email, he asked me if he could use the Rubyize this concept for an (un)conference event that would be held at RubyCamp. Of course, I said yes! It now seems official : Rubyize this will be part of RubyCamp Vancouver 2008! I was also happy when I read the following : “the audience will submit their refactorings to Refactor My Code“. That’s good for you, Marc!
Important : The future of Rubyize this
I need your help to create ugly pieces of code! I don’t want to become repetitive from edition to edition so I’d really appreciate if you could send me some fresh ideas. Just send me an email to [email protected] containing an ugly piece of code as well as a small intro to help us understand what this is all about. I’ll make sure to put your name as well as a link to your website (if any) in the appropriate Rubyize This edition.
Enough babbling :
Tom wrote a very simple ruby script to highlight some words in a text. He chose to highlight the words with asterisks, like *that* (In 2 months, perhaps Tom will have to use his script to produce HTML output or something else… he will be screwed with those basic asterisks). Tom has absolutely no ruby background, help him refactor his code ala Ruby, that is : short, clean and simple.
By the way, Tom didn’t put a lot of effort in his algorithm. His dumb gsub thing will mistakenly replace parts of words instead of whole words only. Maybe some improvements would be needed there.

@wordsToHighlight=["important","monkey","dancing"]
def highlightText(input)
  for i in [email protected] do
    input = input.gsub(@wordsToHighlight[i],"*" + @wordsToHighlight[i] + "*")
  end
  return input
end

Refactor this!