WIP: Move to using modules for implementing core behavior

Since modules can now intercept all messages in the channels they're
listening for, it'd be cool to have modules handling things like making
sure the Omnibot.State stays updated as appropriate, and that pings are
ponged, etc.

This will probably deprecate the router, since it's been reduced to a
single function call, but we'll see about that.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2020-06-13 16:04:19 -04:00
parent 5f251d22ce
commit 73c5f58243
5 changed files with 65 additions and 60 deletions

View File

@@ -17,7 +17,10 @@ defmodule Omnibot.Config do
Gets all channels that the bot should join via its modules. Gets all channels that the bot should join via its modules.
""" """
def all_channels(cfg) do def all_channels(cfg) do
Enum.flat_map(cfg.modules, fn {_, [channels: channels]} -> channels end) Enum.flat_map(cfg.modules, fn
{_, [channels: :all]} -> []
{_, [channels: channels]} -> channels
end)
|> MapSet.new() |> MapSet.new()
|> MapSet.to_list() |> MapSet.to_list()
end end
@@ -28,7 +31,9 @@ defmodule Omnibot.Config do
""" """
def channel_modules(cfg, channel) do def channel_modules(cfg, channel) do
cfg.modules cfg.modules
|> Enum.filter(fn {_, cfg} -> Enum.member?(cfg[:channels] || [], channel) end) |> Enum.filter(fn {_, cfg} ->
cfg[:channels] == :all or Enum.member?(cfg[:channels] || [], channel)
end)
end end
def msg_prefix(cfg) do def msg_prefix(cfg) do

11
lib/core/join.ex Normal file
View File

@@ -0,0 +1,11 @@
defmodule Omnibot.Core.Join do
use Omnibot.Module
alias Omnibot.State
def on_join(channel, nick) do
cfg = State.cfg()
if nick == cfg.nick do
State.add_channel(channel)
end
end
end

View File

@@ -65,6 +65,17 @@ defmodule Omnibot.Irc.Msg do
params: params, params: params,
} }
end end
@doc "Gets the channel that the given message is targeting, if any."
def channel(msg) do
case String.upcase(msg.command) do
"PRIVMSG" -> Enum.at(msg.params, 0)
"JOIN" -> Enum.at(msg.params, 0)
"PART" -> Enum.at(msg.params, 0)
"KICK" -> Enum.at(msg.params, 0)
_ -> nil
end
end
end end
defimpl String.Chars, for: Irc.Msg.Prefix do defimpl String.Chars, for: Irc.Msg.Prefix do

View File

@@ -1,7 +1,7 @@
defmodule Omnibot.Module do defmodule Omnibot.Module do
defmodule Hooks do defmodule Hooks do
defmacro __before_compile__(_env) do defmacro __before_compile__(_env) do
quote do quote generated: true do
@impl true @impl true
def on_channel_msg(_channel, _nick, _line), do: nil def on_channel_msg(_channel, _nick, _line), do: nil

View File

@@ -2,70 +2,48 @@ defmodule Omnibot.Router do
require Logger require Logger
alias Omnibot.{Config, Irc, Irc.Msg, State} alias Omnibot.{Config, Irc, Irc.Msg, State}
def route(irc, msg) do def route(_irc, msg) do
# TODO - consider removing this check and specific handling into the `use Omnibot.Module` block channel = Msg.channel(msg)
# PROS:
# - Don't have to determine message command twice (first here, second in Omnibot.Module)
# - Allows for much more powerful modules. JOIN, PART, KICK, etc handlers would be modules themselves
# - This is an extremely big win IMO
#
# CONS:
# - All routed functionality needs to be in a module
# - This may get a little old
# - A failed command could cause important messages to be missed (?)
# - Do messages in a named PID's mailbox persist after that PID goes away? My guess is "no"
# - To get around this, there could be a mailbox for "special" commands, probably using ETS
#
case String.upcase(msg.command) do
"PRIVMSG" -> handle(irc, :privmsg, msg)
"JOIN" -> handle(irc, :join, msg)
"KICK" -> handle(irc, :kick, msg)
"PART" -> handle(irc, :part, msg)
"PING" -> handle(irc, :ping, msg)
"001" -> handle(irc, :welcome, msg)
_ -> nil
end
end
def handle(_irc, :privmsg, msg) do
[channel | _params] = msg.params
# Find modules that want this message
State.cfg() State.cfg()
|> Config.channel_modules(channel) |> Config.channel_modules(channel)
|> Enum.each(fn {module, _} -> module.on_msg(msg) end) |> Enum.each(fn {module, _} -> module.on_msg(msg) end)
end end
def handle(_irc, :join, %Msg {prefix: %Msg.Prefix{nick: nick}, params: [channel | _]}) do #def handle(_irc, :privmsg, msg) do
cfg = State.cfg() # [channel | _params] = msg.params
if nick == cfg.nick do
State.add_channel(channel)
end
end
def handle(irc, :kick, %Msg {params: [channel, who | _]}) do # # Find modules that want this message
cfg = State.cfg() # State.cfg()
if who == cfg.nick do # |> Config.channel_modules(channel)
State.remove_channel(State, channel) # |> Enum.each(fn {module, _} -> module.on_msg(msg) end)
# sync_channels here because this is a state that we (should) not have put ourselves into #end
Irc.sync_channels(irc)
end
end
def handle(_irc, :part, %Msg {prefix: %Msg.Prefix{nick: nick}, params: [channel | _]}) do #def handle(_irc, :join, msg: %Msg {params: [channel | _]}) do
cfg = State.cfg() # State.cfg()
if nick == cfg.nick do # |> Config.channel_modules(channel)
State.remove_channel(State, channel) # |> Enum.each(fn {module, _} -> module.on_join(msg) end)
end #end
end
def handle(irc, :ping, msg) do #def handle(irc, :kick, msg: %Msg {params: [channel | _]}) do
cfg = State.cfg() # State.cfg()
reply = Config.msg(cfg, "PONG", msg.params) # |> Config.channel_modules(channel)
Irc.send_msg(irc, reply) # |> Enum.each(fn {module, _} -> module.on_kick(msg) end)
end #end
def handle(irc, :welcome, _msg) do #def handle(_irc, :part, %Msg {prefix: %Msg.Prefix{nick: nick}, params: [channel | _]}) do
Irc.sync_channels(irc) # cfg = State.cfg()
end # if nick == cfg.nick do
# State.remove_channel(State, channel)
# end
#end
#def handle(irc, :ping, msg) do
# cfg = State.cfg()
# reply = Config.msg(cfg, "PONG", msg.params)
# Irc.send_msg(irc, reply)
#end
#def handle(irc, :welcome, _msg) do
# Irc.sync_channels(irc)
#end
end end