Created
June 5, 2019 18:58
-
-
Save Awlexus/41ee5c1cb58c29d651d7746cb3a43d59 to your computer and use it in GitHub Desktop.
A very heavy macro based commander module for Nosturm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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