2020-06-13 18:01:40 -04:00
|
|
|
defmodule Omnibot.Core do
|
2020-08-12 15:33:04 -07:00
|
|
|
require Logger
|
2020-07-12 12:19:14 -07:00
|
|
|
use Omnibot.Plugin
|
2020-08-12 15:33:04 -07:00
|
|
|
alias Omnibot.{Config, Irc, Util}
|
2020-06-13 18:01:40 -04:00
|
|
|
|
2020-08-12 18:25:24 -07:00
|
|
|
@default_config quit_after: 180, ping_after: 60, channels: :all
|
2020-08-18 16:41:27 -07:00
|
|
|
|
|
|
|
|
## Client API
|
|
|
|
|
|
|
|
|
|
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_reply() do
|
|
|
|
|
state().last_reply
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp update_last_reply(last_reply) do
|
|
|
|
|
update_state(fn cfg -> %{cfg | last_reply: last_reply} end)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
## Server callbacks
|
2020-08-12 15:33:04 -07:00
|
|
|
|
|
|
|
|
@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
|
2020-06-13 21:47:46 -04:00
|
|
|
|
2020-06-13 18:45:02 -04:00
|
|
|
@impl true
|
2020-06-13 18:01:40 -04:00
|
|
|
def on_join(irc, channel, nick) do
|
2020-08-11 12:52:37 -07:00
|
|
|
cfg = Irc.cfg(irc)
|
2020-06-13 18:01:40 -04:00
|
|
|
if nick == cfg.nick do
|
2020-06-13 18:45:02 -04:00
|
|
|
add_channel(channel)
|
2020-06-13 18:01:40 -04:00
|
|
|
# Sync if we join a channel we shouldn't be in
|
2020-08-11 16:00:09 -07:00
|
|
|
if channel in Config.all_channels(cfg),
|
2020-06-13 18:45:02 -04:00
|
|
|
do: sync_channels(irc)
|
2020-06-13 18:01:40 -04:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-06-13 18:45:02 -04:00
|
|
|
@impl true
|
2020-06-13 18:01:40 -04:00
|
|
|
def on_part(irc, channel, nick) do
|
2020-08-11 12:52:37 -07:00
|
|
|
cfg = Irc.cfg(irc)
|
2020-06-13 18:01:40 -04:00
|
|
|
if nick == cfg.nick do
|
2020-06-13 18:45:02 -04:00
|
|
|
remove_channel(channel)
|
2020-06-13 18:01:40 -04:00
|
|
|
# Sync if we join a channel we forcibly part a channel we shouldn't leave
|
2020-08-11 16:00:09 -07:00
|
|
|
if channel in Config.all_channels(cfg),
|
2020-06-13 18:45:02 -04:00
|
|
|
do: sync_channels(irc)
|
2020-06-13 18:01:40 -04:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-06-13 18:45:02 -04:00
|
|
|
@impl true
|
2020-06-13 18:01:40 -04:00
|
|
|
def on_kick(irc, channel, _nick, target) do
|
2020-08-11 12:52:37 -07:00
|
|
|
cfg = Irc.cfg(irc)
|
2020-06-13 18:01:40 -04:00
|
|
|
if target == cfg.nick do
|
2020-06-13 18:45:02 -04:00
|
|
|
remove_channel(channel)
|
2020-06-13 18:01:40 -04:00
|
|
|
# Generally, being kicked is not intentionally leaving a channel, so always sync here
|
2020-06-13 18:45:02 -04:00
|
|
|
sync_channels(irc)
|
2020-06-13 18:01:40 -04:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-08-12 15:33:04 -07:00
|
|
|
@impl true
|
|
|
|
|
def on_msg(irc, :connect) do
|
|
|
|
|
on_connect(irc)
|
|
|
|
|
end
|
|
|
|
|
|
2020-06-13 18:01:40 -04:00
|
|
|
@impl true
|
|
|
|
|
def on_msg(irc, msg) do
|
|
|
|
|
case String.upcase(msg.command) do
|
2020-06-13 18:45:02 -04:00
|
|
|
"001" -> sync_channels(irc)
|
2020-08-12 15:33:04 -07:00
|
|
|
"PING" ->
|
|
|
|
|
Irc.send_msg(irc, "PONG", msg.params)
|
2020-08-12 18:25:24 -07:00
|
|
|
update_last_reply(Util.now_unix())
|
2020-06-13 18:01:40 -04:00
|
|
|
_ -> route_msg(irc, msg)
|
|
|
|
|
end
|
2020-08-12 18:25:24 -07:00
|
|
|
update_last_reply(Util.now_unix())
|
2020-06-13 18:01:40 -04:00
|
|
|
end
|
2020-06-13 18:45:02 -04:00
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
|
def on_init(_cfg) do
|
2020-08-12 18:25:24 -07:00
|
|
|
%{channels: MapSet.new(), last_reply: Util.now_unix()}
|
2020-06-13 18:45:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp sync_channels(irc) do
|
2020-08-11 13:30:32 -07:00
|
|
|
cfg = Irc.cfg(irc)
|
|
|
|
|
desired = MapSet.new(Config.all_channels(cfg))
|
2020-08-11 16:17:42 -07:00
|
|
|
present = state().channels
|
2020-06-13 18:45:02 -04:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2020-08-18 16:41:27 -07:00
|
|
|
## Ping watcher worker
|
2020-06-13 18:45:02 -04:00
|
|
|
|
2020-08-12 15:33:04 -07:00
|
|
|
defp ping_watcher(irc) do
|
2020-08-12 18:25:24 -07:00
|
|
|
since_reply = Util.now_unix() - last_reply()
|
2020-08-12 15:33:04 -07:00
|
|
|
ping_after = cfg(:ping_after)
|
2020-08-12 18:25:24 -07:00
|
|
|
quit_after = cfg(:quit_after)
|
2020-08-12 15:33:04 -07:00
|
|
|
cond do
|
|
|
|
|
# Kill IRC instance
|
2020-08-12 18:25:24 -07:00
|
|
|
since_reply >= quit_after ->
|
|
|
|
|
Logger.error("IRC has not replied in #{quit_after}")
|
2020-08-12 15:33:04 -07:00
|
|
|
Process.exit(irc, :ping_timeout)
|
|
|
|
|
|
|
|
|
|
# Send ping message
|
2020-08-12 18:25:24 -07:00
|
|
|
# use == since >= will ping each time until a reply is received
|
|
|
|
|
since_reply == ping_after ->
|
|
|
|
|
update_last_reply(Util.now_unix())
|
2020-08-12 15:33:04 -07:00
|
|
|
Irc.send_msg(irc, "PING", "omnibot")
|
|
|
|
|
|
|
|
|
|
true -> nil
|
|
|
|
|
end
|
|
|
|
|
Process.sleep(1000)
|
|
|
|
|
ping_watcher(irc)
|
|
|
|
|
end
|
2020-06-13 18:01:40 -04:00
|
|
|
end
|
|
|
|
|
|