From 8f22cebaafe001c1cbf573a34c07efd288008f02 Mon Sep 17 00:00:00 2001 From: Matt Mullins Date: Thu, 19 May 2011 15:15:10 -0500 Subject: [PATCH] Finished most of the IRC protocol-level work: * added module irc_util to hold the parser and writer for IRC commands * irc_conn module should at least be able to join channels --- irc/irc_conn.erl | 39 ++++++++++++++++++++++------ irc/irc_net_sup.erl | 2 +- irc/irc_util.erl | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ irc/irc_util.hrl | 6 +++++ 4 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 irc/irc_util.erl create mode 100644 irc/irc_util.hrl diff --git a/irc/irc_conn.erl b/irc/irc_conn.erl index 31d1037..3af4399 100644 --- a/irc/irc_conn.erl +++ b/irc/irc_conn.erl @@ -2,7 +2,7 @@ -behavior(gen_server). -export([ - start_link/5 + start_link/2 ]). -export([ @@ -14,24 +14,40 @@ 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}. @@ -41,3 +57,10 @@ terminate(_Reason, _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). diff --git a/irc/irc_net_sup.erl b/irc/irc_net_sup.erl index 3d26ad6..cd4be7a 100644 --- a/irc/irc_net_sup.erl +++ b/irc/irc_net_sup.erl @@ -7,7 +7,7 @@ -behavior(supervisor). -export([ - start_link/4, + start_link/2 ]). -export([init/1]). diff --git a/irc/irc_util.erl b/irc/irc_util.erl new file mode 100644 index 0000000..e2de915 --- /dev/null +++ b/irc/irc_util.erl @@ -0,0 +1,75 @@ +%% @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. diff --git a/irc/irc_util.hrl b/irc/irc_util.hrl new file mode 100644 index 0000000..cfa5b44 --- /dev/null +++ b/irc/irc_util.hrl @@ -0,0 +1,6 @@ +-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 + }). -- 2.11.0