Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

make it work on IRC

  • Loading branch information...
commit 2693263f4b14e3a4a8ff5c1f84cdbb3a2b733ba5 1 parent c7659a6
@etrepum authored
View
3  rebar.config
@@ -3,6 +3,3 @@
{cover_enabled, true}.
{eunit_opts, [verbose, {report, {eunit_surefire,[{dir,"."}]}}]}.
{clean_files, ["*.eunit", "ebin/*.beam"]}.
-%%{deps,
-%% [{lets, ".*",
-%% {git, "git://github.com/norton/lets.git", {branch, "master"}}}]}.
View
3  src/markov.app.src
@@ -6,4 +6,5 @@
{applications, [kernel,
stdlib]},
{mod, {markov_app, []}},
- {env, []}]}.
+ {env, [{nick, <<"antihector">>},
+ {channels, [<<"#mochialumni">>]}]}]}.
View
32 src/markov_server.erl
@@ -2,7 +2,7 @@
-behaviour(gen_server).
-export([input/1, output/1, output/0]).
--export([start_link/0, start_link/1, start/1]).
+-export([start/0, start_link/0, start_link/1]).
-export([checkpoint_async/2]).
-export([write_count/0]).
@@ -32,16 +32,18 @@ write_count() ->
gen_server:call(?MODULE, write_count).
start_link() ->
- Args = [{K, V} || K <- [module, etf, opts, log_name, log_file,
- checkpoint_writes],
- {ok, V} <- [application:get_env(?APP, K)]],
- gen_server:start_link({local, ?MODULE}, ?MODULE, Args, []).
+ start_link([]).
-start_link(Args) ->
- gen_server:start_link(?MODULE, Args, []).
+app_args() ->
+ [{K, V} || K <- [module, etf, opts, log_name, log_file,
+ checkpoint_writes],
+ {ok, V} <- [application:get_env(?APP, K)]].
-start(Args) ->
- gen_server:start(?MODULE, Args, []).
+start_link([]) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, app_args(), []).
+
+start() ->
+ gen_server:start({local, ?MODULE}, ?MODULE, app_args(), []).
init(Args) ->
NewArgs = [proplists:get_value(module, Args, markov_cstack),
@@ -84,7 +86,7 @@ handle_info({'DOWN', MRef, _Type, _Obj, _Info},
State=#state{checkpoint_monitor=MRef,
writes=W,
log_file=LogFile}) ->
- error_logger:info_msg("~s: Checkpoint of ~p finished",
+ error_logger:info_msg("~s: Checkpoint of ~p finished~n",
[?MODULE, LogFile]),
{noreply,
maybe_checkpoint_log(
@@ -132,14 +134,14 @@ read_any(ETF, S=#state{log=Log, log_file=LogFile}) ->
end,
fun (SAcc) -> read_etf(ETF, SAcc) end,
fun ({S0, Acc}) ->
- error_logger:info_msg("~s: Starting a new log from scratch",
+ error_logger:info_msg("~s: Starting a new log from scratch~n",
[?MODULE]),
{checkpoint_log(from_list([], S0)), Acc} end],
{S, []}).
read_log(Log, SAcc) ->
{_K, LogFile} = lists:keyfind(file, 1, disk_log:info(Log)),
- error_logger:info_msg("~s: Reading log ~p", [?MODULE, LogFile]),
+ error_logger:info_msg("~s: Reading log ~p~n", [?MODULE, LogFile]),
read_chunk(Log, disk_log:chunk(Log, start), SAcc).
try_read([F | Rest], SAcc) ->
@@ -148,7 +150,7 @@ try_read([F | Rest], SAcc) ->
try_read(Rest, SAcc1);
{S0, Acc} ->
{S1, []} = lists:foldl(fun chunk_fold/2, {S0, []}, Acc),
- error_logger:info_msg("~s: Initialized", [?MODULE]),
+ error_logger:info_msg("~s: Initialized~n", [?MODULE]),
S1
end.
@@ -165,7 +167,7 @@ read_chunk(_Log, eof, SAcc) ->
read_etf(undefined, SAcc) ->
SAcc;
read_etf(FName, {S, Acc}) ->
- error_logger:info_msg("~s: Reading ETF ~p", [?MODULE, FName]),
+ error_logger:info_msg("~s: Reading ETF ~p~n", [?MODULE, FName]),
{ok, B} = file:read_file(FName),
{checkpoint_log(from_list(binary_to_term(B), S)), Acc}.
@@ -191,7 +193,7 @@ maybe_checkpoint_log(S) ->
checkpoint_log(S=#state{log_file=LogFile, log=Log, t=T,
checkpoint_monitor=undefined}) ->
- error_logger:info_msg("~s: Checkpoint of ~p starting",
+ error_logger:info_msg("~s: Checkpoint of ~p starting~n",
[?MODULE, LogFile]),
disk_log:reopen(Log, LogFile ++ ".prev"),
MRef = monitor(process, spawn_link(?MODULE, checkpoint_async, [Log, T])),
View
3  src/markov_sup.erl
@@ -23,5 +23,6 @@ start_link() ->
%% ===================================================================
init([]) ->
- Children = [?CHILD(markov_server, worker)],
+ Children = [?CHILD(markov_server, worker),
+ ?CHILD(min_irc, worker)],
{ok, {{one_for_one, 5, 10}, Children}}.
View
168 src/min_irc.erl
@@ -0,0 +1,168 @@
+%% @doc Minimal IRC client.
+-module(min_irc).
+-behaviour(gen_server).
+
+-define(APP, markov).
+
+-export([code_change/3, handle_call/3, handle_cast/2, handle_info/2,
+ init/1, terminate/2]).
+-export([start_link/0, start_link/1, start/0]).
+
+-record(state, {socket :: gen_tcp:socket(),
+ nick :: binary(),
+ identify :: undefined | binary(),
+ host :: string(),
+ port :: non_neg_integer(),
+ rng_state :: term(),
+ channels :: [binary()],
+ tokenizers :: undefined | [{re:mp(), atom()}],
+ current_nick :: undefined | binary()}).
+
+start_link() ->
+ start_link([]).
+
+start_link([]) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, app_args(), []).
+
+start() ->
+ gen_server:start({local, ?MODULE}, ?MODULE, app_args(), []).
+
+init(Args) ->
+ Host = proplists:get_value(irc_host, Args, "irc.freenode.net"),
+ Port = proplists:get_value(irc_port, Args, 6667),
+ Nick = iolist_to_binary(proplists:get_value(nick, Args, "antihector")),
+ Identify = proplists:get_value(identify, Args),
+ Channels = lists:map(
+ fun iolist_to_binary/1,
+ proplists:get_value(channels, Args, ["#antihector"])),
+ {ok, Sock} = gen_tcp:connect(Host,
+ Port,
+ [{packet, line},
+ {mode, binary},
+ {active, true}]),
+ write(Sock, cmd(["NICK", Nick])),
+ %% <username> <hostname> <servername> <realname>
+ write(Sock, cmd(["USER", Nick, Nick, Nick, Nick])),
+ S = #state{socket=Sock,
+ nick=Nick,
+ identify=Identify,
+ host=Host,
+ port=Port,
+ channels=Channels,
+ tokenizers=undefined,
+ rng_state=now(),
+ current_nick=Nick},
+ {ok, update_tokenizers(S)}.
+
+handle_cast(_Req, State) ->
+ {noreply, State}.
+
+handle_info({tcp, Socket, Data}, State=#state{socket=Socket, tokenizers=T}) ->
+ Msg = tokenize(Data, T),
+ io:format("MSG: ~p~n", [Msg]),
+ handle_msg(Msg, State);
+handle_info({tcp_closed, Socket}, State=#state{socket=Socket}) ->
+ {stop, tcp_closed, State#state{socket=undefined}};
+handle_info({tcp_error, Socket, Reason}, State=#state{socket=Socket}) ->
+ {stop, {tcp_error, Reason}, State#state{socket=undefined}};
+handle_info(_Req, State) ->
+ {noreply, State}.
+
+handle_call(_Req, _From, State) ->
+ {reply, ignored, State}.
+
+terminate(_Reason, #state{socket=undefined}) ->
+ ok;
+terminate(_Reason, #state{socket=Socket}) ->
+ write(Socket, cmd(["QUIT", "Died in a fire"])),
+ ok = gen_tcp:close(Socket),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+write(Sock, Raw) ->
+ io:format("WRITE: ~s", [Raw]),
+ ok = gen_tcp:send(Sock, Raw).
+
+handle_msg({ping, [Server]}, State=#state{socket=Socket}) ->
+ write(Socket, cmd(["PONG", Server])),
+ {noreply, State};
+handle_msg({nick_mode, []}, State=#state{socket=Socket, channels=Channels}) ->
+ lists:foreach(
+ fun (Channel) -> write(Socket, cmd(["JOIN", Channel])) end,
+ Channels),
+ {noreply, State};
+handle_msg({nick_in_use, [Nick]}, State=#state{socket=Socket}) ->
+ NewNick = <<Nick/binary, $_>>,
+ write(Socket, cmd(["NICK", NewNick])),
+ {noreply, State#state{current_nick=NewNick}};
+handle_msg({Type, [From, To, Msg]},
+ State=#state{channels=Channels}) when Type =:= privmsg orelse
+ Type =:= mention ->
+ case lists:member(To, Channels) of
+ true ->
+ msg(Type, From, To, Msg, State);
+ false ->
+ {noreply, State}
+ end;
+handle_msg(_Other, State) ->
+ {noreply, State}.
+
+msg(Type, _From, To, Msg, State=#state{socket=Socket}) ->
+ Chance = chance(Type),
+ markov_server:input(Msg),
+ case uniform(State) of
+ {R, State1} when R < Chance ->
+ write(Socket, cmd(["PRIVMSG", To, markov_server:output(Msg)])),
+ {noreply, State1};
+ {_R, State1} ->
+ {noreply, State1}
+ end.
+
+chance(privmsg) ->
+ 0.1;
+chance(mention) ->
+ 1.0.
+
+uniform(State=#state{rng_state=RNG}) ->
+ {Res, RNG1} = random:uniform_s(RNG),
+ {Res, State#state{rng_state=RNG1}}.
+
+compile([{Template, Token} | Rest]) ->
+ {ok, R} = re:compile([$^, Template, "\r?\n$"]),
+ [{R, Token} | compile(Rest)];
+compile([]) ->
+ [].
+
+update_tokenizers(S) ->
+ S#state{tokenizers=compile(tokenizers(S))}.
+
+tokenize(Data, [{R, Token} | Rest]) ->
+ case re:run(Data, R, [{capture, all_but_first, binary}]) of
+ nomatch ->
+ tokenize(Data, Rest);
+ {match, Groups} ->
+ {Token, Groups}
+ end.
+
+tokenizers(#state{nick=Nick, current_nick=CurrentNick}) ->
+ AnyNick = <<"(?:", Nick/binary, $|, CurrentNick/binary, ")">>,
+ [{<<"PING :(\\S+)">>, ping},
+ {<<":\\S+ 433 (", CurrentNick/binary, ") :.*?">>,
+ nick_in_use},
+ {<<":", CurrentNick/binary, " MODE ", CurrentNick/binary, " :.*?">>,
+ nick_mode},
+ {<<":([^!]+)!\\S+ PRIVMSG (\\S+) :", AnyNick/binary, "[:,] (.*?)">>,
+ mention},
+ {<<":([^!]+)!\\S+ PRIVMSG (\\S+) :(.*?)">>, privmsg},
+ {<<"(.*?)">>, raw}].
+
+cmd([Last]) ->
+ [$:, Last, <<"\r\n">>];
+cmd([S | Rest]) ->
+ [S, $\s | cmd(Rest)].
+
+app_args() ->
+ [{K, V} || K <- [irc_host, irc_port, nick, channels, identify],
+ {ok, V} <- [application:get_env(?APP, K)]].
Please sign in to comment.
Something went wrong with that request. Please try again.