diff --git a/lib/exirc/client.ex b/lib/exirc/client.ex index 341fcc0..bc4d017 100644 --- a/lib/exirc/client.ex +++ b/lib/exirc/client.ex @@ -1,403 +1,381 @@ defmodule ExIrc.Client do @moduledoc """ Maintains the state and behaviour for individual IRC client connections """ import Irc.Commands import ExIrc.Logger alias ExIrc.Channels, as: Channels alias ExIrc.Utils, as: Utils # Records defrecord ClientState, - event_handlers = [], - server = 'localhost', - port = 6667, - socket = nil, - nick = '', - pass = '', - user = '', - name = '', - logged_on? = false, - autoping = true, - channel_prefixes = '', - network = '', - user_prefixes = '', - login_time = '', - bot_supervisor = nil, - channels = [], - debug = false + event_handlers: [], + server: 'localhost', + port: 6667, + socket: nil, + nick: '', + pass: '', + user: '', + name: '', + logged_on?: false, + autoping: true, + channel_prefixes: '', + network: '', + user_prefixes: '', + login_time: '', + channels: [], + debug: false defrecord IrcMessage, - server = '', - nick = '', - user = '', - host = '', - ctcp = nil, - cmd = '', - args = [] + server: '', + nick: '', + user: '', + host: '', + ctcp: nil, + cmd: '', + args: [] ################# # Module API ################# def start!(options // []) do start_link(options) end def start_link(options // []) do :gen_server.start_link(__MODULE__, options, []) end - def install_bot(client, botid, module, args) do - :gen_server.call(client, {:install_bot, botid, module, args}, :infinity) - end - - def uninstall_bot(client, botid) do - :gen_server.call(client, {:uninstall_bot, botid}, :infinity) - end - def stop!(client) do :gen_server.call(client, :stop) end def connect!(client, server, port) do :gen_server.call(client, {:connect, server, port}, :infinity) end def logon(client, pass, nick, user, name) do :gen_server.call(client, {:logon, pass, nick, user, name}, :infinity) end def msg(client, type, nick, msg) do :gen_server.call(client, {:msg, type, nick, msg}, :infinity) end def nick(client, new_nick) do :gen_server.call(client, {:nick, new_nick}, :infinity) end def cmd(client, raw_cmd) do :gen_server.call(client, {:cmd, raw_cmd}) end def join(client, channel, key) do :gen_server.call(client, {:join, channel, key}, :infinity) end def part(client, channel) do :gen_server.call(client, {:part, channel}, :infinity) end def quit(client, msg // 'Leaving..') do :gen_server.call(client, {:quit, msg}, :infinity) end def is_logged_on?(client) do :gen_server.call(client, :is_logged_on?) end def channels(client) do :gen_server.call(client, :channels) end def channel_users(client, channel) do :gen_server.call(client, {:channel_users, channel}) end def channel_topic(client, channel) do :gen_server.call(client, {:channel_topic, channel}) end def channel_type(client, channel) do :gen_server.call(client, {:channel_type, channel}) end def channel_has_user?(client, channel, nick) do :gen_server.call(client, {:channel_has_user?, channel, nick}) end def add_handler(client, pid) do :gen_server.call(client, {:add_handler, pid}) end def remove_handler(client, pid) do :gen_server.call(client, {:remove_handler, pid}) end def add_handler_async(client, pid) do :gen_server.cast(client, {:add_handler, pid}) end def remove_handler_async(client, pid) do :gen_server.cast(client, {:remove_handler, pid}) end def state(client) do :gen_server.call(client, :state) end ############### # GenServer API ############### def init(options // []) do autoping = Keyword.get(options, :autoping, true) debug = Keyword.get(options, :debug, false) - bots = Keyword.get(options, :bots, []) - # Start bot supervisor and children - {:ok, botsup} = ExIrc.Bots.start!(bots) # Add event handlers handlers = Keyword.get(options, :event_handlers, []) |> Enum.foldl(&do_add_handler/2) # Return initial state {:ok, ClientState.new( event_handlers: handlers, autoping: autoping, logged_on?: false, debug: debug, - channels: ExIrc.Channels.init(), - bot_supervisor: botsup)} + channels: ExIrc.Channels.init())} end - def handle_call({:install_bot, botid, module, args}, _from, state) do - start_bot(state.bot_supervisor, {botid, module, args}) - {:reply, :ok, state} - end - - def handle_call({:uninstall_bot, botid}, _from, state) do - stop_bot(state.bot_supervisor, botid) - {:reply, :ok, state} - end - def handle_call({:add_handler, pid}, _from, state) do handlers = do_add_handler(pid, state.event_handlers) {:reply, :ok, state.event_handlers(handlers)} end def handle_call({:remove_handler, pid}, _from, state) do handlers = do_remove_handler(pid, state.event_handlers) {:reply, :ok, state.event_handlers(handlers)} end def handle_call(:state, _from, state), do: {:reply, state, state} - def handle_call(:stop, _from, state), do: {:stop, :normal, :ok, state} + def handle_call(:stop, _from, state), do: {:stop, :normal, :ok, state} def handle_call({:connect, server, port}, _from, state) do case :gen_tcp.connect(server, port, [:list, {:packet, :line}]) do {:ok, socket} -> send_event {:connect, server, port}, state - {:reply, :ok, state[server: server, port: port, socket: socket]} + {:reply, :ok, state.server(server).port(port).socket(socket)} error -> {:reply, error, state} end end - def handle_call({:logon, pass, nick, user, name}, _from, state) when not state.logged_on? do - send! state.socket, PASS(pass) - send! state.socket, NICK(nick) - send! state.socket, USER(user, name) + def handle_call({:logon, pass, nick, user, name}, _from, ClientState[logged_on?: false] = state) do + send! state.socket, pass!(pass) + send! state.socket, nick!(nick) + send! state.socket, user!(user, name) send_event({:login, pass, nick, user, name}, state) - {:reply, :ok, state[pass: pass, nick: nick, user: user, name: name]} + {:reply, :ok, state.pass(pass).nick(nick).user(user).name(name)} end - def handle_call(:is_logged_on?, _from, state), do: {:reply, state.is_logged_on?, state} - def handle_call(_, _from, state) when not state.is_logged_on?, do: {:reply, {:error, :not_connected}, state} + def handle_call(:is_logged_on?, _from, state), do: {:reply, state.is_logged_on?, state} + def handle_call(_, _from, ClientState[logged_on?: false] = state), do: {:reply, {:error, :not_connected}, state} def handle_call({:msg, type, nick, msg}, _from, state) do data = case type do - :privmsg -> PRIVMSG(nick, msg) - :notice -> NOTICE(nick, msg) - :ctcp -> NOTICE(nick, CTCP(msg)) + :privmsg -> privmsg!(nick, msg) + :notice -> notice!(nick, msg) + :ctcp -> notice!(nick, ctcp!(msg)) end - :gen_tcp.send(state.socket, data) + send! state.stocket, data {:reply, :ok, state} end - def handle_call({:quit, msg}, _from, state), do: send!(state.socket, QUIT(msg)); {:reply, :ok, state} - def handle_call({:join, channel, key}, _from, state), do: send!(state.socket, JOIN(channel, key)); {:reply, :ok, state} - def handle_call({:part, channel}, _from, state), do: send!(state.socket, PART(channel)); {:reply, :ok, state} - def handle_call({:nick, new_nick}, _from, state), do: send!(state.socket, NICK(new_nick)); {:reply, :ok, state} - def handle_call({:cmd, raw_cmd}, _from, state), do: send!(state.socket, CMD(raw_cmd)); {:reply, :ok, state} + def handle_call({:quit, msg}, _from, state), do: send!(state.socket, quit!(msg)) and {:reply, :ok, state} + def handle_call({:join, channel, key}, _from, state), do: send!(state.socket, join!(channel, key)) and {:reply, :ok, state} + def handle_call({:part, channel}, _from, state), do: send!(state.socket, part!(channel)) and {:reply, :ok, state} + def handle_call({:nick, new_nick}, _from, state), do: send!(state.socket, nick!(new_nick)) and {:reply, :ok, state} + def handle_call({:cmd, raw_cmd}, _from, state), do: send!(state.socket, command!(raw_cmd)) and {:reply, :ok, state} def handle_call(:channels, _from, state), do: {:reply, Channels.channels(state.channels), state} def handle_call({:channel_users, channel}, _from, state), do: {:reply, Channels.channel_users(state.channels, channel), state} def handle_call({:channel_topic, channel}, _from, state), do: {:reply, Channels.channel_topic(state.channels, channel), state} def handle_call({:channel_type, channel}, _from, state), do: {:reply, Channels.channel_type(state.channels, channel), state} def handle_call({:channel_has_user?, channel, nick}, _from, state) do {:reply, Channels.channel_has_user?(state.channels, channel, nick), state} end def handle_cast({:add_handler, pid}, state) do handlers = do_add_handler(pid, state.event_handlers) {:noreply, state.event_handlers(handlers)} end def handle_cast({:remove_handler, pid}, state) do handlers = do_remove_handler(pid, state.event_handlers) {:noreply, state.event_handlers(handlers)} end def handle_info({:tcp_closed, _socket}, state) do notice "Connection closed!" {:noreply, state.channels(Channels.init())} end def handle_info({:tcp_error, socket}, state) do {:stop, {:tcp_error, socket}, state} end def handle_info({:tcp, _, data}, state) do + debug? = state.debug case Utils.parse(data) do IrcMessage[ctcp: true] = msg -> send_event(msg, state) {:noreply, state} IrcMessage[ctcp: false] = msg -> send_event(msg, state) handle_data(msg, state) - IrcMessage[ctcp: :invalid] = msg when state.debug -> + IrcMessage[ctcp: :invalid] = msg when debug? -> send_event(msg, state) {:noreply, state} _ -> {:noreply, state} end end def handle_info({'DOWN', _, _, pid, _}, state) do handlers = do_remove_handler(pid, state.event_handlers) {:noreply, state.event_handlers(handlers)} end def handle_info(_, state) do {:noreply, state} end # Handle termination def terminate(_reason, _state), do: :ok # Handle code changes def code_change(_old, state, _extra), do: {:ok, state} ############### # Data handling ############### # Sucessfully logged in - def handle_data(msg, state) when msg.cmd == @RPL_WELCOME and not state.logged_on? do - {:noreply, state[logged_on?: true, login_time: :erlang.now()]} + def handle_data(IrcMessage[cmd: @rpl_WELCOME] = _msg, ClientState[logged_on?: false] = state) do + {:noreply, state.logged_on?(true).login_time(:erlang.now())} end # Server capabilities - def handle_data(msg, state) when msg.cmd == @RPL_ISUPPORT do + def handle_data(IrcMessage[cmd: @rpl_ISUPPORT] = msg, state) do {:noreply, Utils.isup(msg.args, state)} end # Client entered a channel def handle_data(IrcMessage[nick: nick, cmd: 'JOIN'] = msg, ClientState[nick: nick] = state) do channels = Channels.join(state.channels, Enum.first(msg.args)) {:noreply, state.channels(channels)} end # Someone joined the client's channel def handle_data(IrcMessage[nick: user_nick, cmd: 'JOIN'] = msg, state) do channels = Channels.user_join(state.channels, Enum.first(msg.args), user_nick) {:noreply, state.channels(channels)} end # Topic message on join # 3 arguments is not RFC compliant but _very_ common # 2 arguments is RFC compliant - def handle_data(IrcMessage[cmd: @RPL_TOPIC] = msg, state) do + def handle_data(IrcMessage[cmd: @rpl_TOPIC] = msg, state) do {channel, topic} = case msg.args do [_nick, channel, topic] -> {channel, topic} [channel, topic] -> {channel, topic} end channels = Channels.set_topic(state.channels, channel, topic) {:noreply, state.channels(channels)} end # Topic message while in channel def handle_data(IrcMessage[cmd: 'TOPIC', args: [channel, topic]], state) do channels = Channels.set_topic(state.channels, channel, topic) {:noreply, state.channels(channels)} end # NAMES reply - def handle_data(IrcMessage[cmd: @RPL_NAMREPLY] = msg, state) do + def handle_data(IrcMessage[cmd: @rpl_NAMREPLY] = msg, state) do {channel_type, channel, names} = case msg.args do [_nick, channel_type, channel, names] -> {channel_type, channel, names} [channel_type, channel, names] -> {channel_type, channel, names} end channels = Channels.set_type( Channels.users_join(state.channels, channel, String.split(names, ' '), channel, channel_type)) {:noreply, state.channels(channels)} end # We successfully changed name def handle_data(IrcMessage[cmd: 'NICK', nick: nick, args: [new_nick]], ClientState[nick: nick] = state) do {:noreply, state.nick(new_nick)} end # Someone we know (or can see) changed name def handle_data(IrcMessage[cmd: 'NICK', nick: nick, args: [new_nick]], state) do channels = Channels.user_rename(state.channels, nick, new_nick) {:noreply, state.channels(channels)} end # We left a channel def handle_data(IrcMessage[cmd: 'PART', nick: nick] = msg, ClientState[nick: nick] = state) do channels = Channels.part(state.channels, Enum.first(msg.args)) {:noreply, state.channels(channels)} end # Someone left a channel we are in def handle_data(IrcMessage[cmd: 'PART', nick: user_nick] = msg, state) do channels = Channels.user_part(state.channels, Enum.first(msg.args), user_nick) {:noreply, state.channels(channels)} end # We got a ping, reply if autoping is on. def handle_data(IrcMessage[cmd: 'PING'] = msg, ClientState[autoping: true] = state) do case msg do - IrcMessage[args: [from]] -> send!(state.socket, PONG2(state.nick, from)) - _ -> send!(state.socket, PONG1(state.nick)) - end, + IrcMessage[args: [from]] -> send!(state.socket, pong2!(state.nick, from)) + _ -> send!(state.socket, pong1!(state.nick)) + end {:noreply, state}; end # "catch-all" (probably should remove this) def handle_data(_msg, state) do {:noreply, state} end ############### # Internal API ############### def send_event(msg, ClientState[event_handlers: handlers]) when is_list(handlers) do Enum.each(handlers, fn({pid, _}) -> pid <- msg end) end - def gv(key, options) -> :proplists.get_value(key, options) - def gv(key, options, default) -> :proplists.get_value(key, options, default) + def gv(key, options), do: :proplists.get_value(key, options) + def gv(key, options, default), do: :proplists.get_value(key, options, default) def do_add_handler(pid, handlers) do case Process.alive?(pid) and not Enum.member(handlers, pid) do true -> ref = Process.monitor(pid) [{pid, ref} | handlers] false -> handlers end end def do_remove_handler(pid, handlers) do case List.keyfind(handlers, pid, 1) do {pid, ref} -> Process.demonitor(ref) List.keydelete(handlers, pid, 1) false -> handlers end end end \ No newline at end of file diff --git a/lib/exirc/commands.ex b/lib/exirc/commands.ex index 5566ac7..3f78d4d 100644 --- a/lib/exirc/commands.ex +++ b/lib/exirc/commands.ex @@ -1,87 +1,87 @@ defmodule Irc.Commands do # Helpers - @CRLF '\r\n' - defmacro CMD(cmd) do - quote do: [unquote(cmd), @CRLF] + @crlf '\r\n' + defmacro command!(cmd) do + quote do: [unquote(cmd), @crlf] end - defmacro CTCP(cmd) do + defmacro ctcp!(cmd) do quote do: [1, unquote(cmd), 1] end defmacro send!(socket, data) do quote do: :gen_tcp.send(unquote(socket), unquote(data)) end # IRC Commands - defmacro PASS(pwd) do - quote do: CMD(['PASS ', unquote(pwd)]) + defmacro pass!(pwd) do + quote do: command! ['PASS ', unquote(pwd)] end - defmacro NICK(nick) do - quote do: CMD ['NICK ', unquote(nick)] + defmacro nick!(nick) do + quote do: command! ['NICK ', unquote(nick)] end - defmacro USER(user, name) do - quote do: CMD ['USER ', unquote(user), ' 0 * :', unquote(name)] + defmacro user!(user, name) do + quote do: command! ['USER ', unquote(user), ' 0 * :', unquote(name)] end - defmacro PONG1(nick) do - quote do: CMD ['PONG ', unquote(nick)] + defmacro pong1!(nick) do + quote do: command! ['PONG ', unquote(nick)] end - defmacro PONG2(nick, to) do - quote do: CMD ['PONG ', unquote(nick), ' ', unquote(to)] + defmacro pong2!(nick, to) do + quote do: command! ['PONG ', unquote(nick), ' ', unquote(to)] end - defmacro PRIVMSG(nick, msg) do - quote do: CMD ['PRIVMSG ', unquote(nick), ' :', unquote(msg)] + defmacro privmsg!(nick, msg) do + quote do: command! ['PRIVMSG ', unquote(nick), ' :', unquote(msg)] end - defmacro NOTICE(nick, msg) do - quote do: CMD ['NOTICE ', unquote(nick), ' :', unquote(msg)] + defmacro notice!(nick, msg) do + quote do: command! ['NOTICE ', unquote(nick), ' :', unquote(msg)] end - defmacro JOIN(channel, key) do - quote do: CMD ['JOIN ', unquote(channel), ' ', unquote(key)] + defmacro join!(channel, key) do + quote do: command! ['JOIN ', unquote(channel), ' ', unquote(key)] end - defmacro PART(channel) do - quote do: CMD ['PART ', unquote(channel)] + defmacro part!(channel) do + quote do: command! ['PART ', unquote(channel)] end - defmacro QUIT(msg // 'Leaving') do - quote do: CMD ['QUITE :', unquote(msg)] + defmacro quit!(msg // 'Leaving') do + quote do: command! ['QUITE :', unquote(msg)] end #################### # IRC Numeric Codes #################### - @RPL_WELCOME '001' - @RPL_YOURHOST '002' - @RPL_CREATED '003' - @RPL_MYINFO '004' - # @RPL_BOUNCE '005' # RFC2812 - @RPL_ISUPPORT '005' # Defacto standard for server support - @RPL_BOUNCE '010' # Defacto replacement of '005' in RFC2812 - @RPL_STATSDLINE '250' - @RPL_LUSERCLIENT '251' - @RPL_LUSEROP '252' - @RPL_LUSERUNKNOWN '253' - @RPL_LUSERCHANNELS '254' - @RPL_LUSERME '255' - @RPL_LOCALUSERS '265' - @RPL_GLOBALUSERS '266' - @RPL_TOPIC '332' - @RPL_NAMREPLY '353' - @RPL_ENDOFNAMES '366' - @RPL_MOTD '372' - @RPL_MOTDSTART '375' - @RPL_ENDOFMOTD '376' + @rpl_WELCOME '001' + @rpl_YOURHOST '002' + @rpl_CREATED '003' + @rpl_MYINFO '004' + # @rpl_BOUNCE '005' # RFC2812 + @rpl_ISUPPORT '005' # Defacto standard for server support + @rpl_BOUNCE '010' # Defacto replacement of '005' in RFC2812 + @rpl_STATSDLINE '250' + @rpl_LUSERCLIENT '251' + @rpl_LUSEROP '252' + @rpl_LUSERUNKNOWN '253' + @rpl_LUSERCHANNELS '254' + @rpl_LUSERME '255' + @rpl_LOCALUSERS '265' + @rpl_GLOBALUSERS '266' + @rpl_TOPIC '332' + @rpl_NAMREPLY '353' + @rpl_ENDOFNAMES '366' + @rpl_MOTD '372' + @rpl_MOTDSTART '375' + @rpl_ENDOFMOTD '376' # Error Codes - @ERR_NONICKNAMEGIVEN '431' - @ERR_ERRONEUSNICKNAME '432' - @ERR_NICKNAMEINUSE '433' - @ERR_NICKCOLLISION '436' - @ERR_UNAVAILRESOURCE '437' - @ERR_NEEDMOREPARAMS '461' - @ERR_ALREADYREGISTRED '462' - @ERR_RESTRICTED '484' + @err_NONICKNAMEGIVEN '431' + @err_ERRONEUSNICKNAME '432' + @err_NICKNAMEINUSE '433' + @err_NICKCOLLISION '436' + @err_UNAVAILRESOURCE '437' + @err_NEEDMOREPARAMS '461' + @err_ALREADYREGISTRED '462' + @err_RESTRICTED '484' # Code groups - @LOGON_ERRORS [@ERR_NONICKNAMEGIVEN, @ERR_ERRONEUSNICKNAME, - @ERR_NICKNAMEINUSE, @ERR_NICKCOLLISION, - @ERR_UNAVAILRESOURCE, @ERR_NEEDMOREPARAMS, - @ERR_ALREADYREGISTRED, @ERR_RESTRICTED] + @logon_errors [@err_NONICKNAMEGIVEN, @err_ERRONEUSNICKNAME, + @err_NICKNAMEINUSE, @err_NICKCOLLISION, + @err_UNAVAILRESOURCE, @err_NEEDMOREPARAMS, + @err_ALREADYREGISTRED, @err_RESTRICTED] end \ No newline at end of file diff --git a/lib/exirc/utils.ex b/lib/exirc/utils.ex index 6ffba7c..9aebbe0 100644 --- a/lib/exirc/utils.ex +++ b/lib/exirc/utils.ex @@ -1,118 +1,117 @@ defmodule ExIrc.Utils do alias ExIrc.Client.IrcMessage, as: IrcMessage @doc """ Parse IRC message data """ def parse(raw_data) do data = String.slice(raw_data, 1, String.length(raw_data) - 2) case data do <<":", _ :: binary>> -> [<<":", from :: binary>>, rest] = String.split(data, " ") - get_cmd rest, parse_from(from, IrcMessage.new(ctcp: false) + get_cmd rest, parse_from(from, IrcMessage.new(ctcp: false)) data -> get_cmd String.split(data, " "), IrcMessage.new(ctcp: false) - end. + end end def parse_from(from, msg) do case Regex.split(%r/(!|@|\.)/, from) do [nick, "!", user, "@", host | host_rest] -> - IrcMessage.new(nick: nick, user: user, host: host <> host_rest) + msg.nick(nick).user(user).host(host ++ host_rest) [nick, "@", host | host_rest] -> - IrcMessage.new(nick: nick, host: host <> host_rest) + msg.nick(nick).host(host ++ host_rest) [_, "." | _] -> # from is probably a server name - IrcMessage.new(server: from) + msg.server(from) [nick] -> - IrcMessage.new(nick: nick) + msg.nick(nick) end end def get_cmd([cmd, arg1, [':', 1 | ctcp_trail] | rest], msg) when cmd == 'PRIVMSG' or cmd == 'NOTICE' do get_cmd([cmd, arg1, [1 | ctcp_trail] | rest], msg) end def get_cmd([cmd, _arg1, [1 | ctcp_trail] | rest], msg) when cmd == 'PRIVMSG' or cmd == 'NOTICE' do list = (ctcp_trail ++ (lc arg inlist rest, do: ' ' ++ arg)) |> Enum.flatten |> Enum.reverse case list do [1 | ctcp_rev] -> [ctcp_cmd | args] = Enum.reverse(ctcp_rev) |> String.split(' ') - msg[cmd: ctcp_cmd, args: args, ctcp: true] + msg = msg.cmd(ctcp_cmd).args(args).ctcp(true) _ -> - msg[cmd: cmd, ctcp: :invalid] + msg = msg.cmd(cmd).ctcp(:invalid) end end def get_cmd([cmd | rest], msg) do get_args(rest, msg.cmd(cmd)) end def get_args([], msg) do msg.args(Enum.reverse(msg.args)) end def get_args([[':' | first_arg] | rest], msg) do list = lc arg inlist [first_arg | rest], do: ' ' ++ arg case Enum.flatten(list) do [_ | []] -> get_args([], msg.args(['' | msg.args])) [_ | full_trail] -> get_args([], msg.args([full_trail | msg.args])) end end def get_args([arg | []], msg) do get_args([], msg.args(['', arg | msg.args])) end def get_args([arg | rest], msg) do get_args(rest, msg.args([arg | msg.args])) end ########################## # Parse RPL_ISUPPORT (005) ########################## - def isup([], state) do: state + def isup([], state), do: state def isup([param | rest], state) do try do isup(rest, isup_param(param, state)) rescue _ -> isup(rest, state) end end def isup_param('CHANTYPES=' ++ channel_prefixes, state) do state.channel_prefixes(channel_prefixes) end def isup_param('NETWORK=' ++ network, state) do state.network(network) end def isup_param('PREFIX=' ++ user_prefixes, state) do - result = Regex.run(%r/\((.*)\)(.*)/, user_prefixes, [:capture, :all_but_first]) - {match, [{p1, l1}, {p2, l2}]} = result + {:match, [{p1, l1}, {p2, l2}]} = Regex.run(%r/\((.*)\)(.*)/, user_prefixes, [:capture, :all_but_first]) group1 = String.slice(user_prefixes, p1 + 1, l1) group2 = String.slice(user_prefixes, p2 + 1, l2) state.user_prefixes(Enum.zip(group1, group2)) end def isup_param(_, state) do state end @days_of_week ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] @months_of_year ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] - def ctcp_time({{y, m, d}, {h, n, s}}) -> + def ctcp_time({{y, m, d}, {h, n, s}}) do [:lists.nth(:calendar.day_of_the_week(y,m,d), @days_of_week), " ", - :lists:nth(m, @months_of_year), + :lists.nth(m, @months_of_year), " ", :io_lib.format('~2..0s',[integer_to_list(d)]), " ", :io_lib.format('~2..0s',[integer_to_list(h)]), ":", :io_lib.format('~2..0s',[integer_to_list(n)]), ":", :io_lib.format('~2..0s',[integer_to_list(s)]), " ", integer_to_list(y)] end end \ No newline at end of file