summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/irc/puppet_connection.ex157
-rw-r--r--lib/lsg/telegram_room.ex6
-rw-r--r--lib/lsg_irc.ex1
-rw-r--r--lib/lsg_irc/say_plugin.ex10
4 files changed, 166 insertions, 8 deletions
diff --git a/lib/irc/puppet_connection.ex b/lib/irc/puppet_connection.ex
new file mode 100644
index 0000000..88b4f8a
--- /dev/null
+++ b/lib/irc/puppet_connection.ex
@@ -0,0 +1,157 @@
+defmodule IRC.PuppetConnection do
+ require Logger
+ @min_backoff :timer.seconds(5)
+ @max_backoff :timer.seconds(2*60)
+ @max_idle :timer.minutes(30)
+
+ defmodule Supervisor do
+ use DynamicSupervisor
+
+ def start_link() do
+ DynamicSupervisor.start_link(__MODULE__, [], name: __MODULE__)
+ end
+
+ def start_child(%IRC.Account{id: account_id}, %IRC.Connection{id: connection_id}) do
+ spec = %{id: {account_id, connection_id}, start: {IRC.PuppetConnection, :start_link, [account_id, connection_id]}, restart: :transient}
+ DynamicSupervisor.start_child(__MODULE__, spec)
+ end
+
+ @impl true
+ def init(_init_arg) do
+ DynamicSupervisor.init(
+ strategy: :one_for_one,
+ max_restarts: 10,
+ max_seconds: 1
+ )
+ end
+ end
+
+ def send_message(account = %IRC.Account{id: account_id}, connection = %IRC.Connection{id: connection_id}, channel, text) do
+ pid = case IRC.PuppetConnection.Supervisor.start_child(account, connection) do
+ {:ok, pid} -> pid
+ {:error, {:already_started, pid}} -> pid
+ end
+ GenServer.cast(pid, {:send_message, channel, text})
+ end
+
+ def start_link(account_id, connection_id) do
+ GenServer.start_link(__MODULE__, [account_id, connection_id], name: name(account_id, connection_id))
+ end
+
+ def name(account_id, connection_id) do
+ {:global, {PuppetConnection, account_id, connection_id}}
+ end
+
+ def init([account_id, connection_id]) do
+ account = %IRC.Account{} = IRC.Account.get(account_id)
+ connection = %IRC.Connection{} = 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}}
+ end
+
+ def handle_continue(:connect, state) do
+ conn = 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
+ if conn.tls do
+ ExIRC.Client.connect_ssl!(client, conn.host, conn.port, [])#[{:ifaddr, {45,150,150,33}}])
+ else
+ ExIRC.Client.connect!(client, conn.host, conn.port, [])#[{:ifaddr, {45,150,150,33}}])
+ end
+ {:noreply, %{state | client: client}}
+ end
+
+ def handle_continue(:connected, state) do
+ state = Enum.reduce(state.buffer, state, fn(b, state) ->
+ {:noreply, state} = handle_cast(b, state)
+ state
+ end)
+ {:noreply, %{state | buffer: []}}
+ end
+
+ def handle_cast(cast = {:send_message, channel, text}, state = %{connected: false, buffer: buffer}) do
+ {:noreply, %{state | buffer: [cast | buffer]}}
+ end
+
+ def handle_cast({:send_message, channel, text}, 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
+ ExIRC.Client.msg(state.client, :privmsg, channel, text)
+
+ 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
+
+ def handle_info(:idle, state) do
+ ExIRC.Client.quit(state.client, "Puppet is idle for too long")
+ ExIRC.Client.stop!(state.client)
+ {:stop, :normal, state}
+ end
+
+ def handle_info(:disconnected, state) do
+ {delay, backoff} = :backoff.fail(state.backoff)
+ Logger.info("#{inspect(self())} Disconnected -- reconnecting in #{inspect delay}ms")
+ Process.send_after(self(), :connect, delay)
+ {:noreply, %{state | connected: false, backoff: backoff}}
+ end
+
+ def handle_info(:connect, state) do
+ {:noreply, state, {:continue, :connect}}
+ end
+
+ # Connection successful
+ def handle_info({:connected, server, port}, state) do
+ Logger.info("#{inspect(self())} Connected to #{server}:#{port} #{inspect state}")
+ {_, backoff} = :backoff.succeed(state.backoff)
+ account = IRC.Account.get(state.account_id)
+ user = IRC.UserTrack.find_by_account(state.network, account)
+ base_nick = if(user, do: user.nick, else: account.name)
+ nick = "#{base_nick}[p]"
+ ExIRC.Client.logon(state.client, "", nick, base_nick, "#{base_nick}'s puppet")
+ {:noreply, %{state | backoff: backoff, connected_server: server, connected_port: port}}
+ end
+
+ # Logon successful
+ def handle_info(:logged_in, state) do
+ Logger.info("#{inspect(self())} Logged in")
+ {_, backoff} = :backoff.succeed(state.backoff)
+ {:noreply, %{state | backoff: backoff}}
+ end
+
+ # ISUP
+ def handle_info({:isup, network}, state) do
+ {:noreply, %{state | network: network, connected: true}, {:continue, :connected}}
+ end
+
+ # Been kicked
+ def handle_info({:kicked, _sender, chan, _reason}, state) do
+ {:noreply, %{state | channels: state.channels -- [chan]}}
+ end
+
+ def handle_info(_info, state) do
+ {:noreply, state}
+ end
+
+end
diff --git a/lib/lsg/telegram_room.ex b/lib/lsg/telegram_room.ex
index 8c228e1..f973c58 100644
--- a/lib/lsg/telegram_room.ex
+++ b/lib/lsg/telegram_room.ex
@@ -22,10 +22,8 @@ defmodule LSG.TelegramRoom do
def handle_update(%{"message" => %{"from" => %{"id" => user_id}, "text" => text}}, _token, state) do
account = IRC.Account.find_meta_account("telegram-id", user_id)
- user = IRC.UserTrack.find_by_account(state.net, account)
- nick = if(user, do: user.nick, else: account.name)
- prefix = "<#{nick}> "
- IRC.Connection.broadcast_message(state.net, state.chan, "#{prefix}#{text}")
+ connection = IRC.Connection.get_network(state.net)
+ IRC.PuppetConnection.send_message(account, connection, state.chan, text)
{:ok, state}
end
diff --git a/lib/lsg_irc.ex b/lib/lsg_irc.ex
index ba0828a..c2782ad 100644
--- a/lib/lsg_irc.ex
+++ b/lib/lsg_irc.ex
@@ -21,6 +21,7 @@ defmodule LSG.IRC do
worker(IRC.Account.AccountPlugin, []),
supervisor(IRC.Plugin.Supervisor, [], [name: IRC.Plugin.Supervisor]),
supervisor(IRC.Connection.Supervisor, [], [name: IRC.Connection.Supervisor]),
+ supervisor(IRC.PuppetConnection.Supervisor, [], [name: IRC.PuppetConnection.Supervisor]),
]
end
diff --git a/lib/lsg_irc/say_plugin.ex b/lib/lsg_irc/say_plugin.ex
index 690d0a6..c385e77 100644
--- a/lib/lsg_irc/say_plugin.ex
+++ b/lib/lsg_irc/say_plugin.ex
@@ -61,10 +61,12 @@ defmodule LSG.IRC.SayPlugin do
for {net, chan} <- IRC.Membership.of_account(account) do
chan2 = String.replace(chan, "#", "")
if (target == "#{net}/#{chan}" || target == "#{net}/#{chan2}" || target == chan || target == chan2) do
- user = IRC.UserTrack.find_by_account(net, account)
- nick = if(user, do: user.nick, else: account.name)
- prefix = if(with_nick?, do: "<#{nick}> ", else: "")
- IRC.Connection.broadcast_message(net, chan, "#{prefix}#{text}")
+ if with_nick? do
+ connection = IRC.Connection.get_network(net)
+ IRC.PuppetConnection.send_message(account, connection, chan, text)
+ else
+ IRC.Connection.broadcast_message(net, chan, text)
+ end
end
end
end