
Всем доброго времени суток!
Тема написания ботов для жаббера довольно широко распространена. Но на хабре нашел всего одну статью, в которой бот был написан для сервера 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. Если логин не писать вообще или писать какой-нибудь другой, все работает отлично