Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Look, an IRC bot. Almost

git-svn-id: http://dev.brendonh.org/svn/veracity/trunk@5 9d18217e-6996-4b5f-b398-a6959e6ab315
  • Loading branch information...
commit 78bce921f54b06b86b9420615e0ee4eaf51d920a 1 parent eae936a
Brendon Hogger authored
3  veracity/Makefile
View
@@ -39,6 +39,9 @@ $(RELEASE_NAME).boot: $(RELEASE_NAME).rel
run: run_prereqs
$(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
$(ERL_CMD) -detached -sname $(PKG_NAME) -s $(PKG_PREFIX)_app >> $(PKG_NAME).log 2>&1
113 veracity/src/veracity_codes.erl
View
@@ -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 veracity/src/veracity_conn.erl
View
@@ -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).
+
48 veracity/src/veracity_conn_group.erl
View
@@ -0,0 +1,48 @@
+%%%-------------------------------------------------------------------
+%%% File : veracity_conn_group.erl
+%%% Author : Brendon Hogger <brendonh@lightblue>
+%%% Description : Manage processes for one connection
+%%%
+%%% Created : 31 Dec 2008 by Brendon Hogger <brendonh@lightblue>
+%%%-------------------------------------------------------------------
+-module(veracity_conn_group).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/1, get_child/2]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%%====================================================================
+%% API functions
+%%====================================================================
+
+start_link(Opts) ->
+ supervisor:start_link(?MODULE, Opts).
+
+
+%%====================================================================
+%% Supervisor callbacks
+%%====================================================================
+
+init(Opts0) ->
+
+ Opts = [{supervisor, self()}|Opts0],
+
+ Conn = {conn,{veracity_conn,start_link,[Opts]},
+ permanent,2000,worker,[veracity_conn]},
+
+ Users = {users, {veracity_users, start_link, [Opts]},
+ permanent,2000, worker, [veracity_users]},
+
+ {ok,{{one_for_one,1,1}, [Conn, Users]}}.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+get_child(SupRef, ID) ->
+ hd([Child || {CID, Child, _, _} <- supervisor:which_children(SupRef),
+ CID == ID]).
45 veracity/src/veracity_conn_sup.erl
View
@@ -0,0 +1,45 @@
+%%%-------------------------------------------------------------------
+%%% File : veracity_conn_sup.erl
+%%% Author : Brendon Hogger <brendonh@lightblue>
+%%% Description : simple_one_for_one connection supervisor
+%%%
+%%% Created : 29 Dec 2008 by Brendon Hogger <brendonh@lightblue>
+%%%-------------------------------------------------------------------
+-module(veracity_conn_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0, connect/1]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+-define(SERVER, ?MODULE).
+
+%%====================================================================
+%% API functions
+%%====================================================================
+start_link() ->
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+connect(Opts) ->
+ {ok, Pid} = supervisor:start_child(veracity_conn_sup, [Opts]),
+ Conn = veracity_conn_group:get_child(Pid, conn),
+ Users = veracity_conn_group:get_child(Pid, users),
+ {Conn, Users}.
+
+
+%%====================================================================
+%% Supervisor callbacks
+%%====================================================================
+init([]) ->
+
+ GroupSpec = {group ,{veracity_conn_group,start_link,[]},
+ transient,2000,supervisor,[veracity_conn_group]},
+
+ {ok,{{simple_one_for_one,0,1}, [GroupSpec]}}.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
24 veracity/src/veracity_parse.erl
View
@@ -30,19 +30,20 @@ message([$:|Rest]) ->
case prefix(Prefix) of
true ->
case message(tl(Tail)) of
- {ok, {Command, Params}} ->
+ {ok, {none, Command, Params}} ->
{ok, {Prefix, Command, Params}};
- Other -> Other
+ Other ->
+ Other
end;
false ->
- invalid_prefix
+ {invalid_prefix, Prefix}
end;
message(S) ->
{Tail, Command} = getnext(S),
case command(Command) of
true ->
case params(Tail) of
- {ok, Params} -> {ok, {Command, Params}};
+ {ok, Params} -> {ok, {none, Command, Params}};
{error, Error} -> Error
end;
false ->
@@ -286,9 +287,12 @@ servername(S) -> hostname(S).
%% host = hostname / hostaddr
-host(S) -> hostname(S) orelse hostaddr(S).
+host(S) -> hostname(S) orelse hostaddr(S) orelse cloak(S).
+cloak(_S) ->
+ true. % Heh heh
+
%% hostname = shortname *( "." shortname )
hostname([]) -> false;
@@ -506,10 +510,8 @@ tryfuncs(S, Fs) ->
test() ->
- [io:format("~s: ~p~n", [N, message(N)])
- || N <- [
- "1234 world",
- "one two three four five six seven eight nine ten etc etc etc etc etc etc etc etc",
- ":brend@taizilla.dynalias.org Hi there! : tailing arguments"
- ]].
+
+ io:format("Message: ~p~n", [message(
+ ":Brend!n=brendonh@220-131-227-200.HINET-IP.hinet.net PRIVMSG veracity_bot :Also, hi")]).
+
7 veracity/src/veracity_sup.erl
View
@@ -28,7 +28,12 @@ start_link(Args) ->
%% Supervisor callbacks
%%====================================================================
init(_Args) ->
- {ok,{{one_for_one,0,1}, []}}.
+
+ ConnSup = {conn_sup,{veracity_conn_sup,start_link,[]},
+ permanent,2000,supervisor,[veracity_conn_sup]},
+
+
+ {ok,{{one_for_one,0,1}, [ConnSup]}}.
%%====================================================================
76 veracity/src/veracity_users.erl
View
@@ -0,0 +1,76 @@
+%%%-------------------------------------------------------------------
+%%% File : veracity_users.erl
+%%% Author : Brendon Hogger <brendonh@lightblue>
+%%% Description : Track users
+%%%
+%%% Created : 31 Dec 2008 by Brendon Hogger <brendonh@lightblue>
+%%%-------------------------------------------------------------------
+-module(veracity_users).
+
+-behaviour(gen_server).
+
+-include("veracity_util.hrl").
+
+%% API
+-export([start_link/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state, {
+ opts,
+ conn
+}).
+
+%%====================================================================
+%% API
+%%====================================================================
+start_link(Opts) ->
+ gen_server:start_link(?MODULE, Opts, []).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+
+init(Opts) ->
+ gen_server:cast(self(), findconn),
+ {ok, #state{opts=Opts}}.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+
+handle_cast(findconn, State) ->
+ Sup = ?GV(supervisor, State#state.opts),
+ Conn = veracity_conn_group:get_child(Sup, conn),
+
+ Whois = fun(T) ->
+ lists:any(fun(X) -> X == T end,
+ [rpl_whoisuser, rpl_whoisserver,
+ rpl_whoisoperator, rpl_whoisidle,
+ rpl_endofwhois, rpl_whoischannels,
+ rpl_whoislogin])
+ end,
+
+ gen_server:call(Conn, {listen, {Whois, all}}),
+ {noreply, State#state{conn=Conn}};
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(Info, State) ->
+ ?DBG({user_info, Info}),
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
13 veracity/src/veracity_util.hrl
View
@@ -0,0 +1,13 @@
+%%%-------------------------------------------------------------------
+%%% File : veracity_util.hrl
+%%% Author : Brendon Hogger <brendonh@lightblue>
+%%% Description : Frequently used bits and bobs
+%%%
+%%% Created : 29 Dec 2008 by Brendon Hogger <brendonh@lightblue>
+%%%-------------------------------------------------------------------
+
+-define(GV(E, P), proplists:get_value(E, P)).
+-define(GVD(E, P, D), proplists:get_value(E, P, D)).
+
+-define(DBG(Term), io:format("~p: ~p~n", [self(), Term])).
+
45 veracity/src/vtest.erl
View
@@ -0,0 +1,45 @@
+%%%-------------------------------------------------------------------
+%%% File : vtest.erl
+%%% Author : Brendon Hogger <brendonh@lightblue>
+%%% Description : Veracity test sandbox
+%%%
+%%% Created : 29 Dec 2008 by Brendon Hogger <brendonh@lightblue>
+%%%-------------------------------------------------------------------
+-module(vtest).
+
+-export([test/0]).
+
+-include("veracity_util.hrl").
+
+test() ->
+ spawn_link(fun go/0).
+
+
+go() ->
+ {Conn, Users} = veracity_conn_sup:connect([{server, "irc.freenode.net"},
+ {pass, "arthur"},
+ {listeners, []},
+ {loggers, [self()]}]),
+
+ ?DBG({conn, Conn, users, Users}),
+
+ gen_server:call(Conn, {listen_msg, {all, all, "(?i)w"}}),
+
+ spawn(fun() -> go2(Conn) end),
+
+ gen_server:cast(Conn, {send, ["WHOIS brend"]}),
+
+ loop().
+
+loop() ->
+ receive
+ {send, S} -> io:format("~p: SEND: ~s", [self(), S]);
+ {recv, S} -> io:format("~p: RECV: ~s", [self(), S]);
+ {privmsg, Msg} -> io:format("~p: PRIV: ~p~n", [self(), Msg])
+ end,
+ loop().
+
+
+go2(Pid) ->
+ gen_server:call(Pid, {listen_msg, {"^(?i).*b.*!.*", all, all}}),
+ loop().
Please sign in to comment.
Something went wrong with that request. Please try again.