summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/exirc/client.ex22
-rw-r--r--lib/exirc/commands.ex45
-rw-r--r--lib/exirc/example_handler.ex4
-rw-r--r--lib/exirc/utils.ex17
-rw-r--r--test/commands_test.exs59
-rw-r--r--test/utils_test.exs17
6 files changed, 111 insertions, 53 deletions
diff --git a/lib/exirc/client.ex b/lib/exirc/client.ex
index 0cd92fd..d569486 100644
--- a/lib/exirc/client.ex
+++ b/lib/exirc/client.ex
@@ -104,6 +104,13 @@ defmodule ExIrc.Client do
:gen_server.call(client, {:msg, type, nick, msg}, :infinity)
end
@doc """
+ Send an action message, i.e. (/me slaps someone with a big trout)
+ """
+ @spec me(client :: pid, channel :: binary, msg :: binary) :: :ok | {:error, atom}
+ def me(client, channel, msg) do
+ :gen_server.call(client, {:me, channel, msg}, :infinity)
+ end
+ @doc """
Change the client's nick
"""
@spec nick(client :: pid, new_nick :: binary) :: :ok | {:error, atom}
@@ -330,6 +337,12 @@ defmodule ExIrc.Client do
send! state.socket, data
{:reply, :ok, state}
end
+ # Handle /me messages
+ def handle_call({:me, channel, msg}, _from, state) do
+ data = me!(channel, msg)
+ 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
@@ -418,10 +431,9 @@ defmodule ExIrc.Client do
debug? = state.debug?
case Utils.parse(data) do
%IrcMessage{:ctcp => true} = msg ->
- send_event msg, state
+ handle_data 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
@@ -610,6 +622,12 @@ defmodule ExIrc.Client do
end
{:noreply, state}
end
+ # Called when someone uses ACTION, i.e. `/me dies`
+ def handle_data(%IrcMessage{:nick => from, :cmd => "ACTION", :args => [channel, message]} = _msg, state) do
+ if state.debug?, do: debug "* #{from} #{message} in #{channel}"
+ send_event {:me, message, from, channel}, state
+ {: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
diff --git a/lib/exirc/commands.ex b/lib/exirc/commands.ex
index 0d3862e..6f2d385 100644
--- a/lib/exirc/commands.ex
+++ b/lib/exirc/commands.ex
@@ -165,6 +165,7 @@ defmodule Irc.Commands do
############
# Helpers
############
+ @ctcp_delimiter <<0x01>>
@doc """
Send data to a TCP socket.
@@ -181,64 +182,72 @@ defmodule Irc.Commands do
@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! String.to_char_list(cmd)
+ def command!(cmd), do: [cmd, '\r\n']
@doc """
Builds a valid CTCP command.
"""
- def ctcp!(cmd), do: [1, '#{cmd}', 1]
+ def ctcp!(cmd), do: command! [@ctcp_delimiter, cmd, @ctcp_delimiter]
+ def ctcp!(cmd, args) do
+ expanded = args |> Enum.intersperse(' ')
+ command! [@ctcp_delimiter, cmd, expanded, @ctcp_delimiter]
+ end
# IRC Commands
@doc """
Send password to server
"""
- def pass!(pwd), do: command! ['PASS ', '#{pwd}']
+ def pass!(pwd), do: command! ['PASS ', pwd]
@doc """
Send nick to server. (Changes or sets your nick)
"""
- def nick!(nick), do: command! ['NICK ', '#{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}']
+ command! ['USER ', user, ' 0 * :', name]
end
@doc """
Send PONG in response to PING
"""
- def pong1!(nick), do: command! ['PONG ', '#{nick}']
+ 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}']
+ 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}']
+ def privmsg!(nick, msg), do: command! ['PRIVMSG ', nick, ' :', msg]
+ @doc """
+ Send a `/me <msg>` CTCP command to t
+ """
+ def me!(channel, msg), do: command! ['PRIVMSG ', channel, ' :', @ctcp_delimiter, 'ACTION ', msg, @ctcp_delimiter]
@doc """
Send notice to channel or user
"""
- def notice!(nick, msg), do: command! ['NOTICE ', '#{nick}', ' :', '#{msg}']
+ 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}']
+ def join!(channel), do: command! ['JOIN ', 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}']
+ def part!(channel), do: command! ['PART ', channel]
@doc """
Send quit command to server (disconnect from server)
"""
- def quit!(msg \\ "Leaving"), do: command! ['QUIT :', '#{msg}']
+ 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}']
+ 0 -> command! ['KICK ', channel, ' ', nick]
+ _ -> command! ['KICK ', channel, ' ', nick, ' ', message]
end
end
@doc """
@@ -248,15 +257,15 @@ defmodule Irc.Commands do
"""
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}']
+ 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}']
+ command! ['INVITE ', nick, ' ', channel]
end
end
diff --git a/lib/exirc/example_handler.ex b/lib/exirc/example_handler.ex
index bd3f142..4bc8e8c 100644
--- a/lib/exirc/example_handler.ex
+++ b/lib/exirc/example_handler.ex
@@ -99,6 +99,10 @@ defmodule ExampleHandler do
debug "#{from} mentioned us in #{channel}: #{message}"
{:noreply, nil}
end
+ def handle_info({:me, message, from, channel}, _state) do
+ debug "* #{from} #{message} in #{channel}"
+ {:noreply, nil}
+ end
# This is an example of how you can manually catch commands if ExIrc.Client doesn't send a specific message for it
def handle_info(%IrcMessage{:nick => from, :cmd => "PRIVMSG", :args => ["testnick", msg]}, _state) do
debug "Received a private message from #{from}: #{msg}"
diff --git a/lib/exirc/utils.ex b/lib/exirc/utils.ex
index c46f648..3221956 100644
--- a/lib/exirc/utils.ex
+++ b/lib/exirc/utils.ex
@@ -44,14 +44,17 @@ defmodule ExIrc.Utils do
get_cmd([cmd, arg1, [1 | ctcp_trail] | restargs], msg)
end
- defp get_cmd([cmd, _arg1, [1 | ctcp_trail] | restargs], msg) when cmd == 'PRIVMSG' or cmd == 'NOTICE' do
- args = ctcp_trail ++ for arg <- restargs, do: ' ' ++ arg
- |> Enum.flatten
- |> Enum.reverse
+ defp get_cmd([cmd, target, [1 | ctcp_cmd] | cmd_args], msg) when cmd == 'PRIVMSG' or cmd == 'NOTICE' do
+ args = cmd_args
+ |> Enum.map(&Enum.take_while(&1, fn c -> c != ?\001 end))
+ |> Enum.map(&List.to_string/1)
case args do
- [1 | ctcp_rev] ->
- [ctcp_cmd | args] = ctcp_rev |> Enum.reverse |> :string.tokens(' ')
- %{msg | :cmd => to_string(ctcp_cmd), :args => args, :ctcp => true}
+ args when args != [] ->
+ %{msg |
+ :cmd => to_string(ctcp_cmd),
+ :args => [to_string(target), args |> Enum.join(" ")],
+ :ctcp => true
+ }
_ ->
%{msg | :cmd => to_string(cmd), :ctcp => :invalid}
end
diff --git a/test/commands_test.exs b/test/commands_test.exs
index 950e559..c84fce3 100644
--- a/test/commands_test.exs
+++ b/test/commands_test.exs
@@ -4,24 +4,45 @@ defmodule ExIrc.CommandsTest do
use Irc.Commands
test "Commands are formatted properly" do
- assert [1, 'TESTCMD', 1] == ctcp! "TESTCMD"
- assert [['PASS ', 'testpass'], '\r\n'] == pass! "testpass"
- assert [['NICK ', 'testnick'], '\r\n'] == nick! "testnick"
- assert [['USER ', 'testuser', ' 0 * :', 'Test User'], '\r\n'] == user! "testuser", "Test User"
- assert [['PONG ', 'testnick'], '\r\n'] == pong1! "testnick"
- assert [['PONG ', 'testnick', ' ', 'othernick'], '\r\n'] == pong2! "testnick", "othernick"
- assert [['PRIVMSG ', 'testnick', ' :', 'Test message!'], '\r\n'] == privmsg! "testnick", "Test message!"
- assert [['NOTICE ', 'testnick', ' :', 'Test notice!'], '\r\n'] == notice! "testnick", "Test notice!"
- assert [['JOIN ', 'testchan', ' ', ''], '\r\n'] == join! "testchan"
- assert [['JOIN ', 'testchan', ' ', 'chanpass'], '\r\n'] == join! "testchan", "chanpass"
- assert [['PART ', 'testchan'], '\r\n'] == part! "testchan"
- assert [['QUIT :', 'Leaving'], '\r\n'] == quit!
- assert [['QUIT :', 'Goodbye, cruel world.'], '\r\n'] == quit! "Goodbye, cruel world."
- assert [['KICK ', '#testchan', ' ', 'testuser'], '\r\n'] == kick! "#testchan", "testuser"
- assert [['KICK ', '#testchan', ' ', 'testuser', ' ', 'Get outta here!'], '\r\n'] == kick! "#testchan", "testuser", "Get outta here!"
- assert [['MODE ', 'testuser', ' ', '-o'], '\r\n'] == mode! "testuser", "-o"
- assert [['MODE ', '#testchan', ' ', '+im'], '\r\n'] == mode! "#testchan", "+im"
- assert [['MODE ', '#testchan', ' ', '+o', ' ', 'testuser'], '\r\n'] = mode! "#testchan", "+o", "testuser"
- assert [['INVITE ', 'testuser', ' ', '#testchan'], '\r\n'] = invite! "testuser", "#testchan"
+ expected = <<1, "TESTCMD", 1, ?\r, ?\n>>
+ assert expected == ctcp!("TESTCMD") |> IO.iodata_to_binary
+ expected = <<"PRIVMSG #testchan :", 0x01, "ACTION mind explodes!!", 0x01, ?\r, ?\n>>
+ assert expected == me!("#testchan", "mind explodes!!") |> IO.iodata_to_binary
+ expected = <<"PASS testpass", ?\r, ?\n>>
+ assert expected == pass!("testpass") |> IO.iodata_to_binary
+ expected = <<"NICK testnick", ?\r, ?\n>>
+ assert expected == nick!("testnick") |> IO.iodata_to_binary
+ expected = <<"USER testuser 0 * :Test User", ?\r, ?\n>>
+ assert expected == user!("testuser", "Test User") |> IO.iodata_to_binary
+ expected = <<"PONG testnick", ?\r, ?\n>>
+ assert expected == pong1!("testnick") |> IO.iodata_to_binary
+ expected = <<"PONG testnick othernick", ?\r, ?\n>>
+ assert expected == pong2!("testnick", "othernick") |> IO.iodata_to_binary
+ expected = <<"PRIVMSG testnick :Test message!", ?\r, ?\n>>
+ assert expected == privmsg!("testnick", "Test message!") |> IO.iodata_to_binary
+ expected = <<"NOTICE testnick :Test notice!", ?\r, ?\n>>
+ assert expected == notice!("testnick", "Test notice!") |> IO.iodata_to_binary
+ expected = <<"JOIN testchan", ?\r, ?\n>>
+ assert expected == join!("testchan") |> IO.iodata_to_binary
+ expected = <<"JOIN testchan chanpass", ?\r, ?\n>>
+ assert expected == join!("testchan", "chanpass") |> IO.iodata_to_binary
+ expected = <<"PART testchan", ?\r, ?\n>>
+ assert expected == part!("testchan") |> IO.iodata_to_binary
+ expected = <<"QUIT :Leaving", ?\r, ?\n>>
+ assert expected == quit! |> IO.iodata_to_binary
+ expected = <<"QUIT :Goodbye, cruel world.", ?\r, ?\n>>
+ assert expected == quit!("Goodbye, cruel world.") |> IO.iodata_to_binary
+ expected = <<"KICK #testchan testuser", ?\r, ?\n>>
+ assert expected == kick!("#testchan", "testuser") |> IO.iodata_to_binary
+ expected = <<"KICK #testchan testuser Get outta here!", ?\r, ?\n>>
+ assert expected == kick!("#testchan", "testuser", "Get outta here!") |> IO.iodata_to_binary
+ expected = <<"MODE testuser -o", ?\r, ?\n>>
+ assert expected == mode!("testuser", "-o") |> IO.iodata_to_binary
+ expected = <<"MODE #testchan +im", ?\r, ?\n>>
+ assert expected == mode!("#testchan", "+im") |> IO.iodata_to_binary
+ expected = <<"MODE #testchan +o testuser", ?\r, ?\n>>
+ assert expected == mode!("#testchan", "+o", "testuser") |> IO.iodata_to_binary
+ expected = <<"INVITE testuser #testchan", ?\r, ?\n>>
+ assert expected == invite!("testuser", "#testchan") |> IO.iodata_to_binary
end
end \ No newline at end of file
diff --git a/test/utils_test.exs b/test/utils_test.exs
index 34d4e2d..e079661 100644
--- a/test/utils_test.exs
+++ b/test/utils_test.exs
@@ -13,13 +13,16 @@ defmodule ExIrc.UtilsTest do
assert Utils.ctcp_time(local_time) == "Fri Dec 06 14:05:00 2013"
end
- test "Can parse an IRC message" do
- message = ':irc.example.org 005 nick NETWORK=Freenode PREFIX=(ov)@+ CHANTYPES=#&'
- assert %IrcMessage{
- :server => "irc.example.org",
- :cmd => @rpl_isupport,
- :args => ["nick", "NETWORK=Freenode", "PREFIX=(ov)@+", "CHANTYPES=#&"]
- } = Utils.parse(message)
+ test "Can parse a CTCP command" do
+ message = ':pschoenf NOTICE #testchan :\001ACTION mind explodes!!\001'
+ expected = %IrcMessage{
+ nick: "pschoenf",
+ cmd: "ACTION",
+ ctcp: true,
+ args: ["#testchan", "mind explodes!!"]
+ }
+ result = Utils.parse(message)
+ assert expected == result
end
test "Parse INVITE message" do