Skip to content

Instantly share code, notes, and snippets.

@Awlexus
Created June 5, 2019 18:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Awlexus/41ee5c1cb58c29d651d7746cb3a43d59 to your computer and use it in GitHub Desktop.
Save Awlexus/41ee5c1cb58c29d651d7746cb3a43d59 to your computer and use it in GitHub Desktop.
A very heavy macro based commander module for Nosturm
defmodule Nostrum.Commander do
@params [
:arguments,
:channel,
:channel_permissions,
:dm_channel,
:guild,
:me,
:message,
:permissions,
:user
]
defmacro __using__(opts) do
module = __CALLER__.module
Module.register_attribute(module, :commands, accumulate: true)
prefix = Keyword.get(opts, :prefix, "!")
quote do
alias Nostrum.Commander
require Commander
alias Nostrum.Api
@prefix unquote(prefix)
@before_compile Nostrum.Commander
end
end
defmacro __before_compile__(env) do
module = env.module
prefix = Module.get_attribute(module, :prefix)
commands =
module
|> Module.get_attribute(:commands)
|> Enum.map(&{&1, prefix})
quote do
defmacro __using__(_env) do
unquote(commands)
end
def prefix do
@prefix
end
end
end
defmacro def(function, body) do
body = blockify(body)
{name, function} = inject(function, body)
quote do
@commands unquote(name)
unquote(function)
end
end
def clause({_module, []}), do: []
def clause({module, {function, prefix}}) do
match_string = prefix <> to_string(function)
quote do
unquote(match_string) <> rest ->
unquote(module).unquote(function)(var!(message), String.trim(rest))
end
end
defmacro load_commands(modules, content, do: other_blocks) when is_list(modules) do
other_blocks = blockify_clauses(other_blocks)
clauses =
modules
|> Enum.map(&unescape_quoted_module/1)
|> Enum.map(&use_module/1)
|> List.flatten()
|> Enum.map(&clause/1)
|> List.flatten()
|> Enum.uniq()
|> Kernel.++(other_blocks)
q =
quote do
case unquote(content) do
unquote(clauses)
end
end
Macro.to_string(q) |> String.split("\n") |> Enum.each(&IO.puts/1)
q
end
defp use_module(module) do
Code.ensure_compiled(module)
{commands, []} = Code.eval_string("use #{module}")
Enum.map(commands, &{module, &1})
end
defp unescape_quoted_module({_, [alias: module], _}), do: module
defp unescape_quoted_module({_, _, list}), do: Module.concat(list)
defp blockify(block) do
{:ok, agent} = Agent.start(fn -> [] end)
block =
Macro.postwalk(block, fn
{atom, _, _} = tuple when atom in @params ->
Agent.update(agent, fn state -> [atom | state] end)
{:var!, [], [tuple]}
v ->
v
end)
|> case do
[do: {:__block__, [], block}] ->
block
[do: block] ->
List.wrap(block)
end
used_vars = Agent.get(agent, & &1)
Agent.stop(agent)
# Make sure that guild is automatically included if we need to access the channel
used_vars =
used_vars
|> Enum.uniq()
|> add_needed_vars()
|> Enum.uniq()
|> create_vars()
alias_api = [
quote do
alias Nostrum.Api
end
]
[do: {:__block__, [], alias_api ++ used_vars ++ block}]
end
defp blockify_clauses(clauses) do
Macro.postwalk(clauses, fn
{:message, _, _} = tuple ->
{:var!, [], [tuple]}
v ->
v
end)
end
defp create_vars(list) do
Enum.map(list, fn
:user ->
quote do
var!(user) =
if var!(guild) do
Map.get(var!(guild).members, var!(message).author.id)
else
Nostrum.Cache.UserCache.get(var!(message).author.id)
end
end
:channel ->
quote do
var!(channel) =
if var!(guild) do
Map.get(var!(guild).channels, var!(message).channel_id)
else
{:ok, channel} = Nostrum.Cache.ChannelCache.get(var!(message).channel_id)
channel
end
# {:ok, var!(channel)} = Api.get_channel(var!(message).channel_id)
end
:guild ->
quote do
var!(guild) =
if var!(message).guild_id do
case Nostrum.Cache.GuildCache.get(var!(message).guild_id) do
{:ok, guild} ->
guild
{:error, _} ->
nil
end
else
nil
end
# {:ok, var!(guild)} = Api.get_guild(var!(message).guild_id)
end
:permissions ->
quote do
var!(permissions) =
if var!(guild) do
Nostrum.Struct.Guild.Member.guild_permissions(var!(user), var!(guild))
else
[]
end
end
:channel_permissions ->
quote do
var!(channel_permissions) =
if var!(guild) do
Nostrum.Struct.Guild.Member.guild_channel_permissions(
var!(user),
var!(guild),
var!(channel)
)
else
[]
end
end
:dm_channel ->
quote do
{:ok, var!(dm_channel)} = Nostrum.Api.create_dm(var!(message).author.id)
end
:me ->
quote do
user = Nostrum.Cache.Me.get()
var!(me) =
if var!(guild) do
var!(guild).members[user.id]
else
user
end
end
_other ->
nil
end)
|> Enum.filter(& &1)
end
defp add_needed_vars(vars) do
Enum.reduce(vars, vars, fn
:channel, acc -> [:guild | acc]
:channel_permissions, acc -> [:guild, :channel, :user] ++ acc
:me, acc -> [:guild | acc]
:permissions, acc -> [:guild, :user] ++ acc
:user, acc -> [:guild | acc]
_other, acc -> acc
end)
end
# Building
def inject({:when, meta, params}, body) do
[{name, _meta, arguments} | rest] = params
context = meta[:context]
arguments = arguments || [{:var!, [], [{:arguments, [], context}]}]
arguments = [
{:var!, [], [{:message, [], context}]}
| arguments
]
params = [{name, meta, arguments} | rest]
tuple = {:when, meta, params}
func = {:def, meta, [tuple, body]}
{name, func}
end
def inject({name, meta, arguments}, body) do
context = meta[:context]
arguments = arguments || [{:var!, [], [{:arguments, [], context}]}]
arguments = [
{:var!, [], [{:message, [], context}]}
| arguments
]
tuple = {name, meta, arguments}
func = {:def, meta, [tuple, body]}
{name, func}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment