Page MenuHomePhabricator

No OneTemporary

diff --git a/lib/exirc/client.ex b/lib/exirc/client.ex
index 031b2bb..79390f7 100644
--- a/lib/exirc/client.ex
+++ b/lib/exirc/client.ex
@@ -1,648 +1,648 @@
defmodule ExIrc.Client do
@moduledoc """
Maintains the state and behaviour for individual IRC client connections
"""
use Irc.Commands
import ExIrc.Logger
alias ExIrc.Channels, as: Channels
alias ExIrc.Utils, as: Utils
# Client internal state
defmodule ClientState do
defstruct event_handlers: [],
server: "localhost",
port: 6667,
socket: nil,
nick: "",
pass: "",
user: "",
name: "",
connected?: false,
logged_on?: false,
autoping: true,
channel_prefixes: "",
network: "",
user_prefixes: "",
login_time: "",
channels: [],
debug?: false
end
#################
# External API
#################
@doc """
Start a new IRC client process
Returns either {:ok, pid} or {:error, reason}
"""
@spec start!(options :: list() | nil) :: {:ok, pid} | {:error, term}
def start!(options \\ []) do
start_link(options)
end
@doc """
Start a new IRC client process.
Returns either {:ok, pid} or {:error, reason}
"""
@spec start_link(options :: list() | nil) :: {:ok, pid} | {:error, term}
def start_link(options \\ []) do
:gen_server.start_link(__MODULE__, options, [])
end
@doc """
Stop the IRC client process
"""
@spec stop!(client :: pid) :: {:stop, :normal, :ok, ClientState.t}
def stop!(client) do
:gen_server.call(client, :stop)
end
@doc """
Connect to a server with the provided server and port
Example:
Client.connect! pid, "localhost", 6667
"""
@spec connect!(client :: pid, server :: binary, port :: non_neg_integer) :: :ok
def connect!(client, server, port) do
:gen_server.call(client, {:connect, server, port}, :infinity)
end
@doc """
Determine if the provided client process has an open connection to a server
"""
@spec is_connected?(client :: pid) :: true | false
def is_connected?(client) do
:gen_server.call(client, :is_connected?)
end
@doc """
Logon to a server
Example:
Client.logon pid, "password", "mynick", "username", "My Name"
"""
@spec logon(client :: pid, pass :: binary, nick :: binary, user :: binary, name :: binary) :: :ok | {:error, :not_connected}
def logon(client, pass, nick, user, name) do
:gen_server.call(client, {:logon, pass, nick, user, name}, :infinity)
end
@doc """
Determine if the provided client is logged on to a server
"""
@spec is_logged_on?(client :: pid) :: true | false
def is_logged_on?(client) do
:gen_server.call(client, :is_logged_on?)
end
@doc """
Send a message to a nick or channel
Message types are:
:privmsg
:notice
:ctcp
"""
@spec msg(client :: pid, type :: atom, nick :: binary, msg :: binary) :: :ok | {:error, atom}
def msg(client, type, nick, msg) do
:gen_server.call(client, {:msg, type, nick, msg}, :infinity)
end
@doc """
Change the client's nick
"""
@spec nick(client :: pid, new_nick :: binary) :: :ok | {:error, atom}
def nick(client, new_nick) do
:gen_server.call(client, {:nick, new_nick}, :infinity)
end
@doc """
Send a raw IRC command
"""
@spec cmd(client :: pid, raw_cmd :: binary) :: :ok | {:error, atom}
def cmd(client, raw_cmd) do
:gen_server.call(client, {:cmd, raw_cmd})
end
@doc """
Join a channel, with an optional password
"""
@spec join(client :: pid, channel :: binary, key :: binary | nil) :: :ok | {:error, atom}
def join(client, channel, key \\ "") do
:gen_server.call(client, {:join, channel, key}, :infinity)
end
@doc """
Leave a channel
"""
@spec part(client :: pid, channel :: binary) :: :ok | {:error, atom}
def part(client, channel) do
:gen_server.call(client, {:part, channel}, :infinity)
end
@doc """
Kick a user from a channel
"""
@spec kick(client :: pid, channel :: binary, nick :: binary, message :: binary | nil) :: :ok | {:error, atom}
def kick(client, channel, nick, message \\ "") do
:gen_server.call(client, {:kick, channel, nick, message}, :infinity)
end
@doc """
Change mode for a user or channel
"""
@spec mode(client :: pid, channel_or_nick :: binary, flags :: binary, args :: binary | nil) :: :ok | {:error, atom}
def mode(client, channel_or_nick, flags, args \\ "") do
:gen_server.call(client, {:mode, channel_or_nick, flags, args}, :infinity)
end
@doc """
Invite a user to a channel
"""
@spec invite(client :: pid, nick :: binary, channel :: binary) :: :ok | {:error, atom}
def invite(client, nick, channel) do
:gen_server.call(client, {:invite, nick, channel}, :infinity)
end
@doc """
Quit the server, with an optional part message
"""
@spec quit(client :: pid, msg :: binary | nil) :: :ok | {:error, atom}
def quit(client, msg \\ "Leaving..") do
:gen_server.call(client, {:quit, msg}, :infinity)
end
@doc """
Get details about each of the client's currently joined channels
"""
@spec channels(client :: pid) :: list(binary) | [] | {:error, atom}
def channels(client) do
:gen_server.call(client, :channels)
end
@doc """
Get a list of users in the provided channel
"""
@spec channel_users(client :: pid, channel :: binary) :: list(binary) | [] | {:error, atom}
def channel_users(client, channel) do
:gen_server.call(client, {:channel_users, channel})
end
@doc """
Get the topic of the provided channel
"""
@spec channel_topic(client :: pid, channel :: binary) :: binary | {:error, atom}
def channel_topic(client, channel) do
:gen_server.call(client, {:channel_topic, channel})
end
@doc """
Get the channel type of the provided channel
"""
@spec channel_type(client :: pid, channel :: binary) :: atom | {:error, atom}
def channel_type(client, channel) do
:gen_server.call(client, {:channel_type, channel})
end
@doc """
Determine if a nick is present in the provided channel
"""
@spec channel_has_user?(client :: pid, channel :: binary, nick :: binary) :: true | false | {:error, atom}
def channel_has_user?(client, channel, nick) do
:gen_server.call(client, {:channel_has_user?, channel, nick})
end
@doc """
Add a new event handler process
"""
@spec add_handler(client :: pid, pid) :: :ok
def add_handler(client, pid) do
:gen_server.call(client, {:add_handler, pid})
end
@doc """
Add a new event handler process, asynchronously
"""
@spec add_handler_async(client :: pid, pid) :: :ok
def add_handler_async(client, pid) do
:gen_server.cast(client, {:add_handler, pid})
end
@doc """
Remove an event handler process
"""
@spec remove_handler(client :: pid, pid) :: :ok
def remove_handler(client, pid) do
:gen_server.call(client, {:remove_handler, pid})
end
@doc """
Remove an event handler process, asynchronously
"""
@spec remove_handler_async(client :: pid, pid) :: :ok
def remove_handler_async(client, pid) do
:gen_server.cast(client, {:remove_handler, pid})
end
@doc """
Get the current state of the provided client
"""
@spec state(client :: pid) :: [{atom, any}]
def state(client) do
state = :gen_server.call(client, :state)
[server: state.server,
port: state.port,
nick: state.nick,
pass: state.pass,
user: state.user,
name: state.name,
autoping: state.autoping,
connected?: state.connected?,
logged_on?: state.logged_on?,
channel_prefixes: state.channel_prefixes,
user_prefixes: state.user_prefixes,
channels: Channels.to_proplist(state.channels),
network: state.network,
login_time: state.login_time,
debug?: state.debug?,
event_handlers: state.event_handlers]
end
###############
# GenServer API
###############
@doc """
Called when :gen_server initializes the client
"""
@spec init(list(any) | []) :: {:ok, ClientState.t}
def init(options \\ []) do
autoping = Keyword.get(options, :autoping, true)
debug = Keyword.get(options, :debug, false)
# Add event handlers
handlers =
Keyword.get(options, :event_handlers, [])
|> List.foldl([], &do_add_handler/2)
# Return initial state
{:ok, %ClientState{
event_handlers: handlers,
autoping: autoping,
logged_on?: false,
debug?: debug,
channels: ExIrc.Channels.init()}}
end
@doc """
Handle calls from the external API. It is not recommended to call these directly.
"""
# Handle call to get the current state of the client process
def handle_call(:state, _from, state), do: {:reply, state, state}
# Handle call to stop the current client process
def handle_call(:stop, _from, state) do
# Ensure the socket connection is closed if stop is called while still connected to the server
if state.connected?, do: :gen_tcp.close(state.socket)
{:stop, :normal, :ok, %{state | :connected? => false, :logged_on? => false, :socket => nil}}
end
# Handles call to add a new event handler process
def handle_call({:add_handler, pid}, _from, state) do
handlers = do_add_handler(pid, state.event_handlers)
{:reply, :ok, %{state | :event_handlers => handlers}}
end
# Handles call to remove an event handler process
def handle_call({:remove_handler, pid}, _from, state) do
handlers = do_remove_handler(pid, state.event_handlers)
{:reply, :ok, %{state | :event_handlers => handlers}}
end
# Handle call to connect to an IRC server
def handle_call({:connect, server, port}, _from, state) do
# If there is an open connection already, close it.
if state.socket != nil, do: :gen_tcp.close(state.socket)
# Open a new connection
- case :gen_tcp.connect(List.from_char_data!(server), port, [:list, {:packet, :line}, {:keepalive, true}]) do
+ case :gen_tcp.connect(String.to_char_list(server), port, [:list, {:packet, :line}, {:keepalive, true}]) do
{:ok, socket} ->
send_event {:connected, server, port}, state
{:reply, :ok, %{state | :connected? => true, :server => server, :port => port, :socket => socket}}
error ->
{:reply, error, state}
end
end
# Handle call to determine if the client is connected
def handle_call(:is_connected?, _from, state), do: {:reply, state.connected?, state}
# Prevents any of the following messages from being handled if the client is not connected to a server.
# Instead, it returns {:error, :not_connected}.
def handle_call(_, _from, %ClientState{:connected? => false} = state), do: {:reply, {:error, :not_connected}, state}
# Handle call to login to the connected IRC server
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)
{:reply, :ok, %{state | :pass => pass, :nick => nick, :user => user, :name => name} }
end
# Handle call to determine if client is logged on to a server
def handle_call(:is_logged_on?, _from, state), do: {:reply, state.logged_on?, state}
# Prevents any of the following messages from being handled if the client is not logged on to a server.
# Instead, it returns {:error, :not_logged_in}.
def handle_call(_, _from, %ClientState{:logged_on? => false} = state), do: {:reply, {:error, :not_logged_in}, state}
# Handles call to send a message
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))
end
send! state.socket, data
{:reply, :ok, state}
end
# Handles call to join a channel
def handle_call({:join, channel, key}, _from, state) do send!(state.socket, join!(channel, key)); {:reply, :ok, state} end
# Handles a call to leave a channel
def handle_call({:part, channel}, _from, state) do send!(state.socket, part!(channel)); {:reply, :ok, state} end
# Handles a call to kick a client
def handle_call({:kick, channel, nick, message}, _from, state) do
send!(state.socket, kick!(channel, nick, message))
{:reply, :ok, state}
end
# Handles a call to change mode for a user or channel
def handle_call({:mode, channel_or_nick, flags, args}, _from, state) do
send!(state.socket, mode!(channel_or_nick, flags, args))
{:reply, :ok, state}
end
# Handle call to invite a user to a channel
def handle_call({:invite, nick, channel}, _from, state) do
send!(state.socket, invite!(nick, channel))
{:reply, :ok, state}
end
# Handle call to quit the server and close the socket connection
def handle_call({:quit, msg}, _from, state) do
if state.connected? do
send! state.socket, quit!(msg)
send_event :disconnected, state
:gen_tcp.close state.socket
end
{:reply, :ok, %{state | :connected? => false, :logged_on? => false, :socket => nil}}
end
# Handles call to change the client's nick
def handle_call({:nick, new_nick}, _from, state) do send!(state.socket, nick!(new_nick)); {:reply, :ok, state} end
# Handles call to send a raw command to the IRC server
def handle_call({:cmd, raw_cmd}, _from, state) do send!(state.socket, command!(raw_cmd)); {:reply, :ok, state} end
# Handles call to return the client's channel data
def handle_call(:channels, _from, state), do: {:reply, Channels.channels(state.channels), state}
# Handles call to return a list of users for a given channel
def handle_call({:channel_users, channel}, _from, state), do: {:reply, Channels.channel_users(state.channels, channel), state}
# Handles call to return the given channel's topic
def handle_call({:channel_topic, channel}, _from, state), do: {:reply, Channels.channel_topic(state.channels, channel), state}
# Handles call to return the type of the given channel
def handle_call({:channel_type, channel}, _from, state), do: {:reply, Channels.channel_type(state.channels, channel), state}
# Handles call to determine if a nick is present in the given channel
def handle_call({:channel_has_user?, channel, nick}, _from, state) do
{:reply, Channels.channel_has_user?(state.channels, channel, nick), state}
end
# Handles message to add a new event handler process asynchronously
def handle_cast({:add_handler, pid}, state) do
handlers = do_add_handler(pid, state.event_handlers)
{:noreply, %{state | :event_handlers => handlers}}
end
@doc """
Handles asynchronous messages from the external API. Not recommended to call these directly.
"""
# Handles message to remove an event handler process asynchronously
def handle_cast({:remove_handler, pid}, state) do
handlers = do_remove_handler(pid, state.event_handlers)
{:noreply, %{state | :event_handlers => handlers}}
end
@doc """
Handle messages from the TCP socket connection.
"""
# Handles the client's socket connection 'closed' event
def handle_info({:tcp_closed, _socket}, %ClientState{:server => server, :port => port} = state) do
info "Connection to #{server}:#{port} closed!"
send_event :disconnected, state
new_state = %{state |
:socket => nil,
:connected? => false,
:logged_on? => false,
:channels => Channels.init()
}
{:noreply, new_state}
end
# Handles any TCP errors in the client's socket connection
def handle_info({:tcp_error, socket, reason}, %ClientState{:server => server, :port => port} = state) do
error "TCP error in connection to #{server}:#{port}:\r\n#{reason}\r\nClient connection closed."
new_state = %{state |
:socket => nil,
:connected? => false,
:logged_on? => false,
:channels => Channels.init()
}
{:stop, {:tcp_error, socket}, new_state}
end
# General handler for messages from the IRC server
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 debug? ->
send_event msg, state
{:noreply, state}
_ ->
{:noreply, state}
end
end
# If an event handler process dies, remove it from the list of event handlers
def handle_info({'DOWN', _, _, pid, _}, state) do
handlers = do_remove_handler(pid, state.event_handlers)
{:noreply, %{state | :event_handlers => handlers}}
end
# Catch-all for unrecognized messages (do nothing)
def handle_info(_, state) do
{:noreply, state}
end
@doc """
Handle termination
"""
def terminate(_reason, state) do
if state.socket != nil do
:gen_tcp.close state.socket
%{state | :socket => nil}
end
:ok
end
@doc """
Transform state for hot upgrades/downgrades
"""
def code_change(_old, state, _extra), do: {:ok, state}
################
# Data handling
################
@doc """
Handle IrcMessages received from the server.
"""
# Called upon successful login
def handle_data(%IrcMessage{:cmd => @rpl_welcome}, %ClientState{:logged_on? => false} = state) do
if state.debug?, do: debug "SUCCESFULLY LOGGED ON"
new_state = %{state | :logged_on? => true, :login_time => :erlang.now()}
send_event :logged_in, new_state
{:noreply, new_state}
end
# Called when the server sends it's current capabilities
def handle_data(%IrcMessage{:cmd => @rpl_isupport} = msg, state) do
if state.debug?, do: debug "RECEIVING SERVER CAPABILITIES"
{:noreply, Utils.isup(msg.args, state)}
end
# Called when the client enters a channel
def handle_data(%IrcMessage{:nick => nick, :cmd => "JOIN"} = msg, %ClientState{:nick => nick} = state) do
channel = msg.args |> List.first
if state.debug?, do: debug "JOINED A CHANNEL #{channel}"
channels = Channels.join(state.channels, channel)
new_state = %{state | :channels => channels}
send_event {:joined, channel}, new_state
{:noreply, new_state}
end
# Called when another user joins a channel the client is in
def handle_data(%IrcMessage{:nick => user_nick, :cmd => "JOIN"} = msg, state) do
channel = msg.args |> List.first
if state.debug?, do: debug "ANOTHER USER JOINED A CHANNEL: #{channel} - #{user_nick}"
channels = Channels.user_join(state.channels, channel, user_nick)
new_state = %{state | :channels => channels}
send_event {:joined, channel, user_nick}, new_state
{:noreply, new_state}
end
# Called on joining a channel, to tell us the channel topic
# Message with three arguments is not RFC compliant but very common
# Message with two arguments is RFC compliant
def handle_data(%IrcMessage{:cmd => @rpl_topic} = msg, state) do
if state.debug?, do: debug "INITIAL TOPIC MSG"
{channel, topic} = case msg.args do
[_nick, channel, topic] -> debug("1. TOPIC SET FOR #{channel} TO #{topic}"); {channel, topic}
[channel, topic] -> debug("2. TOPIC SET FOR #{channel} TO #{topic}"); {channel, topic}
end
channels = Channels.set_topic(state.channels, channel, topic)
new_state = %{state | :channels => channels}
send_event {:topic_changed, channel, topic}, new_state
{:noreply, new_state}
end
# Called when the topic changes while we're in the channel
def handle_data(%IrcMessage{:cmd => "TOPIC", :args => [channel, topic]}, state) do
if state.debug?, do: debug "TOPIC CHANGED FOR #{channel} TO #{topic}"
channels = Channels.set_topic(state.channels, channel, topic)
new_state = %{state | :channels => channels}
send_event {:topic_changed, channel, topic}, new_state
{:noreply, new_state}
end
# Called when joining a channel with the list of current users in that channel, or when the NAMES command is sent
def handle_data(%IrcMessage{:cmd => @rpl_namereply} = msg, state) do
if state.debug?, do: debug "NAMES LIST RECEIVED"
{_nick, channel_type, channel, names} = case msg.args do
[nick, channel_type, channel, names] -> {nick, channel_type, channel, names}
[channel_type, channel, names] -> {nil, channel_type, channel, names}
end
channels = Channels.set_type(
Channels.users_join(state.channels, channel, String.split(names, " ", trim: true)),
channel,
channel_type)
{:noreply, %{state | :channels => channels}}
end
# Called when our nick has succesfully changed
def handle_data(%IrcMessage{:cmd => "NICK", :nick => nick, :args => [new_nick]}, %ClientState{:nick => nick} = state) do
if state.debug?, do: debug "NICK CHANGED FROM #{nick} TO #{new_nick}"
new_state = %{state | :nick => new_nick}
send_event {:nick_changed, new_nick}, new_state
{:noreply, new_state}
end
# Called when someone visible to us changes their nick
def handle_data(%IrcMessage{:cmd => "NICK", :nick => nick, :args => [new_nick]}, state) do
if state.debug?, do: debug "#{nick} CHANGED THEIR NICK TO #{new_nick}"
channels = Channels.user_rename(state.channels, nick, new_nick)
new_state = %{state | :channels => channels}
send_event {:nick_changed, nick, new_nick}, new_state
{:noreply, new_state}
end
# Called when we leave a channel
def handle_data(%IrcMessage{:cmd => "PART", :nick => nick} = msg, %ClientState{:nick => nick} = state) do
channel = msg.args |> List.first
if state.debug?, do: debug "WE LEFT A CHANNEL: #{channel}"
channels = Channels.part(state.channels, channel)
new_state = %{state | :channels => channels}
send_event {:parted, channel}, new_state
{:noreply, new_state}
end
# Called when someone else in our channel leaves
def handle_data(%IrcMessage{:cmd => "PART", :nick => user_nick} = msg, state) do
channel = msg.args |> List.first
if state.debug?, do: debug "#{user_nick} LEFT A CHANNEL: #{channel}"
channels = Channels.user_part(state.channels, channel, user_nick)
new_state = %{state | :channels => channels}
send_event {:parted, channel, user_nick}, new_state
{:noreply, new_state}
end
# Called when we receive a PING
def handle_data(%IrcMessage{:cmd => "PING"} = msg, %ClientState{:autoping => true} = state) do
if state.debug?, do: debug "RECEIVED A PING!"
case msg do
%IrcMessage{:args => [from]} ->
debug("SENT PONG2"); send!(state.socket, pong2!(state.nick, from))
_ ->
debug("SENT PONG1"); send!(state.socket, pong1!(state.nick))
end
{:noreply, state};
end
# Called when we are invited to a channel
def handle_data(%IrcMessage{:cmd => "INVITE", :args => [nick, channel], :nick => by} = msg, %ClientState{:nick => nick} = state) do
if state.debug?, do: debug "RECEIVED AN INVITE: #{msg.args |> Enum.join(" ")}"
send_event {:invited, by, channel}, state
{:noreply, state}
end
# Called when we are kicked from a channel
def handle_data(%IrcMessage{:cmd => "KICK", :args => [channel, nick], :nick => by} = _msg, %ClientState{:nick => nick} = state) do
if state.debug?, do: debug "WE WERE KICKED FROM #{channel} BY #{by}"
send_event {:kicked, by, channel}, state
{:noreply, state}
end
# Called when someone else was kicked from a channel
def handle_data(%IrcMessage{:cmd => "KICK", :args => [channel, nick], :nick => by} = _msg, state) do
if state.debug?, do: debug "#{nick} WAS KICKED FROM #{channel} BY #{by}"
send_event {:kicked, nick, by, channel}, state
{:noreply, state}
end
# Called when someone sends us a message
def handle_data(%IrcMessage{:nick => from, :cmd => "PRIVMSG", :args => [nick, message]} = _msg, %ClientState{:nick => nick} = state) do
if state.debug?, do: debug "#{from} SENT US #{message}"
send_event {:received, message, from}, state
{:noreply, state}
end
# Called when someone sends a message to a channel we're in, or a list of users
def handle_data(%IrcMessage{:nick => from, :cmd => "PRIVMSG", :args => [to, message]} = _msg, %ClientState{:nick => nick} = state) do
if state.debug?, do: debug "#{from} SENT #{message} TO #{to}"
case String.contains?(to, nick) do
# Treat it like a private message if message was sent to a list of users that includes us
true -> send_event {:received, message, from}, state
# Otherwise it was just a channel message
_ ->
send_event {:received, message, from, to}, state
# If we were mentioned, fire that event as well
if String.contains?(message, nick), do: send_event({:mentioned, message, from, to}, state)
end
{:noreply, state}
end
# Called any time we receive an unrecognized message
def handle_data(msg, state) do
if state.debug? do debug "UNRECOGNIZED MSG: #{msg.cmd}"; IO.inspect(msg) end
{:noreply, state}
end
###############
# Internal API
###############
defp send_event(msg, %ClientState{:event_handlers => handlers}) when is_list(handlers) do
Enum.each(handlers, fn({pid, _}) -> Kernel.send(pid, msg) end)
end
defp 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
defp do_remove_handler(pid, handlers) do
case List.keyfind(handlers, pid, 0) do
{pid, ref} ->
Process.demonitor(ref)
List.keydelete(handlers, pid, 0)
nil ->
handlers
end
end
defp debug(msg) do
IO.puts(IO.ANSI.green() <> msg <> IO.ANSI.reset())
end
end
diff --git a/lib/exirc/commands.ex b/lib/exirc/commands.ex
index c3e5b25..0d3862e 100644
--- a/lib/exirc/commands.ex
+++ b/lib/exirc/commands.ex
@@ -1,262 +1,262 @@
defmodule Irc.Commands do
@moduledoc """
Defines IRC command constants, and methods for generating valid commands to send to an IRC server.
"""
defmacro __using__(_) do
quote do
import Irc.Commands
####################
# IRC Numeric Codes
####################
@rpl_welcome "001"
@rpl_yourhost "002"
@rpl_created "003"
@rpl_myinfo "004"
@rpl_isupport "005" # Defacto standard for server support
@rpl_bounce "010" # Defacto replacement of "005" in RFC2812
@rpl_statsdline "250"
#@doc """
#":There are <integer> users and <integer> invisible on <integer> servers"
#"""
@rpl_luserclient "251"
#@doc """
# "<integer> :operator(s) online"
#"""
@rpl_luserop "252"
#@doc """
#"<integer> :unknown connection(s)"
#"""
@rpl_luserunknown "253"
#@doc """
#"<integer> :channels formed"
#"""
@rpl_luserchannels "254"
#@doc """
#":I have <integer> clients and <integer> servers"
#"""
@rpl_luserme "255"
#@doc """
#Local/Global user stats
#"""
@rpl_localusers "265"
@rpl_globalusers "266"
#@doc """
#When sending a TOPIC message to determine the channel topic,
#one of two replies is sent. If the topic is set, RPL_TOPIC is sent back else
#RPL_NOTOPIC.
#"""
@rpl_notopic "331"
@rpl_topic "332"
#@doc """
#To reply to a NAMES message, a reply pair consisting
#of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the
#server back to the client. If there is no channel
#found as in the query, then only RPL_ENDOFNAMES is
#returned. The exception to this is when a NAMES
#message is sent with no parameters and all visible
#channels and contents are sent back in a series of
#RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark
#the end.
#Format: "<channel> :[[@|+]<nick> [[@|+]<nick> [...]]]"
#"""
@rpl_namereply "353"
@rpl_endofnames "366"
#@doc """
#When responding to the MOTD message and the MOTD file
#is found, the file is displayed line by line, with
#each line no longer than 80 characters, using
#RPL_MOTD format replies. These should be surrounded
#by a RPL_MOTDSTART (before the RPL_MOTDs) and an
#RPL_ENDOFMOTD (after).
#"""
@rpl_motd "372"
@rpl_motdstart "375"
@rpl_endofmotd "376"
################
# Error Codes
################
#@doc """
#Used to indicate the nickname parameter supplied to a command is currently unused.
#"""
@err_no_such_nick "401"
#@doc """
#Used to indicate the server name given currently doesn"t exist.
#"""
@err_no_such_server "402"
#@doc """
#Used to indicate the given channel name is invalid.
#"""
@err_no_such_channel "403"
#@doc """
#Sent to a user who is either (a) not on a channel which is mode +n or (b),
#not a chanop (or mode +v) on a channel which has mode +m set, and is trying
#to send a PRIVMSG message to that channel.
#"""
@err_cannot_send_to_chan "404"
#@doc """
#Sent to a user when they have joined the maximum number of allowed channels
#and they try to join another channel.
#"""
@err_too_many_channels "405"
#@doc """
#Returned to a registered client to indicate that the command sent is unknown by the server.
#"""
@err_unknown_command "421"
#@doc """
#Returned when a nickname parameter expected for a command and isn"t found.
#"""
@err_no_nickname_given "431"
#@doc """
#Returned after receiving a NICK message which contains characters which do not fall in the defined set.
#"""
@err_erroneus_nickname "432"
#@doc """
#Returned when a NICK message is processed that results in an attempt to
#change to a currently existing nickname.
#"""
@err_nickname_in_use "433"
#@doc """
#Returned by a server to a client when it detects a nickname collision
#(registered of a NICK that already exists by another server).
#"""
@err_nick_collision "436"
#@doc """
#"""
@err_unavail_resource "437"
#@doc """
#Returned by the server to indicate that the client must be registered before
#the server will allow it to be parsed in detail.
#"""
@err_not_registered "451"
#"""
# Returned by the server by numerous commands to indicate to the client that
# it didn"t supply enough parameters.
#"""
@err_need_more_params "461"
#@doc """
#Returned by the server to any link which tries to change part of the registered
#details (such as password or user details from second USER message).
#"""
@err_already_registered "462"
#@doc """
#Returned by the server to the client when the issued command is restricted
#"""
@err_restricted "484"
###############
# Code groups
###############
@logon_errors [ @err_no_nickname_given, @err_erroneus_nickname,
@err_nickname_in_use, @err_nick_collision,
@err_unavail_resource, @err_need_more_params,
@err_already_registered, @err_restricted ]
end
end
############
# Helpers
############
@doc """
Send data to a TCP socket.
Example:
command = pass! "password"
send! socket, command
"""
def send!(socket, data) do
:gen_tcp.send(socket, data)
end
@doc """
Builds a valid IRC command.
"""
def command!(cmd) when is_list(cmd), do: [cmd, '\r\n']
- def command!(cmd) when is_binary(cmd), do: command! List.from_char_data!(cmd)
+ def command!(cmd) when is_binary(cmd), do: command! String.to_char_list(cmd)
@doc """
Builds a valid CTCP command.
"""
def ctcp!(cmd), do: [1, '#{cmd}', 1]
# IRC Commands
@doc """
Send password to server
"""
def pass!(pwd), do: command! ['PASS ', '#{pwd}']
@doc """
Send nick to server. (Changes or sets your nick)
"""
def nick!(nick), do: command! ['NICK ', '#{nick}']
@doc """
Send username to server. (Changes or sets your username)
"""
def user!(user, name) do
command! ['USER ', '#{user}', ' 0 * :', '#{name}']
end
@doc """
Send PONG in response to PING
"""
def pong1!(nick), do: command! ['PONG ', '#{nick}']
@doc """
Send a targeted PONG in response to PING
"""
def pong2!(nick, to), do: command! ['PONG ', '#{nick}', ' ', '#{to}']
@doc """
Send message to channel or user
"""
def privmsg!(nick, msg), do: command! ['PRIVMSG ', '#{nick}', ' :', '#{msg}']
@doc """
Send notice to channel or user
"""
def notice!(nick, msg), do: command! ['NOTICE ', '#{nick}', ' :', '#{msg}']
@doc """
Send join command to server (join a channel)
"""
def join!(channel, key \\ ""), do: command! ['JOIN ', '#{channel}', ' ', '#{key}']
@doc """
Send part command to server (leave a channel)
"""
def part!(channel), do: command! ['PART ', '#{channel}']
@doc """
Send quit command to server (disconnect from server)
"""
def quit!(msg \\ "Leaving"), do: command! ['QUIT :', '#{msg}']
@doc """
Send kick command to server
"""
def kick!(channel, nick, message \\ "") do
case "#{message}" |> String.length do
0 -> command! ['KICK ', '#{channel}', ' ', '#{nick}']
_ -> command! ['KICK ', '#{channel}', ' ', '#{nick}', ' ', '#{message}']
end
end
@doc """
Send mode command to server
MODE <nick> <flags>
MODE <channel> <flags> [<args>]
"""
def mode!(channel_or_nick, flags, args \\ "") do
case "#{args}" |> String.length do
0 -> command! ['MODE ', '#{channel_or_nick}', ' ', '#{flags}']
_ -> command! ['MODE ', '#{channel_or_nick}', ' ', '#{flags}', ' ', '#{args}']
end
end
@doc """
Send an invite command
"""
def invite!(nick, channel) do
command! ['INVITE ', '#{nick}', ' ', '#{channel}']
end
end
diff --git a/lib/exirc/logger.ex b/lib/exirc/logger.ex
index 7a6817b..6055221 100644
--- a/lib/exirc/logger.ex
+++ b/lib/exirc/logger.ex
@@ -1,29 +1,29 @@
defmodule ExIrc.Logger do
@moduledoc """
A simple abstraction of :error_logger
"""
@doc """
Log an informational message report
"""
@spec info(binary) :: :ok
def info(msg) do
- :error_logger.info_report List.from_char_data!(msg)
+ :error_logger.info_report String.to_char_list(msg)
end
@doc """
Log a warning message report
"""
@spec warning(binary) :: :ok
def warning(msg) do
- :error_logger.warning_report List.from_char_data!("#{IO.ANSI.yellow()}#{msg}#{IO.ANSI.reset()}")
+ :error_logger.warning_report String.to_char_list("#{IO.ANSI.yellow()}#{msg}#{IO.ANSI.reset()}")
end
@doc """
Log an error message report
"""
@spec error(binary) :: :ok
def error(msg) do
- :error_logger.error_report List.from_char_data!("#{IO.ANSI.red()}#{msg}#{IO.ANSI.reset()}")
+ :error_logger.error_report String.to_char_list("#{IO.ANSI.red()}#{msg}#{IO.ANSI.reset()}")
end
end
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 21, 7:05 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
58824
Default Alt Text
(35 KB)

Event Timeline