I made lots of TODO notes in comments.
--- /dev/null
+{application, erlbot,
+ [{mod, {erlbot, []}}
+ ]}.
--- /dev/null
+-module(erlbot).
+-behavior(application).
+
+-export([
+ start/2,
+ stop/1
+ ]).
+
+% TODO: probably should take in a config file through _Args
+start(_Type, _Args) ->
+ erlbot_sup:start_link().
+
+stop(_State) ->
+ ok.
--- /dev/null
+-module(erlbot_sup).
+-behavior(supervisor).
+
+-export([
+ start_link/0
+ ]).
+-export([
+ init/1
+ ]).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, ok).
+
+init(_Args) ->
+ Children = [child("tamu", "irc.tamu.edu", 6667, "moko|bot")],
+ RestartStrategy = {one_for_one, 0, 1}, % TODO: change to 4 per hour
+ {ok, {RestartStrategy, Children}}.
+
+child(Instance, Host, Port, Nick) ->
+ {{irc, Instance},
+ {irc_sup, start_link, [Instance, Host, Port, Nick]},
+ permanent,
+ brutal_kill,
+ supervisor,
+ [irc_sup]}.
--- /dev/null
+-module(irc_channel_sup).
+-behavior(supervisor).
+
+-export([
+ start_link/1
+ ]).
+
+-export([init/1]).
+
+start_link(Instance) ->
+ supervisor:start_link(?MODULE, Instance).
+
+init(Instance) ->
+ Child = {?MODULE,
+ {irc_channel, start_link, [Instance]},
+ transient,
+ brutal_kill,
+ worker,
+ [irc_channel]},
+ RestartStrategy = {simple_one_for_one, 0, 1},
+ {ok, {RestartStrategy, [Child]}}.
+
--- /dev/null
+-module(irc_conn).
+-behavior(gen_server).
+
+-export([
+ start_link/5
+ ]).
+
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3
+ ]).
+
+-record(irc_state, {sup, instance, host, port, nick}).
+
+start_link(Supervisor, Instance, Host, Port, Nick) ->
+ gen_server:start_link(?MODULE,
+ {Supervisor, Instance, Host, Port, Nick}, []).
+
+init({Supervisor, Instance, Host, Port, Nick}) ->
+ State = #irc_state{
+ sup=Supervisor, instance=Instance, host=Host,
+ port=Port, nick=Nick},
+ {ok, State}.
+
+handle_call(_Request, _From, _State) ->
+ {reply, ok, _State}.
+
+handle_cast(_Request, _State) ->
+ {noreply, _State}.
+
+handle_info(_Message, _State) ->
+ {noreply, _State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
--- /dev/null
+-module(irc_pid_cache).
+
+-export([
+ insert_channel/3,
+ get_channel/2,
+ delete_channel/2,
+ insert_user/3,
+ get_user/2,
+ delete_user/2
+ ]).
+
+insert_channel(Instance, Channel, Pid) ->
+ pid_cache:insert({irc_channel, Instance, Channel}, Pid).
+
+get_channel(Instance, Channel) ->
+ pid_cache:get({irc_channel, Instance, Channel}).
+
+delete_channel(Instance, Channel) ->
+ pid_cache:delete({irc_channel, Instance, Channel}).
+
+insert_user(Instance, User, Pid) ->
+ pid_cache:insert({irc_user, Instance, User}, Pid).
+
+get_user(Instance, User) ->
+ pid_cache:get({irc_user, Instance, User}).
+
+delete_user(Instance, User) ->
+ pid_cache:delete({irc_user, Instance, User}).
--- /dev/null
+-module(irc_sup).
+-behavior(supervisor).
+
+-export([
+ start_link/4,
+ get_channel_sup/1,
+ get_user_sup/1
+ ]).
+-export([init/1]).
+
+start_link(Instance, Host, Port, Nick) ->
+ supervisor:start_link(?MODULE,
+ {Instance, Host, Port, Nick}).
+
+get_channel_sup(Pid) ->
+ get_sup(Pid, channels).
+
+get_user_sup(Pid) ->
+ get_sup(Pid, users).
+
+% TODO: should this crash and burn if the child isn't running?
+% i.e. can this become a race condition with the actual
+% respawning of a process?
+get_sup(Pid, Type) ->
+ case supervisor:start_child(Pid,
+ data_child(Type, ok, []))
+ of
+ {error, {already_started, Child}} ->
+ Child
+ end.
+
+init({Instance, Host, Port, Nick}) ->
+ Connection = {connection,
+ {irc_conn, start_link, [self(), Instance, Host, Port, Nick]},
+ transient, 5, worker, [irc_conn]},
+ Children = [data_child(channels, irc_channel_sup, [Instance]),
+ data_child(users, irc_user_sup, [Instance]),
+ Connection
+ ],
+ RestartStrategy = {one_for_all, 0, 1}, % TODO
+ {ok, {RestartStrategy, Children}}.
+
+data_child(Id, Module, Args) ->
+ {Id,
+ {Module, start_link, Args},
+ permanent,
+ 5,
+ supervisor,
+ [Module]}.
+
--- /dev/null
+% TODO: combine with irc_channel_sup
+
+-module(irc_user_sup).
+-behavior(supervisor).
+
+-export([
+ start_link/1
+ ]).
+
+-export([init/1]).
+
+start_link(Instance) ->
+ supervisor:start_link(?MODULE, Instance).
+
+init(Instance) ->
+ Child = {?MODULE,
+ {irc_user, start_link, [Instance]},
+ transient,
+ brutal_kill,
+ worker,
+ [irc_user]},
+ RestartStrategy = {simple_one_for_one, 0, 1},
+ {ok, {RestartStrategy, [Child]}}.
+
--- /dev/null
+-module(pid_cache).
+
+-export([
+ init/0,
+ insert/2,
+ get/1,
+ delete/1
+ ]).
+
+init() ->
+ ets:new(?MODULE, [set, public]).
+
+insert(Key, Value) ->
+ ets:insert(?MODULE, {Key, Value}).
+
+get(Key) ->
+ case ets:lookup(?MODULE, Key) of
+ [{Key, Pid}] -> Pid
+ end.
+
+delete(Key) ->
+ ets:delete(?MODULE, Key).