Files
omnibot/lib/core.ex

134 lines
3.4 KiB
Elixir
Raw Normal View History

defmodule Omnibot.Core do
require Logger
use Omnibot.Plugin
alias Omnibot.{Config, Irc, Util}
@default_config ping_every: 60, ping_after: 60, channels: :all
@impl true
def children(_cfg) do
[{Task.Supervisor, name: Omnibot.Core.PingWatchers}]
end
@impl true
def on_connect(irc) do
Logger.info("Starting ping watcher")
Task.Supervisor.async(Omnibot.Core.PingWatchers, fn -> ping_watcher(irc) end)
end
@impl true
def on_join(irc, channel, nick) do
cfg = Irc.cfg(irc)
if nick == cfg.nick do
add_channel(channel)
# Sync if we join a channel we shouldn't be in
if channel in Config.all_channels(cfg),
do: sync_channels(irc)
end
end
@impl true
def on_part(irc, channel, nick) do
cfg = Irc.cfg(irc)
if nick == cfg.nick do
remove_channel(channel)
# Sync if we join a channel we forcibly part a channel we shouldn't leave
if channel in Config.all_channels(cfg),
do: sync_channels(irc)
end
end
@impl true
def on_kick(irc, channel, _nick, target) do
cfg = Irc.cfg(irc)
if target == cfg.nick do
remove_channel(channel)
# Generally, being kicked is not intentionally leaving a channel, so always sync here
sync_channels(irc)
end
end
@impl true
def on_msg(irc, :connect) do
on_connect(irc)
end
@impl true
def on_msg(irc, msg) do
case String.upcase(msg.command) do
"001" -> sync_channels(irc)
"PING" ->
Irc.send_msg(irc, "PONG", msg.params)
update_last_ping(Util.now_unix())
update_last_pong(Util.now_unix()) # also update pong because we ponged
"PONG" -> update_last_pong(Util.now_unix())
_ -> route_msg(irc, msg)
end
end
@impl true
def on_init(_cfg) do
%{channels: MapSet.new(), last_pong: Util.now_unix(), last_ping: Util.now_unix()}
end
defp sync_channels(irc) do
cfg = Irc.cfg(irc)
desired = MapSet.new(Config.all_channels(cfg))
present = state().channels
to_join = MapSet.difference(desired, present)
|> MapSet.to_list()
to_part = MapSet.difference(present, desired)
|> MapSet.to_list()
Enum.each(to_join, fn channel -> Irc.join(irc, channel) end)
Enum.each(to_part, fn channel -> Irc.part(irc, channel) end)
end
defp add_channel(channel) do
update_state(fn cfg = %{channels: channels} -> %{cfg | channels: MapSet.put(channels, channel)} end)
end
defp remove_channel(channel) do
update_state(fn cfg = %{channels: channels} -> %{cfg | channels: MapSet.delete(channels, channel)} end)
end
defp last_pong() do
state().last_pong
end
defp update_last_pong(last_pong) do
update_state(fn cfg -> %{cfg | last_pong: last_pong} end)
end
defp last_ping() do
state().last_ping
end
defp update_last_ping(last_ping) do
update_state(fn cfg -> %{cfg | last_ping: last_ping} end)
end
defp ping_watcher(irc) do
since_pong = Util.now_unix() - last_pong()
since_ping = Util.now_unix() - last_ping()
ping_every = cfg(:ping_every)
ping_after = cfg(:ping_after)
cond do
# Kill IRC instance
since_pong >= (3 * ping_every) ->
Logger.error("IRC has not replied in #{3 * ping_every}")
Process.exit(irc, :ping_timeout)
# Send ping message
since_pong >= ping_every and ping_after >= since_ping ->
Irc.send_msg(irc, "PING", "omnibot")
true -> nil
end
Process.sleep(1000)
ping_watcher(irc)
end
end