-behavior(gen_server).
-export([
- start_link/5
+ start_link/2
]).
-export([
code_change/3
]).
--record(irc_state, {instance, host, port, nick, socket}).
+-include("irc_util.hrl").
+
+-record(irc_state, {instance, % instance name
+ config, % initial configuration params
+ socket,
+ buffer = "" % for the TCP session
+ }).
start_link(Instance, Config) ->
gen_server:start_link(?MODULE,
{Instance, Config}, []).
-init({Instance, {Host, Port, Nick}}) ->
+init({Instance, Config = {Host, Port, Nick, Realname, Channels}}) ->
{ok, Socket} = gen_tcp:connect(Host, Port, [list]),
- State = #irc_state{
- instance=Instance, host=Host,
- port=Port, nick=Nick, socket=Socket},
+ send_command(#irc_command{command = "NICK", middles = [Nick]}),
+ send_command(#irc_command{command = "USER",
+ middles = ["user", "host", "server"],
+ trailing = Realname
+ }),
+ lists:foreach(fun join_channel/1, Channels),
+ State = #irc_state{instance = Instance,
+ config = Config,
+ socket = Socket
+ },
{ok, State}.
handle_call(_Request, _From, _State) ->
{reply, ok, _State}.
-handle_cast(_Request, _State) ->
- {noreply, _State}.
+handle_cast({send_command, Command}, State) ->
+ gen_tcp:send(State#irc_state.socket,
+ irc_util:command_to_list(Command) ++ "\r\n"
+ ),
+ {noreply, State}.
handle_info(_Message, _State) ->
{noreply, _State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+
+send_command(Command) ->
+ gen_server:cast(self(), {send_command, Command}).
+
+join_channel(Channel) ->
+ Command = #irc_command{command = "JOIN", middles = [Channel]},
+ send_command(Command).
-behavior(supervisor).
-export([
- start_link/4,
+ start_link/2
]).
-export([init/1]).
--- /dev/null
+%% @doc Contains methods to parse and generate messages conforming to
+%% the IRC protocol. Works with the record(s) in irc_util.hrl.
+
+-module(irc_util).
+-export([list_to_command/1, command_to_list/1]).
+
+-include("irc_util.hrl").
+
+%% @doc Parses a string into an irc_command record
+list_to_command(String) ->
+ {Prefix, Body} = parse_prefix(String),
+ {Command, Params} = parse_command(Body),
+ {Middles, Trailing} = parse_params(Params),
+ #irc_command{prefix = Prefix,
+ command = Command,
+ middles = Middles,
+ trailing = Trailing
+ }.
+
+%% @doc Parses and splits off the prefix
+% If it has a : at the beginning, then we have a prefix
+parse_prefix(":" ++ Rest) ->
+ {Prefix, Body} = split_space(Rest),
+ {Prefix, Body};
+
+% Otherwise, no prefix, time for the next guy.
+parse_prefix(Body) ->
+ {none, Body}.
+
+%% @doc Parses and splits off the command portion
+parse_command(Body) ->
+ split_space(Body).
+
+%% @doc Splits the parameters into the middle and trailing sections
+% Base case is when there is nothing left
+parse_params("") ->
+ {[], none}; % no parameters at all
+
+% Or when we find a :, then the rest of the command line is trailing
+parse_params(":" ++ Rest) ->
+ {[], Rest};
+
+% Otherwise, split off a parameter and recurse!
+parse_params(String) ->
+ {Param, Rest} = split_space(String),
+ {Middles, Trailing} = parse_params(Rest),
+ {[Param | Middles], Trailing}.
+
+%% @doc Split a string at the space character
+split_space(String) ->
+ {Left, RHS} = lists:splitwith(fun (Char) -> Char /= $ end, String),
+ % drop any space character that would come with RHS:
+ Right = case RHS of
+ " " ++ Rest -> Rest;
+ Other -> Other
+ end,
+ {Left, Right}.
+
+%% @doc Returns the string representation (i.e. suitable for the IRC protocol)
+%% representing a particular command
+command_to_list(IrcCommand) ->
+ Prefix = case IrcCommand#irc_command.prefix of
+ none -> "";
+ PrefixString -> ":" ++ PrefixString ++ " "
+ end,
+ Command = IrcCommand#irc_command.command,
+ Middles = case IrcCommand#irc_command.middles of
+ [] -> "";
+ MiddleStrings -> " " ++ string:join(MiddleStrings, " ")
+ end,
+ Trailing = case IrcCommand#irc_command.trailing of
+ none -> "";
+ TrailingString -> " :" ++ TrailingString
+ end,
+ Prefix ++ Command ++ Middles ++ Trailing.
--- /dev/null
+-record(irc_command,
+ {prefix = none, % prefix of IRC command
+ command, % command -- the only element required
+ middles = [], % [string] for arguments
+ trailing = none % follows the : at the end of a command
+ }).