
Всем доброго времени суток!
Тема написания ботов для жаббера довольно широко распространена. Но на хабре нашел всего одну статью, в которой бот был написан для сервера OpenFire. И в первом же комментарии написано, что было бы неплохо почитать про написание универсального бота, не привязанного к серверу. Так я и решил написать эту статью. Также расскажу про бота для Google Talk и один нюанс, связанный с этим ботом.
Бот для jabber'а
Как и в вышеуказанной статье, у меня есть OpenFire сервер, поэтому решил использовать их же библиотеку (это не значит, что бот будет работать только с моим сервером). Примеров для реализации бота довольно много, и вряд ли следующий код окажется сильно новым.
public class Main { public static void main(String[] args) { try { String botNick = "nickname"; String botPassword = "password"; String botDomain = "jabber.org"; String botServer = "jabber.org"; int botPort = 5222; JabberBot bot = new JabberBot(botNick, botPassword, botDomain, botServer, botPort); Thread botThread = new Thread(bot); botThread.start(); } catch(Exception e) { System.out.printLn(e.getMessage()); } } } /** * Бот, относящийся к одной учетной записи жабера. * Реализует интерфейс Runnable, так что разных ботов можно будет запускать * в разных потоках, или комбинировать их.<hr> * * Использует библиотеку smack.jar и smackx.jar:<br> * org.jivesoftware.smack<hr> * * @author esin * */ public class JabberBot implements Runnable { private String nick; private String password; private String domain; private String server; private int port; private ConnectionConfiguration connConfig; private XMPPConnection connection; /** * В конструктор должны передаваться данные, необходимые для авторизации на жабер-сервере * @param nick - ник * @param password - пароль * @param domain - домен * @param server - сервер * @param port - порт */ public JabberBot (String nick, String password, String domain, String server, int port) { this.nick = nick; this.password = password; this.domain = domain; this.server = server; this.port = port; } @Override public void run() { connConfig = new ConnectionConfiguration(server, port, domain); connection = new XMPPConnection(connConfig); try { int priority = 10; SASLAuthentication.supportSASLMechanism("PLAIN", 0); connection.connect(); connection.login(nick, password); Presence presence = new Presence(Presence.Type.available); presence.setStatus("статус бота"); connection.sendPacket(presence); presence.setPriority(priority); PacketFilter filter = new AndFilter(new PacketTypeFilter(Message.class)); PacketListener myListener = new PacketListener() { public void processPacket(Packet packet) { if (packet instanceof Message) { Message message = (Message) packet; // обработка входящего сообщения processMessage(message); } } }; connection.addPacketListener(myListener, filter); // раз в минуту просыпаемся и проверяем, что соединение не разорвано while(connection.isConnected()) { Thread.sleep(60000); } } catch (Exception e) { System.out.printLn(e.getMessage()); } } /** * Обработка входящего сообщения<hr> * @param message входящее сообщение */ private void processMessage(Message message) { String messageBody = message.getBody(); String JID = message.getFrom(); // обрабатываем сообщение. можно писать что угодно :) // пока что пусть будет эхо-бот sendMessage(JID, messageBody); } /** * Отправка сообщения пользователю<hr> * @param to JID пользователя, которому надо отправить сообщение<br> * @param message сообщение */ private void sendMessage(String to, String message) { if(!message.equals("")) { ChatManager chatmanager = connection.getChatManager(); Chat newChat = chatmanager.createChat(to, null); try { newChat.sendMessage(message); } catch (XMPPException e) { System.out.printLn(e.getMessage()); } } } }
Вот и все. Для чего я выделил бота в отдельный поток? Понимаю, что плодить потоки — не есть хорошо. Но этот поток после выполнения иниализации бота будет спать практически все время. Он только раз в минуту будет просыпаться для того, чтобы проверить соединение. Да и то это лишнее, но убрать нельзя было, т.к. иначе бот запустится и сразу выключится. Вся обработка входящих сообщений и отправка исходящий осуществляется в потоках, создаваемых библиотекой Smack.
Бот для GoogleTalk
Код этого бота практически ничем не отличается. Есть только пара отличий:
public class Main { public static void main(String[] args) { try { String botNick = "nickname"; String botPassword = "password"; String botDomain = "gmail.com"; // домен и сервер отличны друг от друга String botServer = "talk.google.com"; int botPort = 5222; GoogleTalkBot bot = new GoogleTalkBot(botNick, botPassword, botDomain, botServer, botPort); Thread botThread = new Thread(bot); botThread.start(); } catch(Exception e) { System.out.printLn(e.getMessage()); } } } /** * Бот, относящийся к одной учетной записи гугл тока. * Реализует интерфейс Runnable, так что разных ботов можно будет запускать * в разных потоках, или комбинировать их.<hr> * * Использует библиотеку smack.jar и smackx.jar:<br> * org.jivesoftware.smack<hr> * * @author esin * */ public class GoogleTalkBot implements Runnable { private String nick; private String password; private String domain; private String server; private int port; private ConnectionConfiguration connConfig; private XMPPConnection connection; /** * В конструктор должны передаваться данные, необходимые для авторизации в гугле * @param nick - ник * @param password - пароль * @param domain - домен * @param server - сервер * @param port - порт */ public GoogleTalkBot (String nick, String password, String domain, String server, int port) { this.nick = nick; this.password = password; this.domain = domain; this.server = server; this.port = port; } @Override public void run() { connConfig = new ConnectionConfiguration(server, port, domain); connection = new XMPPConnection(connConfig); try { int priority = 10; SASLAuthentication.supportSASLMechanism("PLAIN", 0); connection.connect(); connection.login(nick + "@" + domain, password); // логин для авторизации нужно обязательно присылать в виде nickname@gmail.com Presence presence = new Presence(Presence.Type.available); presence.setStatus("статус бота"); connection.sendPacket(presence); presence.setPriority(priority); PacketFilter filter = new AndFilter(new PacketTypeFilter(Message.class)); PacketListener myListener = new PacketListener() { public void processPacket(Packet packet) { if (packet instanceof Message) { Message message = (Message) packet; if(message.getType() == Type.chat) // на всякий случай уточним, что нам нужны только сообщения чата { // Process message processMessage(message); } } } }; // Register the listener. connection.addPacketListener(myListener, filter); while(connection.isConnected()) { Thread.sleep(60000); } } catch (Exception e) { System.out.printLn(e.getMessage()); } } /** * Обработка входящего сообщения<hr> * @param message - входящее сообщение */ private void processMessage(Message message) { String messageBody = message.getBody(); String JID = message.getFrom(); sendMessage(JID, messageBody); } /** * Отправка сообщения пользователю<hr> * @param to - JID пользователя, которому надо отправить сообщение<br> * @param message - сообщение */ private void sendMessage(String to, String message) { if(!message.equals("")) { ChatManager chatmanager = connection.getChatManager(); Chat newChat = chatmanager.createChat(to, null); try { newChat.sendMessage(message); } catch (XMPPException e) { System.out.printLn(e.getMessage()); } } } }
В общем, все почти также.
Теперь о нюансе, который связан с ботом для гугл тока (в жабере этого нюанса нет). Я когда тестировал ботов, текст эхо-ответа от них посылал в виде
<кто прислал>: <что прислал>
т.е. в тексте явно указывал идентификатор (JID) того, кто присылал. Жабер работал отлично, без всяких сбоев. Гугл ток же после ровно 10 сообщений переставал присылать ответ. То, что это связано именно с логином в гугле, я выяснил после долгих мучений. Причем он не обязательно должен быть указан как nickname@gmail.com, достаточно будет просто nickname. Если логин не писать вообще или писать какой-нибудь другой, все работает отлично