diff options
-rw-r--r-- | lib/irc/admin_handler.ex | 2 | ||||
-rw-r--r-- | lib/irc/puppet_connection.ex | 226 |
2 files changed, 162 insertions, 66 deletions
diff --git a/lib/irc/admin_handler.ex b/lib/irc/admin_handler.ex index cb953db..39556fe 100644 --- a/lib/irc/admin_handler.ex +++ b/lib/irc/admin_handler.ex @@ -13,7 +13,7 @@ defmodule Nola.Irc.AdminHandler do end def init([client]) do - ExIRC.Client.add_handler client, self + ExIRC.Client.add_handler client, self() {:ok, _} = Registry.register(Nola.PubSub, "op", []) {:ok, client} end diff --git a/lib/irc/puppet_connection.ex b/lib/irc/puppet_connection.ex index 1c093ca..890b976 100644 --- a/lib/irc/puppet_connection.ex +++ b/lib/irc/puppet_connection.ex @@ -1,9 +1,9 @@ defmodule Nola.Irc.PuppetConnection do require Logger @min_backoff :timer.seconds(5) - @max_backoff :timer.seconds(2*60) + @max_backoff :timer.seconds(2 * 60) @max_idle :timer.hours(12) - @env Mix.env + @env Mix.env() defmodule Supervisor do use DynamicSupervisor @@ -13,7 +13,12 @@ defmodule Nola.Irc.PuppetConnection do end def start_child(%Nola.Account{id: account_id}, %Nola.Irc.Connection{id: connection_id}) do - spec = %{id: {account_id, connection_id}, start: {Nola.Irc.PuppetConnection, :start_link, [account_id, connection_id]}, restart: :transient} + spec = %{ + id: {account_id, connection_id}, + start: {Nola.Irc.PuppetConnection, :start_link, [account_id, connection_id]}, + restart: :transient + } + DynamicSupervisor.start_child(__MODULE__, spec) end @@ -27,29 +32,48 @@ defmodule Nola.Irc.PuppetConnection do end end - def whereis(account = %Nola.Account{id: account_id}, connection = %Nola.Irc.Connection{id: connection_id}) do + def whereis( + account = %Nola.Account{id: account_id}, + connection = %Nola.Irc.Connection{id: connection_id} + ) do {:global, name} = name(account_id, connection_id) + case :global.whereis_name(name) do :undefined -> nil pid -> pid end end - def send_message(account = %Nola.Account{id: account_id}, connection = %Nola.Irc.Connection{id: connection_id}, channel, text, meta) do + def send_message( + account = %Nola.Account{id: account_id}, + connection = %Nola.Irc.Connection{id: connection_id}, + channel, + text, + meta + ) do GenServer.cast(name(account_id, connection_id), {:send_message, self(), channel, text, meta}) end - def start_and_send_message(account = %Nola.Account{id: account_id}, connection = %Nola.Irc.Connection{id: connection_id}, channel, text, meta) do + def start_and_send_message( + account = %Nola.Account{id: account_id}, + connection = %Nola.Irc.Connection{id: connection_id}, + channel, + text, + meta + ) do {:global, name} = name(account_id, connection_id) pid = whereis(account, connection) - pid = if !pid do + + pid = + if !pid do case Nola.Irc.PuppetConnection.Supervisor.start_child(account, connection) do {:ok, pid} -> pid {:error, {:already_started, pid}} -> pid end - else - pid - end + else + pid + end + GenServer.cast(pid, {:send_message, self(), channel, text, meta}) end @@ -58,7 +82,9 @@ defmodule Nola.Irc.PuppetConnection do end def start_link(account_id, connection_id) do - GenServer.start_link(__MODULE__, [account_id, connection_id], name: name(account_id, connection_id)) + GenServer.start_link(__MODULE__, [account_id, connection_id], + name: name(account_id, connection_id) + ) end def name(account_id, connection_id) do @@ -69,40 +95,61 @@ defmodule Nola.Irc.PuppetConnection do account = %Nola.Account{} = Nola.Account.get(account_id) connection = %Nola.Irc.Connection{} = Nola.Irc.Connection.lookup(connection_id) Logger.metadata(puppet_conn: account.id <> "@" <> connection.id) - backoff = :backoff.init(@min_backoff, @max_backoff) - |> :backoff.type(:jitter) - idle = :erlang.send_after(@max_idle, self, :idle) - {:ok, %{client: nil, backoff: backoff, idle: idle, connected: false, buffer: [], channels: [], connection_id: connection_id, account_id: account_id, connected_server: nil, connected_port: nil, network: connection.network}, {:continue, :connect}} + + backoff = + :backoff.init(@min_backoff, @max_backoff) + |> :backoff.type(:jitter) + + idle = :erlang.send_after(@max_idle, self(), :idle) + + {:ok, + %{ + client: nil, + backoff: backoff, + idle: idle, + connected: false, + buffer: [], + channels: [], + connection_id: connection_id, + account_id: account_id, + connected_server: nil, + connected_port: nil, + network: connection.network + }, {:continue, :connect}} end def handle_continue(:connect, state) do - #ipv6 = if @env == :prod do + # ipv6 = if @env == :prod do # subnet = Nola.Subnet.assign(state.account_id) # Nola.Account.put_meta(Nola.Account.get(state.account_id), "subnet", subnet) # ip = Pfx.host(subnet, 1) # {:ok, ipv6} = :inet_parse.ipv6_address(to_charlist(ip)) # System.cmd("add-ip6", [ip]) # ipv6 - #end + # end conn = Nola.Irc.Connection.lookup(state.connection_id) - client_opts = [] - |> Keyword.put(:network, conn.network) - client = if state.client && Process.alive?(state.client) do - Logger.info("Reconnecting client") - state.client - else - Logger.info("Connecting") - {:ok, client} = ExIRC.Client.start_link(debug: false) - ExIRC.Client.add_handler(client, self()) - client - end + + client_opts = + [] + |> Keyword.put(:network, conn.network) + + client = + if state.client && Process.alive?(state.client) do + Logger.info("Reconnecting client") + state.client + else + Logger.info("Connecting") + {:ok, client} = ExIRC.Client.start_link(debug: false) + ExIRC.Client.add_handler(client, self()) + client + end base_opts = [ {:nodelay, true} ] - #{ip, opts} = case {ipv6, :inet_res.resolve(to_charlist(conn.host), :in, :aaaa)} do + # {ip, opts} = case {ipv6, :inet_res.resolve(to_charlist(conn.host), :in, :aaaa)} do # {ipv6, {:ok, {:dns_rec, _dns_header, _query, rrs = [{:dns_rr, _, _, _, _, _, _, _, _, _} | _], _, _}}} -> # ip = rrs # |> Enum.map(fn({:dns_rr, _, :aaaa, :in, _, _, ipv6, _, _, _}) -> ipv6 end) @@ -115,8 +162,8 @@ defmodule Nola.Irc.PuppetConnection do # ] # {ip, opts} # _ -> - {ip, opts} = {to_charlist(conn.host), []} - #end + {ip, opts} = {to_charlist(conn.host), []} + # end conn_fun = if conn.tls, do: :connect_ssl!, else: :connect! apply(ExIRC.Client, conn_fun, [client, ip, conn.port, base_opts ++ opts]) @@ -125,46 +172,76 @@ defmodule Nola.Irc.PuppetConnection do end def handle_continue(:connected, state) do - state = Enum.reduce(Enum.reverse(state.buffer), state, fn(b, state) -> - {:noreply, state} = handle_cast(b, state) - state - end) + state = + Enum.reduce(Enum.reverse(state.buffer), state, fn b, state -> + {:noreply, state} = handle_cast(b, state) + state + end) + {:noreply, %{state | buffer: []}} end - def handle_cast(cast = {:send_message, _pid, _channel, _text, _meta}, state = %{connected: false, buffer: buffer}) do + def handle_cast( + cast = {:send_message, _pid, _channel, _text, _meta}, + state = %{connected: false, buffer: buffer} + ) do {:noreply, %{state | buffer: [cast | buffer]}} end def handle_cast({:send_message, pid, channel, text, pub_meta}, state = %{connected: true}) do - channels = if !Enum.member?(state.channels, channel) do - ExIRC.Client.join(state.client, channel) - [channel | state.channels] - else - state.channels - end + channels = + if !Enum.member?(state.channels, channel) do + ExIRC.Client.join(state.client, channel) + [channel | state.channels] + else + state.channels + end + ExIRC.Client.msg(state.client, :privmsg, channel, text) meta = %{puppet: true, from: pid, origin: Keyword.get(pub_meta, :origin, __MODULE__)} account = Nola.Account.get(state.account_id) nick = make_nick(state) - sender = %ExIRC.SenderInfo{network: state.network, nick: suffix_nick(nick), user: nick, host: "puppet."} - reply_fun = fn(text) -> + + sender = %ExIRC.SenderInfo{ + network: state.network, + nick: suffix_nick(nick), + user: nick, + host: "puppet." + } + + reply_fun = fn text -> Nola.Irc.Connection.broadcast_message(state.network, channel, text, pub_meta) end - message = %Nola.Message{id: FlakeId.get(), at: NaiveDateTime.utc_now(), text: text, network: state.network, account: account, sender: sender, channel: channel, replyfun: reply_fun, trigger: Nola.Irc.Connection.extract_trigger(text), meta: meta} - message = case Nola.UserTrack.messaged(message) do - :ok -> message - {:ok, message} -> message - end + + message = %Nola.Message{ + id: FlakeId.get(), + at: NaiveDateTime.utc_now(), + text: text, + network: state.network, + account: account, + sender: sender, + channel: channel, + replyfun: reply_fun, + trigger: Nola.Irc.Connection.extract_trigger(text), + meta: meta + } + + message = + case Nola.UserTrack.messaged(message) do + :ok -> message + {:ok, message} -> message + end + Nola.Irc.Connection.publish(message, ["#{message.network}/#{channel}:messages"]) - idle = if length(state.buffer) == 0 do - :erlang.cancel_timer(state.idle) - :erlang.send_after(@max_idle, self(), :idle) - else - state.idle - end + idle = + if length(state.buffer) == 0 do + :erlang.cancel_timer(state.idle) + :erlang.send_after(@max_idle, self(), :idle) + else + state.idle + end {:noreply, %{state | idle: idle, channels: channels}} end @@ -177,7 +254,7 @@ defmodule Nola.Irc.PuppetConnection do def handle_info(:disconnected, state) do {delay, backoff} = :backoff.fail(state.backoff) - Logger.info("#{inspect(self())} Disconnected -- reconnecting in #{inspect delay}ms") + Logger.info("#{inspect(self())} Disconnected -- reconnecting in #{inspect(delay)}ms") Process.send_after(self(), :connect, delay) {:noreply, %{state | connected: false, backoff: backoff}} end @@ -188,10 +265,18 @@ defmodule Nola.Irc.PuppetConnection do # Connection successful def handle_info({:connected, server, port}, state) do - Logger.info("#{inspect(self())} Connected to #{inspect(server)}:#{port} #{inspect state}") + Logger.info("#{inspect(self())} Connected to #{inspect(server)}:#{port} #{inspect(state)}") {_, backoff} = :backoff.succeed(state.backoff) base_nick = make_nick(state) - ExIRC.Client.logon(state.client, "", suffix_nick(base_nick), base_nick, "#{base_nick}'s puppet") + + ExIRC.Client.logon( + state.client, + "", + suffix_nick(base_nick), + base_nick, + "#{base_nick}'s puppet" + ) + {:noreply, %{state | backoff: backoff, connected_server: server, connected_port: port}} end @@ -199,8 +284,17 @@ defmodule Nola.Irc.PuppetConnection do def handle_info(:logged_in, state) do Logger.info("#{inspect(self())} Logged in") {_, backoff} = :backoff.succeed(state.backoff) + # Create an UserTrack entry for the client so it's authenticated to the right account_id already. - Nola.UserTrack.connected(state.network, suffix_nick(make_nick(state)), make_nick(state), "puppet.", state.account_id, %{puppet: true}) + Nola.UserTrack.connected( + state.network, + suffix_nick(make_nick(state)), + make_nick(state), + "puppet.", + state.account_id, + %{puppet: true} + ) + {:noreply, %{state | backoff: backoff}} end @@ -222,19 +316,21 @@ defmodule Nola.Irc.PuppetConnection do account = Nola.Account.get(state.account_id) user = Nola.UserTrack.find_by_account(state.network, account) base_nick = if(user, do: user.nick, else: account.name) - clean_nick = case String.split(base_nick, ":", parts: 2) do - ["@"<>nick, _] -> nick - [nick] -> nick - end + + clean_nick = + case String.split(base_nick, ":", parts: 2) do + ["@" <> nick, _] -> nick + [nick] -> nick + end + clean_nick |> :unicode.characters_to_nfd_binary() |> String.replace(~r/[^a-z0-9]/, "") end - if Mix.env == :dev do + if Mix.env() == :dev do def suffix_nick(nick), do: "#{nick}[d]" else def suffix_nick(nick), do: "#{nick}[p]" end - end |