Предыстория
Как-то раз мы решили создать свой собственный микроблоггинг
Начали с простого — с джаббера. Никаких проблем тут не возникло — мы писали на Java, поэтому прикрутили Smack API и все заработало. Но, как это не печально, но самым популярным IM протоколом все еще является ICQ…
Задача
Итак, задача: залогиниться в ICQ, принимать и отправлять сообщения, понимать X-статусы, работать с кириллицей.
Рассмотрим пути решения.
Пути решения
* icq java library — найти и использовать некий аналог Smack API для ICQ
* PyICQt — использовать личный ICQ транспорт и работать с ним через то же самое Smack API
* забить на ICQ и продолжать использовать православный Jabber — без комментариев)))
icq java library
Методом глубинного гугления по вышеупомянутому запросу "icq java library" удалось найти следующее:
Библиотека — Последнее обновление
exeQ — 2539 дней назад
OpenMIM — 240 дней назад
joscar — < 30 дней назад
Параметр «Последнее обновление» характеризует заброшенность библиотеки. При выборе библиотеки ICQ совершенно необходимо учитывать ее заброшенность. Дело в том, что владельцем ICQ является корпорация AOL. Там работают очень добрые и веселые люди, которые ради лулзов меняют протокол ICQ примерно пару раз в год, из-за чего сторонние клиенты и библиотеки ВНЕЗАПНО перестают работать до тех пор, пока авторы их не починят.
По понятным причинам из перечисленных библиотек пристального внимания заслуживает только joscar, однако на момент написания статьи у него были серьезные проблемы с кириллицей, однако есть надежда, что в ближайшем будущем они будут устранены.
Тем временем мы переходим к следующему способу.
PyICQt
Бытует мнение, что ICQ-транспорт — это зло. И это правда, но есть один нюанс: публичный ICQ-транспорт — это зло.
Проблемы в работе публичных ICQ-транспортов связаны с тем, что сервера ICQ жестко ограничивают количество соединений с одного клиентского IP, поэтому транспорт может нормально обслуживать только очень небольшое число клиентов.
Публичный транспорт нас не устраивает, значит придется поднять свой собственный.
Процедура установки/настройки PyICQt+EJabberd достаточно проста и описана, например, здесь. Нас больше интересует клиентская часть.
Итак, мы подняли jabber-сервер, расположенный по адресу jabber.joyreactor.ru и icq-транспорт на нем с адресом icq.jabber.joyreactor.ru.
Теперь нужно создать пользователя, от имени которого будем подключаться:
ejabberdctl register username jabber.joyreactor.ru password
Далее логинимся от его имени на наш сервер любым jabber-клиентом и добавляем нашему пользователю icq-транспорт с помощью Service Discovery. Все готово! Можно переходить к Java.
Нам потребуется Smack API:
import org.jivesoftware.smack.*;
а также следующая информация:
Roster.setDefaultSubscriptionMode(SubscriptionMode.accept_all);
XMPPConnection icqConnection = new XMPPConnection(ICQ_HOST);
icqConnection.connect();
SASLAuthentication.supportSASLMechanism("PLAIN", 0); // Для подключения к EJabberd
icqConnection.login(ICQ_LOGIN, ICQ_PASS);
Подключаемся:
Roster.setDefaultSubscriptionMode(SubscriptionMode.accept_all);
XMPPConnection icqConnection = new XMPPConnection(ICQ_HOST);
icqConnection.connect();
SASLAuthentication.supportSASLMechanism("PLAIN", 0); // Для подключения к EJabberd
icqConnection.login(ICQ_LOGIN, ICQ_PASS);
ставим обработчики:
RosterListener rosterListener = new RosterListener() {отправляем сообщение:
public void entriesAdded(Collection<String> arg0) {
Roster roster = connection.getRoster();
RosterGroup gr = roster.getGroup("Users");
if(gr == null)
gr = roster.createGroup("Users");
for(String entry : arg0 ) {
try {
String[] groups = {"Users"};
if(roster.getEntry(entry) == null)
roster.createEntry(entry, null, groups);
} catch (XMPPException ex) {
//TODO: Handle exception.
}
}
}
public void entriesUpdated(Collection<String> arg0) {
Roster roster = connection.getRoster();
RosterGroup gr = roster.getGroup("Users");
if(gr == null)
gr = roster.createGroup("Users");
for(String entry : arg0 ) {
try {
String[] groups = {"Users"};
if(roster.getEntry(entry) == null)
roster.createEntry(entry, null, groups);
} catch (XMPPException ex) {
//TODO: Handle exception.
}
}
}
public void entriesDeleted(Collection<String> arg0) { }
public void presenceChanged(Presence packet) {
try {
org.jivesoftware.smack.packet.Presence prs = (org.jivesoftware.smack.packet.Presence) packet;
if(!prs.getFrom().split("@")[1].equalsIgnoreCase(ICQ_TRANSPORT))
//TODO: Process presence.
} catch (Exception ex) {
//TODO: Handle exception.
}
}
};
PacketListener icqListener = new PacketListener() {
public void processPacket(Packet packet) {
try {
org.jivesoftware.smack.packet.Message msg = (org.jivesoftware.smack.packet.Message) packet;
if(msg.getType() != org.jivesoftware.smack.packet.Message.Type.error &&
msg.getFrom().split("@")[1].equalsIgnoreCase(ICQ_TRANSPORT))
//TODO: Process message.
} catch (Exception ex) {
//TODO: Handle exception.
}
}
};
icqConnection.getRoster().addRosterListener(icqRosterListener);
icqConnection.addPacketListener(icqListener, new PacketTypeFilter(org.jivesoftware.smack.packet.Message.class));
org.jivesoftware.smack.packet.Message message = new org.jivesoftware.smack.packet.Message("587641264"+"@"+ICQ_TRANSPORT);
message.setBody("Hello world!!!");
icqConnection.sendPacket(message);
Мораль
Во время решения поставленной задачи столкнулись с множеством проблем, главная из которых — быстрое устаревание протокола ICQ. Обойти смогли только с помощью личного ICQ-транспорта, который, как выяснилось, настраивается относительно несложно.
Исходники тут.
Было бы интересно послушать про аналогичный опыт у вас.