Skip to content

Instantly share code, notes, and snippets.

@saa
Created February 17, 2014 08:02
Show Gist options
  • Save saa/9046573 to your computer and use it in GitHub Desktop.
Save saa/9046573 to your computer and use it in GitHub Desktop.

Jabber бот с использованием exmpp

Помня что всё в Erlang делается за 20 минут давайте напишем Jabber бота за этот отрезок времени который сможет заходить в комнаты и отвечать на ping с использованием библиотеки exmpp и применением OTP принципов.

Создаём папку проекта.

➜  projects:  mkdir erlbot && cd erlbot
➜  erlbot:

Создаём папки для зависимостей и самого приложения. В apps/bot будет код нашего приложения, в deps зависимости.

➜  erlbot:  mkdir -p apps/bot deps
➜  erlbot:

Перейдём в папку с будущим приложением и его сгенерируем c помощью rebar

➜  erlbot:  cd apps/bot && rebar create-app appid=bot
==> bot (create-app)
Writing src/bot.app.src
Writing src/bot_app.erl
Writing src/bot_sup.erl

Отредактируем файл apps/bot/src/bot.app.src, добавив зависимости для нашего бота и зарегистрировав его модули.

{application, bot,
 [
  {description, "20 min bot"},
  {vsn, "1"},
  {registered, [bot_app, bot_sup, bot_wrk]},
  {applications, [
                  kernel,
                  stdlib,
                  inets,
                  exmpp
                 ]},
  {mod, { bot_app, []}},
  {env, []}
 ]}. 

Добавляем функцию запуска нашего приложения в apps/bot/src/bot_app.erl и экспортируем её чтобы rebar не ругался при компиляции.

%% Application callbacks
-export([start/0, start/2, stop/1]).

…
start() ->
    application:start(inets),
    application:start(exmpp),
    application:start(bot),
    ok.

start(_StartType, _StartArgs) ->

Редактируем код супервизора для запуска воркера бота.

init([]) ->
    {ok, {{one_for_one, 5, 10}, [?CHILD(bot_wrk, worker)]}}.

Дальше нужно создать сам воркер, с этим поможет любимый rebar. Посмотрим список доступных шалонов.

➜  bot:  rebar list-templates
==> bot (list-templates)
Available templates:
	* simplesrv: priv/templates/simplesrv.template (escript)
	* simplenode: priv/templates/simplenode.template (escript)
	* simplemod: priv/templates/simplemod.template (escript)
	* simplefsm: priv/templates/simplefsm.template (escript)
	* simpleapp: priv/templates/simpleapp.template (escript)
	* ctsuite: priv/templates/ctsuite.template (escript)
	* basicnif: priv/templates/basicnif.template (escript)

Нам нужен первый вариант simpleserv, переходим в папку apps/bot и выполняем команду

➜  bot:  rebar create template=simplesrv srvid=bot_wrk
==> bot (create)
Writing src/bot_wrk.erl

В нём будет находиться основной код приложения. Часть кода за нас уже "написал" rebar, остальную напишем в оставшееся время.

-module(bot_wrk).
-behaviour(gen_server).

%% ------------------------------------------------------------------
%% Settings
%% ------------------------------------------------------------------

-define(SERVER, ?MODULE).
-define(USERNAME, "username").
-define(JSERVER, "jabber.ru").
-define(PASSWORD, "password").

%% ------------------------------------------------------------------
%% Includes
%% ------------------------------------------------------------------

-include_lib("exmpp/include/exmpp_client.hrl").
-include_lib("exmpp/include/exmpp_xml.hrl").
-include_lib("exmpp/include/exmpp_nss.hrl").

%% ------------------------------------------------------------------
%% Records
%% ------------------------------------------------------------------

-record(state, {session, name, rooms=[]}).

%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------

-export([start_link/0, stop/0, join/1, rooms/0]).

%% ------------------------------------------------------------------
%% gen_server Function Exports
%% ------------------------------------------------------------------

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------

start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

stop() ->
    gen_server:call(?MODULE, stop).

join(Room) ->
    gen_server:cast(?MODULE, {join, Room}).

rooms() ->
    gen_server:cast(?MODULE, listrooms).

%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------

init([]) ->
    process_flag(trap_exit, true),
    MySession = exmpp_session:start(),
    MyJID = exmpp_jid:make(?USERNAME, ?JSERVER, random),
    exmpp_session:auth_basic_digest(MySession, MyJID, ?PASSWORD),
    _StreamId = exmpp_session:connect_TCP(MySession, ?JSERVER, 5222),
    exmpp_session:login(MySession),
    {ok, #state{session=MySession}}.

handle_call(stop, _From, State) ->
    exmpp_component:stop(State#state.session),
    {stop, normal, ok, State};

handle_call(_Request, _From, State) ->
    {noreply, ok, State}.

handle_info(#received_packet{} = Packet, State) ->
    spawn_link(fun() -> process_received_packet(State, Packet) end),
    {noreply, State};

handle_info(_Info, State) ->
    {noreply, State}.

handle_cast({join, Room}, State) ->
    exmpp_session:send_packet(State#state.session,
        exmpp_stanza:set_recipient(exmpp_presence:available(), Room ++ "/erlbot")
    ),
    io:format("Joined to ~s~n", [Room]),
    {noreply, State#state{rooms = [State#state.rooms, Room],
                          name = "erlbot:"}};

handle_cast(listrooms, State) ->
    [io:format("Room: ~s~n", [Room]) || Room <- State#state.rooms, Room /= []],
    {noreply, State};

handle_cast(_Msg, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------

process_message("ping" = Message, To, State) ->
    io:format("You received: ~s: ~s~n", [To, Message]),
    case string:tokens(To, "/") of
        [Conf, Nick] ->
            exmpp_session:send_packet(State#state.session,
                exmpp_stanza:set_recipient(exmpp_message:groupchat(
                erlang:list_to_binary(Nick ++ ": pong")),Conf));
        _ ->
            ok
    end;
process_message(_Message, _To, _State) ->
    ok.

process_received_packet(#state{name=Name} = State, #received_packet{packet_type=message, raw_packet=Packet}) ->
    From = exmpp_stanza:get_sender(Packet),
    Message = exmpp_xml:get_cdata_as_list(exmpp_xml:get_element(Packet, 'body')),
    case string:tokens(Message, " ") of
        [Name, Msg] -> process_message(Msg, erlang:binary_to_list(From), State);
        _ ->           io:format("~s: ~s~n", [From, Message])
    end;

process_received_packet(_State, _Packet) ->
    ok.

Переходим в корневую директорию проекта и создаём конфиг для rebar

➜  bot:  cd ../../
➜  erlbot:  vim rebar.config

Вписываем пути и зависимости. exmpp беру не официальную верию, а одну из подпиленных для использовния с rebar

{deps_dir, ["deps"]}.
{sub_dirs, ["rel", "apps/bot"]}.
{erl_opts, [debug_info, failt_on_warning]}.

{deps, [
     {exmpp, ".*", {git, "git://github.com/puzza007/exmpp.git", {branch, "master"}}}
   ]}.

Ну и последние приготовления. Получаем зависимости

➜  erlbot:  rebar get-deps                                                                                   
==> bot (get-deps)
Pulling exmpp from {git,"git://github.com/puzza007/exmpp.git",
                        {branch,"master"}}
Cloning into exmpp...
➜  erlbot:

Компилируем (тут может появиться куча проблем с exmpp. Не лучшее, но 100% работающее решение установить библиотеку из офф.репы в систему и компилировать бота отдельно. В deb-based линуксах возможно поможет установка пакета erlang-dev)

➜  erlbot:  rebar compile
==> exmpp (compile)
Compiled src/server/exmpp_server_tls.erl
Compiled src/server/exmpp_server_session.erl
Compiled src/server/exmpp_server_sasl.erl
…
Compiling c_src/exmpp_xml_libxml2.c
==> bot (compile)
Compiled src/bot_app.erl
Compiled src/bot_sup.erl
Compiled src/bot_wrk.erl

Запускаем

➜  erlbot:  erl -sname bot -pa deps/exmpp/ebin -pa apps/bot/ebin -boot start_sasl -s bot_app start

Заходим в конференцию

(bot@MacBook-Air-Roman)3> bot_wrk:join("[email protected]").
Joined to [email protected]
ok

Можно посмотреть список конференций где сидит бот

(bot@MacBook-Air-Roman)4> bot_wrk:rooms().
Room: [email protected]
ok

Напишем боту из клиента в конфе ping, и он нам ответит.

(bot@MacBook-Air-Roman)5> You received: [email protected]/Chubaka: ping
[email protected]/erlbot: Chubaka: pong

Бот вышел в 130 строк и 20 минут. Удачи. Код можно взять из репозитория

Спасибо за исправления:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment