Articles / ICQ Development with the ic…

ICQ Development with the ickle Library

In recent years, the popularity of instant messaging solutions has grown dramatically. It's now difficult to say who was the first to invent something more rapid than email, but there is no doubt that among the wide variety of existing IM solutions, ICQ is the most popular one.

In the GNU world, as usual, there is choice, for there are a lot of ICQ client applications to choose from. They are graphical, console-based, GTK+-based, Qt-based, ncurses-based, commandline-based, skinnable, Web-based, etc. I am the author of one of them, called centericq, which is intended to perfectly suit textmode freaks like me. Recently, it became not only a client for ICQ, but for other IM systems as well.

Mirabilis ICQ was created back in 1996 by several Israeli guys, was free-of-charge software, and gained great popularity because it was easy to use, fast, and nice. Initially, there was a client program for Windows, then they wrote ones for MacOS and PalmOS, and some kind of a Java client which was supposed to work under all the OSes, but which now seems to be outdated. There had been no movements towards creating an official client for Linux (and other Unices) so far. Also, there had been no protocol specification, as they shipped only a Windows DLL which could be used in third party programs. The Free Software community reacted to it slowly, and the first successful attempt to make a GPL-licensed client was made by Matthew Smith several years ago. Dennis V. Dmitrienko then used its code to make a universal library for ICQ applications development, named icqlib. Though some authors of popular Open Source ICQ applications such as Licq used their own code, icqlib was widely used in various client programs. Unfortunately, after some time of silence, the project seemed to be dead, but I kept using icqlib in centericq, making my own minor corrections to its code.

Protocol Problems

Everything went well -- I implemented White pages search and some other features icqlib was missing -- until Mirabilis decided to get all of their users to use their new software (and new protocol) several months ago. I can only guess this is what happened, because first, offline messages sent by the server on every login were not automatically wiped out, and then, after some time, messages sent through the ICQ server disappeared in quite a random way. Also, find queries returned nothing, etc. The same thing happened to their own old icq99 client for Windows. Well, there was nothing wrong about it, since the protocol was their property and they had the right to do anything with it, so they're not the ones to blame.

Obviously, this had a bad effect on Linux (and *BSD and other Unices) users. They suddenly weren't able to use any of the available clients, which all used the outdated protocol. As an author of such a client, I was quite upset, mainly because I was using it for my everyday communications. centericq users started complaining about all the problems they had, and, every time, I had to respond that there were server problems.

I didn't want my work to be in vain, for I already had a useful interface of menus, windows, and dialogs in my software. On the other hand, I had no passion to learn the new ICQ protocol myself; it would require a lot of time to get a clue about their formats, time I didn't have. I had just moved to another country, and had to arrange everything here and start my new job. I decided to take a look at other GPLed IM libraries and incorporate them into centericq gradually, to enable me and users of my software to at least use something. I had some time to restructure centericq's internals, to make it easier to add new protocols, etc. It was quite amusing, and resulted in support for the MSN and Yahoo! protocols. I managed to make something like gaim or Everybuddy, but for my favorite console environment.

Though I was quite happy with Yahoo! (finally, I understood why our superia at Websci prefers it to ICQ <grin>), I was actively looking for something to use to repair the ICQ protocol in my program. I found it in a small project named ickle, lead by Barnaby Gray. The description spoke of v8 protocol support. When I downloaded the source, I expected the protocol code to be roughly knocked into the client program code. Unfortunately, this is what usually happens, because the authors don't pay enough attention to the code design. But I was amazed; the protocol handling code and the client were completely separate, had a nice C++ design, and were split into classes in a very nice way. Barnaby also appeared to be a nice guy, for when I dropped him an email with some questions concerning his code, I got an answer in about 5 minutes. It was courteous and contained an exhaustive explanation for everything I asked.

Now, after 6 days of using his library, I can consider myself an expert at its interface and usage. What I want to say is that even now, it's quite ready for use in your applications, whatever you write. If you're reading this and you're an author of an ICQ client program, it's time for you to migrate to ickle library to make your users happy. Also, if you need to write something which uses the ICQ protocol (to send alert messages automatically or to fetch user information, for example), it's just ideal.

An Introduction to ickle

This part of the article is intended to be a small (really small, in fact) attempt to document the ickle library, and to give you some starting points. Again, I was just amazed with Barnaby's C++ design skills. From the first glance, I thought that if I'd ever teach someone C++ and object oriented design, the source of ickle would be used as one of the best examples.

The main client class is named ICQ2000::Client, and it resides in the Client.h file. It controls all the communications, and it's what you want to use first. The entire library is callback-driven. Callbacks are implemented using libsigc++, a C++ signalling library. First, some callbacks need to be set. libsigc++ requires that the callbacks' owner class be derived from SigC::Object. This piece of code contains the definition of our program's main class, and the constructor code:


#include <set>
#include "Client.h"
#include "events.h"

using namespace ICQ2000;
using namespace std;

class SimpleClient: public SigC::Object {
    private:
        Client cli;
        set<int> rfds, wfds, efds;
        bool acked;
        MessageEvent *msg;
        Contact destContact;

        void connected_cb(ConnectedEvent *c);
        void disconnected_cb(DisconnectedEvent *c);
        void logger_cb(LogEvent *c);
        void socket_cb(SocketEvent *ev);
        void messageack_cb(MessageEvent *ev);

    public:
        SimpleClient(unsigned int ourUIN, const string pass,
            unsigned int destUIN);

        void exec();
};

SimpleClient::SimpleClient(unsigned int ourUIN, const string pass,
unsigned int destUIN): cli(ourUIN, pass), destContact(destUIN) {
    cli.connected.connect(slot(this, &SimpleClient::connected_cb));
    cli.disconnected.connect(slot(this, &SimpleClient::disconnected_cb));
    cli.logger.connect(slot(this, &SimpleClient::logger_cb));
    cli.socket.connect(slot(this, &SimpleClient::socket_cb));
    cli.messageack.connect(slot(this, &SimpleClient::messageack_cb));

    acked = false;
    msg = 0;
}

The "connect" and "disconnect" callbacks are executed on appropriate events, "logger" is be used to print log messages, and "messageack" reports messages' delivery status. The "socket" callback needs special attention. It's executed every time the set of socket descriptors controlled by the library is changed. This example maintains the sets in the rfds, wfds, and efds member variables that represent read, write, and exception watched sockets respectively. Here's the callbacks code:


void SimpleClient::socket_cb(SocketEvent *ev) {
    int fd;

    if(dynamic_cast<AddSocketHandleEvent *>(ev) != NULL) {
        AddSocketHandleEvent *cev = dynamic_cast<AddSocketHandleEvent *>(ev);

        fd = cev->getSocketHandle();
        cout << "connecting socket " << fd << endl;

        if(cev->isRead()) rfds.insert(fd);
        if(cev->isWrite()) wfds.insert(fd);
        if(cev->isException()) efds.insert(fd);

    } else if(dynamic_cast<RemoveSocketHandleEvent *>(ev) != NULL) {
        RemoveSocketHandleEvent *cev = dynamic_cast<RemoveSocketHandleEvent *>(ev);

        fd = cev->getSocketHandle();
        cout << "disconnecting socket " << fd << endl;

        rfds.erase(fd);
        wfds.erase(fd);
        efds.erase(fd);

    }
}

void SimpleClient::connected_cb(ConnectedEvent *c) {
    cout << "Connected" << endl;

    msg = new NormalMessageEvent(&destContact, "Hello");

    cli.addContact(destContact);
    cli.SendEvent(msg);
}

void SimpleClient::disconnected_cb(DisconnectedEvent *c) {
    if(c->getReason() == DisconnectedEvent::REQUESTED) {
        cout << "Disconnected as requested" << endl;
    } else {
        cout << "Problem connecting: ";

        switch(c->getReason()) {
            case DisconnectedEvent::FAILED_LOWLEVEL:
                cout << "Socket problems";
                break;
            case DisconnectedEvent::FAILED_BADUSERNAME:
                cout << "Bad Username";
                break;
            case DisconnectedEvent::FAILED_TURBOING:
                cout << "Turboing";
                break;
            case DisconnectedEvent::FAILED_BADPASSWORD:
                cout << "Bad Password";
                break;
            case DisconnectedEvent::FAILED_MISMATCH_PASSWD:
                cout << "Username and Password did not match";
                break;
            case DisconnectedEvent::FAILED_UNKNOWN:
                cout << "Unknown";
                break;
        }

        cout << endl;
    }
}

void SimpleClient::logger_cb(LogEvent *c) {
    switch(c->getType()) {
        case LogEvent::INFO:
            cout << "\033[34m";
            break;
        case LogEvent::WARN:
            cout << "\033[31m";
            break;
        case LogEvent::PACKET:
        case LogEvent::DIRECTPACKET:
            cout << "\033[32m";
            break;
    }

    cout << c->getMessage() << endl;
    cout << "\033[39m";
}

void SimpleClient::messageack_cb(MessageEvent *ev) {
    Contact *ic;

    if(ev == msg)
    if(ev->isFinished() && ev->isDelivered()) {
        cout << "Message has been delivered well. Terminating" << endl;
        acked = true;
    }
}

As you can see, every callback receives a pointer to an event class as a parameter. Interfaces of the event classes can be found in the events.h header file, and are quite self-descriptive. Since our example is only intended to connect to the ICQ network, send a message, and disconnect, the callbacks behave as shown above. As soon as a connection is established, a message is created and sent. Please note that the SendEvent() method receives a pointer as a parameter. This is necessary to make message acknowledgements work. The pointer lives until it's passed to the messageack callback, then is destroyed. Also, please note that in order to receive messages from someone, you should have their UIN on your ickle contact list, so you'll need to call the addContact() method of the Client class.

Finally, here are the SimpleClient::exec() method and the main() function:


void SimpleClient::exec() {
    int max_fd;
    set<int>::iterator i;

    cli.setStatus(STATUS_ONLINE);

    while(!acked) {
        fd_set rf, wf, ef;
        struct timeval tv;
        max_fd = -1;

        FD_ZERO(&rf);
        for(i = rfds.begin(); i != rfds.end(); i++) {
            FD_SET(*i, &rf);
            max_fd = max(*i, max_fd);
        }

        FD_ZERO(&wf);
        for(i = wfds.begin(); i != wfds.end(); i++) {
            FD_SET(*i, &wf);
            max_fd = max(*i, max_fd);
        }

        FD_ZERO(&ef);
        for(i = efds.begin(); i != efds.end(); i++) {
            FD_SET(*i, &ef);
            max_fd = max(*i, max_fd);
        }

        tv.tv_sec = 60;
        tv.tv_usec = 0;

        if(select(max_fd+1, &rf, &wf, &ef, &tv)) {
            for(i = rfds.begin(); i != rfds.end(); i++) {
                if(FD_ISSET(*i, &rf)) {
                    cli.socket_cb(*i, SocketEvent::READ);
                }
            }

            for(i = wfds.begin(); i != wfds.end(); i++) {
                if(FD_ISSET(*i, &wf)) {
                    cli.socket_cb(*i, SocketEvent::WRITE);
                }
            }

            for(i = efds.begin(); i != efds.end(); i++) {
                if(FD_ISSET(*i, &ef)) {
                    cli.socket_cb(*i, SocketEvent::EXCEPTION);
                }
            }
        } else {
            cli.PingServer();
        }
    }

    cli.setStatus(STATUS_OFFLINE);
}

int main() {
    SimpleClient s(123, "xxx", 456);
    s.exec();
    return 0;
}

Sure, the socket descriptors processing could be done in a nicer way, but it's this dumb here for the sake of clarity. Note that the SimpleClient's constructor takes login parameters and the UIN to which the message is to be sent.

For another good example, look in the ickle package, where you'll find example/shell.cpp source which implements an auto-respond mechanism for ICQ.

Conclusion

I conclude that even though the ickle library misses some major features such as finding users and extended details lookup, it's already more than just usable. It's possible to send messages, URLs, SMSes, authorization requests, and acknowledgements, to register new UINs, to read away messages, etc. The major part of the work is done. At this moment, 9th December 2001, 22:30:07 EET, it is probably the best ICQ protocol implementation which also has all the benefits of a GNU project.

Recent comments

15 Dec 2003 18:21 Avatar kojie

Unable to use Client.h and events.h
i already installed all the libraries with the dependencies
but im unable to compile
I get these errors:
* /home/diablo/dev/icq_send/src/icq_send.cpp:9:20: Client.h: No such file or directory
* /home/diablo/dev/icq_send/src/icq_send.cpp:10:20: events.h: No such file or directory
&lt;other lines trucated- most are jst related to the missing files&gt;

although they are already in the: /usr/local/include/libicq2000/libicq2000/Client.h
/usr/local/include/libicq2000/libicq2000/events.h

any ideas ??? help asap

03 Aug 2002 13:53 Avatar arnognulf

Re: ICQ/AIM/Yahoo! and other closed protocols

>
> %
> % % As mond sort of pointed out
> % already...
> % %
> % % Could someone please explain to me
> % why
> % % open source developers are so
> % accepting
> % % of these closed protocols? Yeah, I
> % know
> % % I'm biased working on an open one,
> % but I
> % % just want to know. It's not like
> % these
> % % protocols are even really good
> % protocols
> % % or anything - are they just
> % accepted
> % % &quot;because so many people use
> % % them&quot;? You could make the
> % exact
> % % same argument for any other piece
> % of
> % % closed source software. There has
> to
> % be
> % % a better reason than that - what is
> % it?
> %
> %
> % popularity, that is key
> %
>
>
> Windows is more popular than Linux. Word
> is more popular than all of the open
> source word processors combined. Yet
> Open Source fans use Linux even when it
> would be easier to use windows.
>


see it like this: word can be replaced by openoffice/abiword/kword/emacs/whatever (offcourse there is some tradeoff of features since you're running free), Photoshop can be replaced by gimp - a huge tradeoff, but certainly enough for me not doing proffessional art work, the list can go on, I even select my hardware and play 'outdated' games just to stay in wonderful linux - which I don't run for ideological reasons.

however trying to get all my friends (and new friends I meet) to use some akward IM protocol just because it's free (jabber devs, no offence, I haven't tried it) really makes no sense to me - when the only reply I get will be "But ICQ is also free..."

26 Mar 2002 05:27 Avatar DiskDoc

Re: ICQ the most popular?
Here in Finland pretty much everyone uses ICQ. I don't know of anyone to use anything else. Intetersting to hear of MSNs popularity in Holland!

25 Feb 2002 21:37 Avatar jmissig

Re: ICQ/AIM/Yahoo! and other closed protocols

>
> % As mond sort of pointed out
> already...
> %
> % Could someone please explain to me
> why
> % open source developers are so
> accepting
> % of these closed protocols? Yeah, I
> know
> % I'm biased working on an open one,
> but I
> % just want to know. It's not like
> these
> % protocols are even really good
> protocols
> % or anything - are they just
> accepted
> % &quot;because so many people use
> % them&quot;? You could make the
> exact
> % same argument for any other piece
> of
> % closed source software. There has to
> be
> % a better reason than that - what is
> it?
>
>
> popularity, that is key
>


Windows is more popular than Linux. Word is more popular than all of the open source word processors combined. Yet Open Source fans use Linux even when it would be easier to use windows.

25 Feb 2002 21:34 Avatar capocanno

Re: ICQ/AIM/Yahoo! and other closed protocols

> As mond sort of pointed out already...
>
> Could someone please explain to me why
> open source developers are so accepting
> of these closed protocols? Yeah, I know
> I'm biased working on an open one, but I
> just want to know. It's not like these
> protocols are even really good protocols
> or anything - are they just accepted
> &quot;because so many people use
> them&quot;? You could make the exact
> same argument for any other piece of
> closed source software. There has to be
> a better reason than that - what is it?


popularity, that is key

Screenshot

Project Spotlight

Kigo Video Converter Ultimate for Mac

A tool for converting and editing videos.

Screenshot

Project Spotlight

Kid3

An efficient tagger for MP3, Ogg/Vorbis, and FLAC files.