diff --git a/lib/contrib/fortune.ex b/lib/contrib/fortune.ex index ade079f..0c3b2bf 100644 --- a/lib/contrib/fortune.ex +++ b/lib/contrib/fortune.ex @@ -1,7 +1,5 @@ defmodule Omnibot.Contrib.Fortune do - use GenServer - alias Omnibot.Irc - require Logger + use Omnibot.Module @fortunes [ "Reply hazy, try again", @@ -21,10 +19,6 @@ defmodule Omnibot.Contrib.Fortune do ## Client API - def start_link(opts) do - GenServer.start_link(__MODULE__, opts[:cfg], opts) - end - def privmsg(module, channel, nick, line) do GenServer.cast(module, {:privmsg, {channel, nick, line}}) end @@ -32,26 +26,11 @@ defmodule Omnibot.Contrib.Fortune do ## Server callbacks @impl true - def init(cfg) do - #Logger.debug("Starting fortune module") - #IO.inspect(self()) - {:ok, cfg} - end - - @impl true - def handle_call(:unload, _from, cfg) do - Logger.info("Unloading") - {:reply, :ok, cfg} - end - - @impl true - def handle_cast({:privmsg, {channel, nick, line}}, cfg) do - if IO.inspect(line) == "!fortune" do + def on_channel_msg(channel, nick, line) do + if line == "!fortune" do fortune = Enum.random(@fortunes) reply = "#{nick}: #{fortune}" - Irc.send_to(Irc, channel, reply) + Irc.send_to(channel, reply) end - - {:noreply, cfg} end end diff --git a/lib/irc.ex b/lib/irc.ex index d332a8e..b6b7ec6 100644 --- a/lib/irc.ex +++ b/lib/irc.ex @@ -22,12 +22,16 @@ defmodule Omnibot.Irc do def send_msg(irc, command, param), do: send_msg(irc, command, [param]) + def send_to(channel, text), do: send_to(__MODULE__, channel, text) def send_to(irc, channel, text), do: send_msg(irc, "PRIVMSG", [channel, text]) + def join(channel), do: join(__MODULE__, channel) def join(irc, channel), do: send_msg(irc, "JOIN", channel) + def part(channel), do: part(__MODULE__, channel) def part(irc, channel), do: send_msg(irc, "PART", channel) + def sync_channels(), do: sync_channels(__MODULE__) def sync_channels(irc), do: GenServer.cast(irc, :sync_channels) ## Server callbacks diff --git a/lib/module.ex b/lib/module.ex new file mode 100644 index 0000000..18d0444 --- /dev/null +++ b/lib/module.ex @@ -0,0 +1,72 @@ +defmodule Omnibot.Module do + defmacro __using__([]) do + quote do + use GenServer + alias Omnibot.{Irc, Module} + require Logger + + @behaviour Module + + ## Client API + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts[:cfg], opts ++ [name: __MODULE__]) + end + + def msg(msg), do: GenServer.cast(__MODULE__, msg) + def msg(module, msg), do: GenServer.cast(module, {:msg, msg}) + + ## Server callbacks + + @impl GenServer + def init(cfg), do: {:ok, cfg} + + @impl Module + def on_msg(msg) do + route_msg(msg) + end + + def route_msg(msg) do + nick = msg.prefix.nick + case String.upcase(msg.command) do + "PRIVMSG" -> [channel | text] = msg.params + on_channel_msg(channel, nick, Enum.join(text, " ")) + "JOIN" -> [channel | _] = msg.params + on_join(channel, nick) + "PART" -> [channel | _] = msg.params + on_part(channel, nick) + "KICK" -> [channel | _] = msg.params + on_kick(channel, nick) + _ -> nil + end + end + + @impl Module + def on_channel_msg(_channel, _nick, _line), do: nil + + @impl Module + def on_join(_channel, _nick), do: nil + + @impl Module + def on_part(_channel, _nick), do: nil + + @impl Module + def on_kick(_channel, _nick), do: nil + + @impl GenServer + def handle_cast({:msg, msg}, cfg) do + on_msg(msg) + {:noreply, cfg} + end + + defoverridable Module + defoverridable GenServer + end + end + + @callback on_msg(msg :: %Omnibot.Irc.Msg{}) :: any + @callback on_channel_msg(channel :: String.t, nick :: String.t, line :: String.t) :: any + @callback on_join(channel :: String.t, nick :: String.t) :: any + @callback on_part(channel :: String.t, nick :: String.t) :: any + @callback on_kick(channel :: String.t, nick :: String.t) :: any +end diff --git a/lib/router.ex b/lib/router.ex index 8fb24ce..adb55c0 100644 --- a/lib/router.ex +++ b/lib/router.ex @@ -3,6 +3,19 @@ defmodule Omnibot.Router do alias Omnibot.{Config, Irc, Irc.Msg, State} def route(irc, msg) do + # TODO - consider removing this check and specific handling into the `use Omnibot.Module` block + # 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) @@ -15,15 +28,12 @@ defmodule Omnibot.Router do end def handle(_irc, :privmsg, msg) do - # TODO : get channel, pass along to modules - [channel | params] = msg.params - line = Enum.join(params, " ") - nick = msg.prefix.nick + [channel | _params] = msg.params # Find modules that want this message State.cfg() |> Config.channel_modules(channel) - |> Enum.each(fn {module, _} -> module.privmsg(module, channel, nick, line) end) + |> Enum.each(fn {module, _} -> module.msg(module, msg) end) end def handle(_irc, :join, %Msg {prefix: %Msg.Prefix{nick: nick}, params: [channel | _]}) do