Сегодня Суровый челябинский программист хочет поделиться исключительно Just for fun'ом, а именно - рассказом о том, как легко и непринужденно написать Jabber-бота с использованием Bot Framework. Наш бот будет принимать команды вида ~karma %username% и воозвращать соответствующую информацию о хабражителе с указанным именем. Для получения данной информации используется Habra API.
Сначала поговорим о содержательной части задачи. Habra API предоставляет возможность получать с помощью GET-запросов по определенному урлу (заданному в формате http://habrahabr.ru/api/profile/%username%) XML-документ с информацией о пользователе или об ошибке, если, например, такой пользователь не зарегистрирован в системе. Понятно, что хотелось бы большего, в частности - получать информацию о месторасположении пользователя или его интересах, а также иметь возможность вытягивать посты целиком или даже публиковать информацию через API. Надеюсь, что хотя бы часть из этого хаброавторы сделают, пока же мы имеем то, что имеем.
Теперь, что касается Bot framework. Данный фреймворк представлен бандлом org.eclipse.ecf.presence.bot и позволяет писать ботов, работающих с поддерживаемыми ECF Messenger-ами (например, Jabber, Skype, Yahoo) и т.н. ChatRoom-ботов, т.е. роботов для IRC-каналов и/или Jabber-конференций. Включает в себя четыре точки расширения, позволяющие регистрировать роботов и обработчики команд. Рассмотрим их подробнее:
chatRoomRobot - задает бота, работающего с чатом. Данный робот описывается строкой подключения (connectId), фабрикой контейнера (containerFactoryName), паролем (connectPassword), именем (name) и множеством комнат чата (chatRooms), каждая из которых описывается именем (name) и паролем (password).
chatRoomMessageHandler - задает обработчик команды. Обработчик описывается идентификатором робота (chatRoomRobotId), регулярным выраженим - фильтром команды (filterExpression) и классом, реализующим логику обработки команды (class). Обработчик вызывается только в том случае, если команда соответствует заданному регулярному выражению.
imRobot - задает бота, работающего со службами обмена сообщениями (например, с Jabber). Описывается робот так же, как и chatRoomRobot, только у него нет списка комнат.
imMessageHandler - задает обработчик команды. Описывается аналогично chatRoomMessageHandler.
У одного робота может быть несколько обработчиков команд, причем сами обработчики могут быть заданы в различных бандлах.
Помимо точек расширения фреймворк предлагает два приложения: chatRoomRobot и imRobot. Данные приложения при старте получают список соответствующих расширений и осуществляют подключение каждого бота. После чего в них запускается цикл обработки сообщений. Сообщение отправляется на обработку тому хэндлеру, регулярному выражению которого оно удовлетворяет.
Начнем разработку с регистрации расширения для imRobot. Сделать это можно или непосредственно редактируя plugins.xml, или через средства PDE, про которые я недавно писал. В результате файл plugins.xml примет следующий вид:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.ecf.presence.bot.imRobot">
<imRobot
connectId="mybot@mybot.ru"
connectPassword="mypswd"
containerFactoryName="ecf.xmpp.smack"
id="name.samolisov.ecf.bot.habr.habraRobot"
name="HabraRobot">
</imRobot>
</extension>
</plugin>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.ecf.presence.bot.imRobot">
<imRobot
connectId="mybot@mybot.ru"
connectPassword="mypswd"
containerFactoryName="ecf.xmpp.smack"
id="name.samolisov.ecf.bot.habr.habraRobot"
name="HabraRobot">
</imRobot>
</extension>
</plugin>
Теперь нам надо зарегистрировать обработчики команд. Каждый обработчик будем наследовать от базового класса HabrBasicMessageHandler, в котором реализуем вызов парсера Habra API, разбор команды и отправку ответа на запрос. Код класса следующий:
package name.samolisov.ecf.bot.habr.handler;
import java.text.MessageFormat;
import name.samolisov.ecf.bot.habr.fetcher.HabraApiEntity;
import name.samolisov.ecf.bot.habr.fetcher.IHabraApiFetcher;
import name.samolisov.ecf.bot.habr.fetcher.UrlClientHabraApiFetcher;
import name.samolisov.ecf.bot.habr.fetcher.exceptions.GetInfoException;
import name.samolisov.ecf.bot.habr.fetcher.exceptions.UserNotFoundException;
import org.eclipse.ecf.core.IContainer;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.presence.IPresenceContainerAdapter;
import org.eclipse.ecf.presence.bot.IIMBotEntry;
import org.eclipse.ecf.presence.bot.IIMMessageHandler;
import org.eclipse.ecf.presence.im.IChatManager;
public abstract class HabrBasicMessageHandler implements IIMMessageHandler
{
private IChatManager _chat;
protected String makeMessage(String karma, String user, String fmt)
{
return MessageFormat.format(fmt, user, karma);
}
protected void sendData(ID toId, String data)
{
try
{
_chat.getChatMessageSender().sendChatMessage(toId, data);
}
catch (ECFException e)
{
e.printStackTrace();
}
}
protected HabraApiEntity getUserData(String user) throws GetInfoException, UserNotFoundException
{
IHabraApiFetcher fetcher = new UrlClientHabraApiFetcher();
return fetcher.fetch(user);
}
@Override
public void init(IIMBotEntry robot)
{
}
@Override
public void preContainerConnect(IContainer container, ID targetID)
{
IPresenceContainerAdapter adapter = (IPresenceContainerAdapter) container
.getAdapter(IPresenceContainerAdapter.class);
_chat = adapter.getChatManager();
}
}
import java.text.MessageFormat;
import name.samolisov.ecf.bot.habr.fetcher.HabraApiEntity;
import name.samolisov.ecf.bot.habr.fetcher.IHabraApiFetcher;
import name.samolisov.ecf.bot.habr.fetcher.UrlClientHabraApiFetcher;
import name.samolisov.ecf.bot.habr.fetcher.exceptions.GetInfoException;
import name.samolisov.ecf.bot.habr.fetcher.exceptions.UserNotFoundException;
import org.eclipse.ecf.core.IContainer;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.presence.IPresenceContainerAdapter;
import org.eclipse.ecf.presence.bot.IIMBotEntry;
import org.eclipse.ecf.presence.bot.IIMMessageHandler;
import org.eclipse.ecf.presence.im.IChatManager;
public abstract class HabrBasicMessageHandler implements IIMMessageHandler
{
private IChatManager _chat;
protected String makeMessage(String karma, String user, String fmt)
{
return MessageFormat.format(fmt, user, karma);
}
protected void sendData(ID toId, String data)
{
try
{
_chat.getChatMessageSender().sendChatMessage(toId, data);
}
catch (ECFException e)
{
e.printStackTrace();
}
}
protected HabraApiEntity getUserData(String user) throws GetInfoException, UserNotFoundException
{
IHabraApiFetcher fetcher = new UrlClientHabraApiFetcher();
return fetcher.fetch(user);
}
@Override
public void init(IIMBotEntry robot)
{
}
@Override
public void preContainerConnect(IContainer container, ID targetID)
{
IPresenceContainerAdapter adapter = (IPresenceContainerAdapter) container
.getAdapter(IPresenceContainerAdapter.class);
_chat = adapter.getChatManager();
}
}
Каждый обработчик должен реализовывать интерфейс IIMMessageHandler, в котором определены следующие методы:
init - вызывается при инициализации робота. В качестве параметра принимает бин, описывающий бота.
preContainerConnect - вызывается непосредственно перед подключением контейнера к серверу. В качестве параметров получает контейнер и ID точки, к которой осуществляется подключение. В нашем примере данный метод используется для сохранения chatManager'а (часть ECF Presence API), через которого мы в дальнейшем будем отправлять сообщения.
handleIMMessage - реализует логику обработки сообщения.
Для примера рассмотрим реализацию метода handleIMMessage в обработчике команды ~karma %username%:
UserCommandMessage command = new UserCommandMessage(message.getBody());
if (command == null)
return;
if (!COMMAND.equals(command.getCommand()))
return;
System.out.println(command);
try
{
HabraApiEntity entry = getUserData(command.getUser());
sendData(message.getFromID(), makeMessage(entry.getKarma(), command.getUser(), DATA_FOR_USER_FMT));
}
catch (Exception e)
{
sendData(message.getFromID(), e.getMessage());
}
if (command == null)
return;
if (!COMMAND.equals(command.getCommand()))
return;
System.out.println(command);
try
{
HabraApiEntity entry = getUserData(command.getUser());
sendData(message.getFromID(), makeMessage(entry.getKarma(), command.getUser(), DATA_FOR_USER_FMT));
}
catch (Exception e)
{
sendData(message.getFromID(), e.getMessage());
}
Как видно логика довольно проста. Прежде всего мы осуществляем разбор команды в структуру UserCommandMessage, после чего производим верификацию, а действительно ли нас вызвали для той команды, которую мы умеем обрабатывать. Если верификация прошла успешно, то получаем данные о пользователе с помощью Habra API и отсылаем информацию о карме. В случае, если при получении данных произошла ошибка (GetInfoException) или такого пользователя на хабре нет (UserNotFoundException) - уведомляем пользователя об этом трагическом событии.
Запускать бота нужно как Eclipse Application, в качестве параметра Programm to Run нужно выбрать Run an application: org.eclipse.ecf.presence.bot.imRobot. Примерный диалог общения может выглядеть следующим образом:
(21:13:00) samolisov@gmail.com/53B20BFE:
~karma beq
(21:13:06) HabraBot:
karma for user beq is 89.45
(21:13:11) samolisov@gmail.com/53B20BFE:
~power beq
(21:13:15) HabraBot:
habrapower for user beq is 103
(21:13:24) samolisov@gmail.com/53B20BFE:
~rating beq
(21:13:28) HabraBot:
rating position for user beq is 285
В качестве упражнения вы можете попробовать создать свой бандл и реализовать в нем обработчик команды help для нашего бота. Думаю, будет интересно.
Скачать примеры к статье (исходники и конфигурации запуска. Rar, 24 Кб)
Понравилось сообщение - подпишитесь на блог или читайте меня в twitter
2 комментария:
Может глупый вопрос :) Но может ли этот бот работать отдельно от еклипсе, т.е. как отдельное консольное приложение?
Да, будет. Вот здесь я писал, как запускать Equinox без Eclipse:
http://samolisov.blogspot.com/2009/03/equinox.html
Главное - ВСЕ необходимые плагины поместить в /plugins/
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!