When I first wrote pidgin-cyanchat over a year ago I had no idea what I was doing. Quite honestly, my knowledge of C was whatever I had picked up reading samples or flying back and forth in IRC conversations. I drew heavily from existing plugins (to the libpurple irc-prpl devs: you are pretty much the only reason this thing works).
I’ve fixed bugs here and there, added a few new features… but I wasn’t really happy with the overall design of the plugin. There were a few inherent issues, and as I was using the IRC plugin as my base, these issues are mainly due to differences between CyanChat and IRC:
- CyanChat has only a single room/channel
- CyanChat requires that you connect to the server, and then join the room/login… you cannot have PM conversations without joining the chatroom
- CyanChat has no user auth, no way to verify that a user is who they claim to be
In the existing implementation, I worked around those issues in a variety of ways, some of which weren’t even fully supported by Pidgin.
- I manually add a chatroom entry to the Pidgin buddylist when a CyanChat account is enabled, and manually remove it when the account is disabled. This prevents the user from setting a “friendly name” alias on the chatroom, and prevents it being moved into a group of the user’s choice.
- Essentially, the code is just full of checks to see if we are logged into the room before allowing PMs to be sent. It’s quite possible to type a number of lines to a user, and have them not sent due to not being logged in.
- I copied from the Bonjour plugin here (which as dynamic buddies across a LAN), and removed the add_buddy function to simply not add a buddy. However, Pidgin still gave me an “Add Buddy” option on all chatroom users. There is an existing bug report for this issue, with an expected resolution in Pidgin 3.0.0.
Wanting to do better, I started this week to rewrite libcyanchat. I split it into multiple files to make editing less of a nightmare, and I actually used comments this time :P
My new implementation is quite different than the existing one. The biggest difference is that one account can connect to multiple servers.
I decided to extend things a bit by making each CyanChat server act as a chatroom in Pidgin. So you create a CyanChat Account, and use the “Join a Chat” menu to specify a server, port, and nickname. A chat opens, which can be added to the buddylist the same way as an IRC channel, or a Jabber MUC. It’s a very nice, clean, easy system… until you remember that CyanChat supports PMs.
Now, a bit of background on the CC protocol… users are sent to the client in the following form
[1 digit user level][nickname],[IP address hash]
Although the server is supposed to prevent users with the same name, the user levels and IP hashes make it relatively simple to identify users. Each user is unique… until you add multiple servers. While there are only two official servers, they use the same IP hashing algorithm, whereas 3rd party servers use a different hashing method. The problem here is that if Bob is logged in on both official servers and I’m having a PM conversation with him, it would be impossible for Pidgin to tell which server I was using to chat with him, because the user ids on both servers would be identical.
So I devised a solution to ensure unique usernames… After the IP hash, add @server:port (thanks Jabber!). That would create a full user id of
[nickname],[IP address hash]@[server name]:[server port]
This works quite nicely for nearly everything. Yes, nearly… because now in a PM conversation with Bob, I get a huge behemoth complicated username sending me chat, rather than simply the nickname. This isn’t a problem in the chatroom, only in PM conversations.
Edit: The user level should not factor in as it would mean messages from ChatServer are spread across two windows due to varying user levels.
So how does on fix that issue? Well, one can’t. Pidgin (libpurple) doesn’t seem to have any way to alias a user who isn’t on the buddylist. So I have no way to simplify the display of Bob’s full id into the simple nick of “Bob”.
I would definitely appreciate feedback on this, especially if someone knows of a workaround for this issue. The biggest problem is that I don’t see any existing protocols which would need to do something like this, and hence I have no examples from which to draw ideas.