Skip to content

Commit

Permalink
Look, an IRC bot. Almost
Browse files Browse the repository at this point in the history
git-svn-id: http://dev.brendonh.org/svn/veracity/trunk@5 9d18217e-6996-4b5f-b398-a6959e6ab315
  • Loading branch information
brendonh committed Dec 30, 2008
1 parent eae936a commit 78bce92
Show file tree
Hide file tree
Showing 10 changed files with 615 additions and 12 deletions.
3 changes: 3 additions & 0 deletions veracity/Makefile
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ $(RELEASE_NAME).boot: $(RELEASE_NAME).rel
run: run_prereqs run: run_prereqs
$(ERL_CMD) -sname $(PKG_NAME) -s $(PKG_PREFIX)_app $(ERL_CMD) -sname $(PKG_NAME) -s $(PKG_PREFIX)_app


make test: run_prereqs
$(ERL_CMD) -sname $(PKG_NAME) -s $(PKG_PREFIX)_app -s vtest test

daemon: run_prereqs daemon: run_prereqs
$(ERL_CMD) -detached -sname $(PKG_NAME) -s $(PKG_PREFIX)_app >> $(PKG_NAME).log 2>&1 $(ERL_CMD) -detached -sname $(PKG_NAME) -s $(PKG_PREFIX)_app >> $(PKG_NAME).log 2>&1


Expand Down
113 changes: 113 additions & 0 deletions veracity/src/veracity_codes.erl
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,113 @@
-module(veracity_codes).

-export([code/1]).

code("PRIVMSG") -> privmsg;
code("NOTICE") -> notice;
code(001) -> rpl_welcome;
code(002) -> rpl_yourhost;
code(003) -> rpl_created;
code(004) -> rpl_myinfo;
code(005) -> rpl_bounce;
code(302) -> rpl_userhost;
code(303) -> rpl_ison;
code(301) -> rpl_away;
code(305) -> rpl_unaway;
code(306) -> rpl_nowaway;
code(311) -> rpl_whoisuser;
code(312) -> rpl_whoisserver;
code(313) -> rpl_whoisoperator;
code(317) -> rpl_whoisidle;
code(318) -> rpl_endofwhois;
code(319) -> rpl_whoischannels;
code(320) -> rpl_whoislogin;
code(314) -> rpl_whowasuser;
code(369) -> rpl_endofwhowas;
code(321) -> rpl_liststart;
code(322) -> rpl_list;
code(323) -> rpl_listend;
code(325) -> rpl_uniqopis;
code(324) -> rpl_channelmodeis;
code(331) -> rpl_notopic;
code(332) -> rpl_topic;
code(341) -> rpl_inviting;
code(342) -> rpl_summoning;
code(346) -> rpl_invitelist;
code(347) -> rpl_endofinvitelist;
code(348) -> rpl_exceptlist;
code(349) -> rpl_endofexceptlist;
code(351) -> rpl_version;
code(352) -> rpl_whoreply;
code(315) -> rpl_endofwho;
code(353) -> rpl_namreply;
code(366) -> rpl_endofnames;
code(364) -> rpl_links;
code(365) -> rpl_endoflinks;
code(367) -> rpl_banlist;
code(368) -> rpl_endofbanlist;
code(371) -> rpl_info;
code(374) -> rpl_endofinfo;
code(375) -> rpl_motdstart;
code(372) -> rpl_motd;
code(376) -> rpl_endofmotd;
code(381) -> rpl_youreoper;
code(382) -> rpl_rehashing;
code(383) -> rpl_youreservice;
code(391) -> rpl_time;
code(392) -> rpl_usersstart;
code(393) -> rpl_users;
code(394) -> rpl_endofusers;
code(395) -> rpl_nousers;
code(200) -> rpl_tracelink;
code(201) -> rpl_traceconnecting;
code(202) -> rpl_tracehandshake;
code(203) -> rpl_traceunknown;
code(204) -> rpl_traceoperator;
code(205) -> rpl_traceuser;
code(206) -> rpl_traceserver;
code(207) -> rpl_traceservice;
code(208) -> rpl_tracenewtype;
code(209) -> rpl_traceclass;
code(210) -> rpl_tracereconnect;
code(261) -> rpl_tracelog;
code(262) -> rpl_traceend;
code(211) -> rpl_statslinkinfo;
code(212) -> rpl_statscommands;
code(219) -> rpl_endofstats;
code(242) -> rpl_statsuptime;
code(243) -> rpl_statsoline;
code(221) -> rpl_umodeis;
code(234) -> rpl_servlist;
code(235) -> rpl_servlistend;
code(251) -> rpl_luserclient;
code(252) -> rpl_luserop;
code(253) -> rpl_luserunknown;
code(254) -> rpl_luserchannels;
code(255) -> rpl_luserme;
code(256) -> rpl_adminme;
code(257) -> rpl_adminloc1;
code(258) -> rpl_adminloc2;
code(259) -> rpl_adminemail;
code(263) -> rpl_tryagain;
code(401) -> err_nosuchnick;
code(402) -> err_nosuchserver;
code(403) -> err_nosuchchannel;
code(404) -> err_cannotsendtochan;
code(405) -> err_toomanychannels;
code(406) -> err_wasnosuchnick;
code(407) -> err_toomanytargets;
code(408) -> err_nosuchservice;
code(409) -> err_noorigin;
code(411) -> err_norecipient;
code(412) -> err_notexttosend;
code(413) -> err_notoplevel;
code(414) -> err_wildtoplevel;
code(415) -> err_badmask;
code(421) -> err_unknowncommand;
code(422) -> err_nomotd;
code(423) -> err_noadmininfo;
code(424) -> err_fileerror;
code(431) -> err_nonicknamegiven;
code(432) -> err_erroneusnickname;
code(433) -> err_nicknameinuse;
code(Other) -> Other.
253 changes: 253 additions & 0 deletions veracity/src/veracity_conn.erl
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,253 @@
%%%-------------------------------------------------------------------
%%% File : veracity_conn.erl
%%% Author : Brendon Hogger <brendonh@lightblue>
%%% Description : IRC server connection
%%%
%%% Created : 29 Dec 2008 by Brendon Hogger <brendonh@lightblue>
%%%-------------------------------------------------------------------
-module(veracity_conn).

-behaviour(gen_server).

-include("veracity_util.hrl").

%% API
-export([start_link/1, space_join/1]).

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

-record(state, {
opts,
socket,
listeners,
loggers,
ready
}).

-define(NEXT(S), inet:setopts(S#state.socket, [{active, once}])).


%%====================================================================
%% API
%%====================================================================

start_link(Opts) ->
gen_server:start_link(?MODULE, Opts, []).


%%====================================================================
%% gen_server callbacks
%%====================================================================

init(Opts) ->

{ok, Sock} = gen_tcp:connect(
?GVD(server, Opts, -1), %% Crash on no server
?GVD(port, Opts, 6667),
[{active, once},
{packet, line},
{reuseaddr, true}]),

?DBG({conn, Sock}),

State = #state{opts=Opts,
socket=Sock,
listeners=[],
loggers=?GVD(loggers, Opts, []),
ready=false},

send_connect_messages(State),

{ok, State}.



handle_call({listen_msg, {Prefix, Target, Msg}}, {Pid, _Ref}, State) ->
?DBG({listen_msg, {Prefix, Target, Msg}}),
{NewState, Reply} = add_filter(State, Pid, [privmsg, Prefix, Target, Msg]),
{reply, Reply, NewState};

handle_call({listen, {Type, Prefix}}, {Pid, _Ref}, State) ->
?DBG({listen, {Type, Prefix}}),
{NewState, Reply} = add_filter(State, Pid, [Type, Prefix]),
{reply, Reply, NewState};

handle_call(Request, _From, State) ->
?DBG({unknown_call, Request}),
{reply, ok, State}.


handle_cast({send, Args}, State) ->
send(State, Args),
{noreply, State};

handle_cast(Msg, State) ->
?DBG({unknown_cast, Msg}),
{noreply, State}.



handle_info({tcp_closed, _Sock}, State) ->
logcast(State, recv, disconnected),
%broadcast(State, disconnected),

Reason = case ?GVD(reconnect, State#state.opts, false) of
true -> disconnected;
false -> normal
end,

{stop, Reason, State};

handle_info({tcp, _Sock, Message}, State) ->

logcast(State, recv, Message),

%?DBG({msg, Message}),
%?DBG({parse, (catch veracity_parse:message(chomp(Message)))}),

case (catch veracity_parse:message(chomp(Message))) of
{ok, {Prefix, RawCommand, Params}} ->
Command = translate(RawCommand),
NewState = handle(State, {Prefix, Command, Params});
Other ->
?DBG({parse_error, Message, Other}),
NewState = State
end,

?NEXT(NewState),
{noreply, NewState};

handle_info(Info, State) ->
?DBG({unknown_info, Info}),
?NEXT(State),
{noreply, State}.



terminate(_Reason, _State) ->
?DBG(terminating),
ok.



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



%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------

add_filter(State, Pid, FilterSpec) ->
Listeners = State#state.listeners,
Filter = [make_filter(X) || X <- FilterSpec],

case lists:any(fun(X) -> X == error end, Filter) of
false ->
{State#state{listeners=[{Filter, Pid}|Listeners]}, ok};
true -> {State, parse_error}
end.


make_filter(all) -> fun(_) -> true end;
make_filter({S, O}) ->
case re:compile(S, O) of
{ok, R} -> fun(M) -> re:run(M, R) /= nomatch end;
{error, _} -> error
end;
make_filter(A) when is_atom(A) ->
fun(X) -> X == A end;
make_filter(F) when is_function(F) ->
F;
make_filter(S) ->
case re:compile(S) of
{ok, R} -> fun(M) -> re:run(M, R) /= nomatch end;
{error, _} -> error
end.


translate([_,_,_]=Maybe3Digit) ->
Parsed = case veracity_parse:digit(Maybe3Digit) of
true -> list_to_integer(Maybe3Digit);
false -> Maybe3Digit
end,
veracity_codes:code(Parsed);
translate(Other) -> veracity_codes:code(Other).


handle(State, {_, rpl_welcome, _}) ->
State#state{ready=true};

handle(State, {Prefix, Type, [Target,Msg]}) ->
Message = [Type, Prefix, Target, Msg],
Matches = lists:usort([Listener || {Spec, Listener} <- State#state.listeners,
listen_match(Spec, Message)]),
[M ! {Type, {Prefix, Target, Msg}} || M <- Matches, M /= none],
State;

handle(State, {Prefix, Type, Args}) ->
Message = [Type, Prefix],
Matches = lists:usort([Listener || {Spec, Listener} <- State#state.listeners,
listen_match(Spec, Message)]),
[M ! {Type, {Prefix, Args}} || M <- Matches, M /= none],
State;

handle(State, Other) ->
?DBG({ignoring, Other}),
State.


send_connect_messages(State) ->
Opts = State#state.opts,

case ?GV(pass, Opts) of
undefined -> ok;
Pass -> send(State, ["PASS ", Pass])
end,

Nick = ?GVD(nick, Opts, "verac"),
send(State, ["NICK ", Nick]),

Username = ?GVD(username, Opts, "veracity"),
Realname = ?GVD(realname, Opts, "veracity"),

Wallops = ?GVD(wallops, Opts, false),
Invisible = ?GVD(invisible, Opts, false),

Mask = case {Wallops, Invisible} of
{false, false} -> "0";
{true, false} -> "4";
{false, true} -> "8";
{true, true} -> "12"
end,

UserCmd = space_join(["USER", Username, Mask, "*", ":" ++ Realname]),

send(State, UserCmd).

space_join(Stuff) ->
Prefixed = lists:foldl(fun(E, A) -> [" ",E|A] end, [], Stuff),
lists:reverse(tl(Prefixed)).

send(State, Bits) ->
Msg = lists:concat(Bits ++ ["\r\n"]),
logcast(State, send, Msg),
gen_tcp:send(State#state.socket, Msg).


listen_match(Filter, Msg) when length(Filter) == length(Msg) ->
lists:all(fun({F,M}) -> F(M) end,
lists:zip(Filter, Msg));
listen_match(_Filter, _Msg) -> false.


logcast(State, Type, Term) ->
[L ! {Type, Term} || L <- State#state.loggers].


chomp(S) ->
string:strip(string:strip(S, right, $\n), right, $\r).

Loading

0 comments on commit 78bce92

Please sign in to comment.