Помня что всё в 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 минут. Удачи. Код можно взять из репозитория
Спасибо за исправления: