Pull to refresh

XMPP-бот на Java с использованием Smack API

Reading time 5 min
Views 14K
image

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

Articles