-module(conn).
-import(lists, [concat/1, keysearch/3]).
-export([start/2, stop/0]).
-export([init/1, terminate/2, handle_event/2, handle_call/2, handle_info/2]).
-export([repl/0, conn/3]).
-behaviour(gen_event).
conn(ClientPid, Host, PortNo) ->
Result = gen_tcp:connect(Host, PortNo, [binary,
{active, true},
{packet, line},
{keepalive, true},
{nodelay, true}]),
case Result of
{ok, Socket} ->
Pid = spawn(fun() -> loop(ClientPid, Socket) end),
gen_tcp:controlling_process(Socket, Pid),
Pid;
{error, Reason} ->
io:format("Error, reason: ~p~n", [Reason]),
error
end.
loop(ClientPid, Socket) ->
receive
{tcp, _Socket, Data} ->
gen_event:notify(?MODULE, {recv, Data}),
loop(ClientPid, Socket);
{tcp_closed, _Socket} ->
io:format("[SOCKET] Connection Closed.~n"),
ok;
{tcp_error, _Socket, Reason} ->
io:format("[SOCKET] Error, reason: ~p~n", [Reason]),
ok;
{send, ClientPid, Data} ->
gen_tcp:send(Socket, Data),
loop(ClientPid, Socket);
{close, ClientPid} ->
gen_tcp:close(Socket),
io:format("[SOCKET] Closing connection...~n"),
ok;
Msg ->
io:format("[SOCKET] Recv: ~p~n", [Msg])
end.
%
% exported functions
%
start(Host, PortNo) ->
gen_event:start({local, ?MODULE}),
gen_event:add_handler(?MODULE, ?MODULE, [{host, Host}, {port, PortNo}]).
stop() ->
gen_event:stop(?MODULE).
%
% helper
%
irc_msg(Args) ->
lists:concat(Args) ++ "\r\n".
%
% gen_event behaviour
%
init(Args) ->
io:format("*** initiating gen_event ***~n"),
{value, {host, Host}} = keysearch(host, 1, Args),
{value, {port, PortNo}} = keysearch(port, 1, Args),
Pid = conn(self(), Host, PortNo),
State = {disconnected, Pid},
{ok, State}.
handle_event({recv, Line}, State) ->
io:format("~s", [binary_to_list(Line)]),
{ok, State};
handle_event({send, Line}, {_, Pid}=State) ->
Pid ! {send, self(), Line ++ "\r\n"},
{ok, State};
handle_event({pass, Password}, {disconnected, Pid}) ->
Pid ! {send, self(), irc_msg(["PASS ", Password])},
{ok, {nick, Pid}};
handle_event({nick, Nickname}, {nick, Pid}) ->
Pid ! {send, self(), irc_msg(["NICK ", Nickname])},
{ok, {user, Pid}};
handle_event({user, Nickname, Realname}, {user, Pid}) ->
Pid ! {send, self(), irc_msg(["USER ", Nickname, " 0 * :", Realname])},
{ok, {connected, Pid}};
handle_event({privmsg, To, Msg}, {connected, Pid}) ->
Pid ! {send, self(), irc_msg(["PRIVMSG ", To, " :", Msg])},
{ok, {connected, Pid}};
handle_event({join, Channel}, {connected, Pid}) ->
Pid ! {send, self(), irc_msg(["JOIN ", Channel])},
{ok, {connected, Pid}};
handle_event(quit, {connected, Pid}) ->
Pid ! {send, self(), irc_msg(["QUIT"])},
{ok, {disconnected, Pid}};
handle_event(Event, State) ->
io:format("*** Error Unknow Event: ~p ***~n", [Event]),
{ok, State}.
handle_call(_Request, State) ->
io:format("~p~n", [State]),
{ok, State, State}.
handle_info(_Info, State) ->
io:format("~p, my pid: ~n", [State]),
{ok, State}.
terminate({stop, Reason}, {_, Pid}) ->
io:format("Stoping: ~p~n", [Reason]),
Pid ! {close, self()},
ok;
terminate(stop, {_, Pid}) ->
io:format("Stoping.. ~n"),
Pid ! {close, self()},
ok.
%
%
%
repl() ->
io:format("*** Registering ***~n"),
gen_event:notify(?MODULE, {pass, "some string"}),
gen_event:notify(?MODULE, {nick, "mynickisdick"}),
gen_event:notify(?MODULE, {user, "mynickisdick", "My Real Name"}),
repl_loop("me:").
repl_loop(Prompt) ->
case io:get_line(Prompt) of
eof ->
ok;
{error, Reason} ->
io:format("error: ~w~n", [Reason]);
Data ->
CleanedData = string:strip(Data, both, $\n),
Op = getops(CleanedData),
case Op of
{join, _Channel} ->
gen_event:notify(?MODULE, Op),
repl_loop(Prompt);
{privmsg, _To, _Msg} ->
gen_event:notify(?MODULE, Op),
repl_loop(Prompt);
{quit} ->
gen_event:notify(?MODULE, quit);
_ ->
io:format("Error, Op was: ~p~n", [Op]),
repl_loop(Prompt)
end
end.
getops(String) ->
AvailOps = [{"/join", join, 1}, {"/quit", quit, 0}, {"/msg", privmsg, 2}],
Msg = string:tokens(String, "\t\n "),
Key = lists:nth(1, Msg),
case keysearch(Key, 1, AvailOps) of
{value, {"/msg", privmsg, 2}} ->
{privmsg, lists:nth(2, Msg),
string:join(lists:nthtail(2, Msg), " ")};
{value, {Key, Op, Len}} ->
list_to_tuple([Op] ++ lists:sublist(Msg, 2, Len));
_ ->
Msg
end.
How to use it?
$ erl
Erlang R13B01 (erts-5.7.2) [source] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]
Eshell V5.7.2 (abort with ^G)
1> c(conn).
./conn.erl:6: Warning: undefined callback function code_change/3 (behaviour 'gen_event')
{ok,conn}
2> conn:start("irc.freenode.org", 6667).
Initiating gen_event...
ok
3> conn:repl().
...
You got "/join #channel", "/msg #channel text to say" or "/msg nick test to say" and of course "/quit".
I saw it came up in reddit and I wanted to mention that this doesn't handle pings automatically, so if you connect to freenode and freenode pings the client, you should pong back else freenode will disconnect you. Anyway maybe some future version...
ReplyDeleteIf you want something easily scriptable then I suggest you try this out: https://github.com/mazenharake/eirc
ReplyDeleteI made it to be easy to create a simple bot. See this file which gives you a simple example of how to create a bot: https://github.com/mazenharake/eirc/blob/master/src/eirc_example_bot.erl
If you have any comments or updates it is more than welcome :)
Did something like that, except also with gtk :) Erlang is nice for that.
ReplyDeleteYou might want to post to erlang-questions mailing list for comments.
ReplyDeleteThank you all your comments! Might post it in erlang-questions!
ReplyDelete