diff options
Diffstat (limited to 'lib/irc/client')
-rw-r--r-- | lib/irc/client/command.ex | 50 | ||||
-rw-r--r-- | lib/irc/client/command/account.ex | 18 | ||||
-rw-r--r-- | lib/irc/client/command/away.ex | 25 | ||||
-rw-r--r-- | lib/irc/client/command/chghost.ex | 15 | ||||
-rw-r--r-- | lib/irc/client/command/invite.ex | 21 | ||||
-rw-r--r-- | lib/irc/client/command/join.ex | 40 | ||||
-rw-r--r-- | lib/irc/client/command/names.ex | 22 | ||||
-rw-r--r-- | lib/irc/client/command/who.ex | 62 | ||||
-rw-r--r-- | lib/irc/client/user_cache.ex | 31 |
9 files changed, 284 insertions, 0 deletions
diff --git a/lib/irc/client/command.ex b/lib/irc/client/command.ex new file mode 100644 index 0000000..a84324f --- /dev/null +++ b/lib/irc/client/command.ex @@ -0,0 +1,50 @@ +defmodule Irc.Client.Command do + + @moduledoc """ + Implementations of IRC protocol commands and capabilities. + + The Capabs modules are used by the BaseClient to implement everything. + + Capabs modules are mostly stateless, except when they enter the buffering FSM state. + + It is recommended that event names matches the command name, to help awaiting response. + """ + + @doc """ + Returns which server-initiated and client-initiated commands the module supports, and IRCv3 capabs to be requested to the server. + """ + @callback init(Keyword.t) :: {commands :: [String.t], + client_commands :: [atom], + capabs :: [String.t]} + + @type buffer :: any() + @type event :: {client_command :: atom | atom, any()} + + @type buffer_ret :: :buffer | {:buffer, buffer()} + @type line_return :: :ok | {:ok, [action]} | {:error, any()} | buffer_ret + @type buffer_return :: buffer_ret | line_return | :postpone + + @type send_action :: {:send, Line.t()} + @type event_action :: {:event, event()} + @type action :: send_action | event_action + + @doc """ + Handles an incoming line. + """ + @callback handle_line(Irc.Line.t(), Irc.Connection.t()) :: line_return + + @doc """ + Handles an incoming line when in buffering state. + + Returning anything else than `:buffer` or `:postpone` will exit the buffering state. + + Postponed lines will be treated as usual (per their respective module) once the buffering state exits. + """ + @callback handle_buffer(Irc.Line.t(), buffer, Irc.Connection.t()) :: buffer_return + + @doc """ + Handles a user requested command. + """ + @callback handle_command(client_command :: atom, args :: list, Irc.Connection.t()) :: line_return + +end diff --git a/lib/irc/client/command/account.ex b/lib/irc/client/command/account.ex new file mode 100644 index 0000000..c5ec1a9 --- /dev/null +++ b/lib/irc/client/command/account.ex @@ -0,0 +1,18 @@ +defmodule Irc.Client.Command.Account do + alias Irc.Parser.Line + + @type t :: logout :: {:account, Irc.Mask.t} | login :: {:account, Irc.Mask.t, String.t} + + def init() do + {"ACCOUNT", nil, "account-notify"} + end + + def handle_line(%Line{command: "ACCOUNT", source: target, args: ["*"]}) do + {:event, {:account, target}} + end + + def handle_line(%Line{command: "ACCOUNT", source: target, args: [account_name]}) do + {:event, {:account, target, account_name}} + end + +end diff --git a/lib/irc/client/command/away.ex b/lib/irc/client/command/away.ex new file mode 100644 index 0000000..3b6b38e --- /dev/null +++ b/lib/irc/client/command/away.ex @@ -0,0 +1,25 @@ +defmodule Irc.Client.Command.Away do + alias Irc.Parser.Line + + @type t :: away :: {:away, Irc.Mask.t, String.t} | unaway :: {:away, Irc.Mask.t} + + def init() do + {"AWAY", :away, "away-notify"} + end + + def handle_command(:away, args, _) do + command = case args do + [] -> ['AWAY'] + [message] -> ['AWAY :', message] + end + {:send, command} + end + + def hanle_line(%Line{command: "AWAY", source: source, args: args}, _) do + case args do + [] -> {:event, {:away, source}} + [message] -> {:event, {:away, {source, message}}} + end + end + +end diff --git a/lib/irc/client/command/chghost.ex b/lib/irc/client/command/chghost.ex new file mode 100644 index 0000000..2b4adab --- /dev/null +++ b/lib/irc/client/command/chghost.ex @@ -0,0 +1,15 @@ +defmodule Irc.Client.Command.Chghost do + alias Irc.Parser.Line + + @type t :: {:chghost, old_mask :: Irc.Mask.t, new_mask :: Irc.Mask.t} + + def init() do + {"CHGHOST", nil, "chghost"} + end + + def handle_line(%Line{source: old_mask, args: [new_user, new_host]}, _) do + new_mask = %Irc.Mask{old_mask | user: new_user, host: new_host} + {:event, {:chghost, old_mask, new_mask}} + end + +end diff --git a/lib/irc/client/command/invite.ex b/lib/irc/client/command/invite.ex new file mode 100644 index 0000000..833c2fc --- /dev/null +++ b/lib/irc/client/command/invite.ex @@ -0,0 +1,21 @@ +defmodule Irc.Client.Command.Invite do + alias Irc.Parser.Line + + @type invite :: {:invite, channel :: String.t(), inviter :: String.t(), invited_nick :: String.t()} + @type invited :: {:invite, channel :: String.t, inviter :: Irc.Mask.t} + @type t :: invite | invited + + def init() do + {"INVITE", :invite, "invite-notify"} + end + + def handle_line(%Line{command: "INVITE", source: inviter, args: [invited, channel]}) do + {:event, {:invite, channel, inviter, invited}} + end + + def handle_line(%Line{command: "INVITE", source: inviter, args: [channel]}) do + {:event, {:invite, channel, inviter}} + end + +end + diff --git a/lib/irc/client/command/join.ex b/lib/irc/client/command/join.ex new file mode 100644 index 0000000..f4728c7 --- /dev/null +++ b/lib/irc/client/command/join.ex @@ -0,0 +1,40 @@ +defmodule Irc.Client.Command.Join do + alias Irc.Parser.Line + require Line + import Line + + @type t :: {:join, channel :: String.t()} | {:join, channel :: String.t(), Irc.User.t()} + + def init(_args) do + {"JOIN", :join, ["extended-join"]} + end + + # An user joined + def handle_line(line = %Line{command: "JOIN", source: source, args: [channel | args]}, conn) do + if Line.self?(line, conn) do + {:event, {:join, channel}} + else + user = Irc.User.from_mask(source) + user = case args do + [] -> user + [account, name] -> + account = if account != "*", do: account + {channel, %Irc.User{user | account: account, name: name}} + end + {:event, {:join, channel, user}} + end + end + + # Join a channel + def handle_command(:join, [channel], _conn_info) do + {:send, ['JOIN', channel]} + end + + # Outdated! + def handle_buffer(%Line{command: "JOIN", source: %Irc.Mask{nick: nick}, args: [channel]}, buffer = %{channel: channel}, %Irc.Connection{nick: nick}) do + {:finish, nil, {:event, {:join, channel}}} + end + + def handle_buffer(_, _), do: :postpone + +end diff --git a/lib/irc/client/command/names.ex b/lib/irc/client/command/names.ex new file mode 100644 index 0000000..b5a84a6 --- /dev/null +++ b/lib/irc/client/command/names.ex @@ -0,0 +1,22 @@ +defmodule Irc.Client.Command.Names do + alias Irc.Parser.Line + require Irc.Parser.Numeric + import Irc.Parser.Numeric + + def init() do + {[rpl_NAMREPLY(), rpl_ENDOFNAMES()], :names, ["userhost-in-names"]} + end + + def handle_line(%Line{command: rpl_NAMREPLY()}) do + :buffer + end + + def handle_line(%Line{command: rpl_ENDOFNAMES(), args: [_, target | _]}) do + :finish + end + + def handle_buffer(buffer = [%Line{args: [_, target | _]} | _]) do + {:event, {:names, target, buffer}} + end + +end diff --git a/lib/irc/client/command/who.ex b/lib/irc/client/command/who.ex new file mode 100644 index 0000000..1654ef6 --- /dev/null +++ b/lib/irc/client/command/who.ex @@ -0,0 +1,62 @@ +defmodule Irc.Client.Command.Who do + alias Irc.Parser.Line + alias Irc.User + use Irc.Parser.Numeric + + @type t :: {:who, target :: String.t, [Irc.User.t]} + + def init(_) do + {nil, "WHO", nil} + end + + def handle_command("WHO", [target], conn) do + args = if Irc.Connection.supports?(conn, "whox") do + " nuhs%cuhsnfdar" + end + {:buffer, %{target: target, acc: [], error: nil}, {:send, ['WHO ', target, args]}} + end + + @errors [err_NOSUCHSERVER(), rpl_TRYAGAIN()] + def handle_buffer(%Line{command: error, args: args}, buffer, _conn) when error in @errors do + #? + {:buffer, %{buffer | error: {error, args}}} + end + + def handle_buffer(%Line{command: rpl_ENDOFWHO()}, buffer, _conn) do + result = if buffer.error do + {:error, buffer.error} + else + {:ok, buffer.acc} + end + {:finish, nil, {:event, {:who, buffer.target, result}}} + end + + def handle_buffer(%Line{command: rpl_WHOREPLY(), args: args}, buffer, conn) do + case args do + [_, c, u, h, s, n, f, d | a_r] -> + {a,r} = case a_r do + [a, r] -> {a, r} + [r] -> {nil, r} + end + {modes, _} = Irc.Parser.Prefix.parse(f, conn) + chanmodes = %{buffer.target => modes} + user = %Irc.User{ + user: u, + host: h, + server: s, + nick: n, + account: a, + name: r, + channels: [buffer.target], + chanmodes: chanmodes + } + + _ -> + {:finish, nil} # ERROFFEL + end + {:buffer, %{buffer | acc: [args | buffer.acc]}} + end + + def handle_buffer(_, _), do: :postpone + +end diff --git a/lib/irc/client/user_cache.ex b/lib/irc/client/user_cache.ex new file mode 100644 index 0000000..2653630 --- /dev/null +++ b/lib/irc/client/user_cache.ex @@ -0,0 +1,31 @@ +defmodule Irc.Client.UserCache do + + # tuple: {nick, user} + + def init(args) do + opts = [{:read_concurrency, true}, :protected] + :ets.new(__MODULE__, opts) + end + + def lookup(ets, nick) do + case :ets.lookup(ets, nick) do + [{^nick, user}] -> user + _ -> nil + end + end + + def change(ets, nick, changes) do + initial = if user = lookup(ets, nick), do: user, else: %Irc.User{nick: nick} + user = User.change(initial, changes) + put(ets, nick, user) + end + + defp put(ets, original_nick, user) do + tuple = {user.nick, user} + if original_nick != user.nick do + true = :ets.delete(ets, original_nick) + end + :ets.insert(ets, tuple) + end + +end |