From ed22d1bf0fd6dd60b1c1d3e405d52026b715f9ad Mon Sep 17 00:00:00 2001 From: Alek Ratzloff Date: Sat, 13 Jun 2020 15:20:00 -0400 Subject: [PATCH] Update modules to be more egonomic, add command macro Modules are now defined using on_{msg,channel_msg,join,part,kick}. Additionally, commands can be defined using a convenient macro. Signed-off-by: Alek Ratzloff --- lib/contrib/fortune.ex | 21 +++---- lib/module.ex | 133 ++++++++++++++++++++++++++++------------- lib/router.ex | 2 +- 3 files changed, 101 insertions(+), 55 deletions(-) diff --git a/lib/contrib/fortune.ex b/lib/contrib/fortune.ex index 0c3b2bf..2a8b107 100644 --- a/lib/contrib/fortune.ex +++ b/lib/contrib/fortune.ex @@ -17,20 +17,15 @@ defmodule Omnibot.Contrib.Fortune do "Godly Luck", ] - ## Client API - - def privmsg(module, channel, nick, line) do - GenServer.cast(module, {:privmsg, {channel, nick, line}}) + command "!fortune", [to] do + fortune = Enum.random(@fortunes) + reply = "#{to}: #{fortune}" + Irc.send_to(channel, reply) end - ## Server callbacks - - @impl true - def on_channel_msg(channel, nick, line) do - if line == "!fortune" do - fortune = Enum.random(@fortunes) - reply = "#{nick}: #{fortune}" - Irc.send_to(channel, reply) - end + command "!fortune" do + fortune = Enum.random(@fortunes) + reply = "#{nick}: #{fortune}" + Irc.send_to(channel, reply) end end diff --git a/lib/module.ex b/lib/module.ex index 18d0444..40c9c41 100644 --- a/lib/module.ex +++ b/lib/module.ex @@ -1,72 +1,123 @@ defmodule Omnibot.Module do + defmodule Hooks do + defmacro __before_compile__(_env) do + quote do + @impl true + def on_channel_msg(_channel, _nick, _line), do: nil + + @impl true + def on_channel_msg(_channel, _nick, _cmd, _params), do: nil + + @impl true + def on_join(_channel, _nick), do: nil + + @impl true + def on_part(_channel, _nick), do: nil + + @impl true + def on_kick(_channel, _nick), do: nil + end + end + end + defmacro __using__([]) do quote do - use GenServer + use Agent alias Omnibot.{Irc, Module} + import Omnibot.Module require Logger @behaviour Module - ## Client API - def start_link(opts) do - GenServer.start_link(__MODULE__, opts[:cfg], opts ++ [name: __MODULE__]) + Agent.start_link(fn -> opts[:cfg] end, 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} + def cfg do + Agent.get(__MODULE__, & &1) + end @impl Module def on_msg(msg) do + # TODO - instead of using a router for modules, consider using a PubSub with a Registry: + # https://hexdocs.pm/elixir/master/Registry.html#module-using-as-a-pubsub route_msg(msg) end - def route_msg(msg) do + defp 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 + "PRIVMSG" -> + [channel | params] = msg.params + line = Enum.join(params, " ") + + case String.split(line, " ") do + [cmd | params] -> on_channel_msg(channel, nick, cmd, params) + _ -> on_channel_msg(channel, nick, line) + end + + "JOIN" -> + [channel | _] = msg.params on_join(channel, nick) - "PART" -> [channel | _] = msg.params + + "PART" -> + [channel | _] = msg.params on_part(channel, nick) - "KICK" -> [channel | _] = msg.params + + "KICK" -> + [channel | _] = msg.params on_kick(channel, nick) - _ -> nil + + _ -> + 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 + + @before_compile Omnibot.Module.Hooks 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 + @callback on_channel_msg(channel :: String.t(), nick :: String.t(), line :: String.t()) :: any + @callback on_channel_msg( + channel :: String.t(), + nick :: String.t(), + cmd :: String.t(), + params :: [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 + + defmacro command(cmd, opts) do + quote generated: true do + @impl Omnibot.Module + def on_channel_msg(var!(channel), var!(nick), unquote(cmd), var!(params)) do + unquote(opts[:do]) + end + end + end + + defmacro command(cmd, params, opts) do + params = + Enum.map( + IO.inspect(params), + fn param -> + case param do + {_, _, _} -> quote(do: var!(unquote(param))) + lit -> Macro.escape(lit) + end + end + ) + + quote generated: true do + @impl Omnibot.Module + def on_channel_msg(var!(channel), var!(nick), unquote(cmd), unquote(params)) do + unquote(opts[:do]) + end + end + end end diff --git a/lib/router.ex b/lib/router.ex index adb55c0..8c8a5b2 100644 --- a/lib/router.ex +++ b/lib/router.ex @@ -33,7 +33,7 @@ defmodule Omnibot.Router do # Find modules that want this message State.cfg() |> Config.channel_modules(channel) - |> Enum.each(fn {module, _} -> module.msg(module, msg) end) + |> Enum.each(fn {module, _} -> module.on_msg(msg) end) end def handle(_irc, :join, %Msg {prefix: %Msg.Prefix{nick: nick}, params: [channel | _]}) do