Pull to refresh

Работа с ICQ в Java

Reading time8 min
Views7.7K

Предыстория


Как-то раз мы решили создать свой собственный микроблоггинг с блекджеком и шлюхами c шахматами и поэтессами. Сначала сделали сайт, но быстро стало понятно, что для реализации идеи его недостаточно. Необходимо было работать с сообщениями через Jabber и ICQ.
Начали с простого — с джаббера. Никаких проблем тут не возникло — мы писали на 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-транспорта, который, как выяснилось, настраивается относительно несложно.
Исходники тут.

Было бы интересно послушать про аналогичный опыт у вас.
Tags:
Hubs:
+15
Comments16

Articles