diff --git a/config/config.exs b/config/config.exs index 23cccb6..3c9373e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,6 +12,10 @@ config :fleet_bot, FleetBot.Discord, discord_allowed_langs: ~w(ar da vi hu uk lt ja hi tr cs th zh-TW el pl zh-CN ko de en-GB fi id ru sv-SE bg es-ES it pt-BR nl ro fr no hr he en-US) +config :fleet_bot, FleetBot.Fleetyards, + api_url: "https://api.fleetyards.net", + client: FleetBot.Fleetyards.Client + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{config_env()}.exs" diff --git a/lib/fleet_bot/application.ex b/lib/fleet_bot/application.ex index fd7a1ed..52fde25 100644 --- a/lib/fleet_bot/application.ex +++ b/lib/fleet_bot/application.ex @@ -6,7 +6,7 @@ defmodule FleetBot.Application do def start(_type, _args) do children = [ FleetBot.Repo, - FleetBot.Discord.Commands.RegisterManager, + FleetBot.Discord.Commands, FleetBot.Discord ] diff --git a/lib/fleet_bot/discord.ex b/lib/fleet_bot/discord.ex index 001c32c..37cd14b 100644 --- a/lib/fleet_bot/discord.ex +++ b/lib/fleet_bot/discord.ex @@ -5,13 +5,14 @@ defmodule FleetBot.Discord do import Ecto.Changeset @spec chat_command_allowed_regex() :: Regex.t() - @moduledoc """ + @doc """ Regex matcher for the discord allowed command string. Converted from discords `^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$` Regex. See: https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-naming """ + alias FleetBot.Discord.Commands.RegisterManager alias Nostrum.Consumer def chat_command_allowed_regex, do: ~R/^[-_\p{L}\p{N}\p{Devanagari}\p{Thai}]{1,32}$/u @@ -44,7 +45,25 @@ defmodule FleetBot.Discord do else: changeset end + @spec get_subcommand_name( + %Nostrum.Struct.Interaction{} + | %Nostrum.Struct.ApplicationCommandInteractionData{} + | [%Nostrum.Struct.ApplicationCommandInteractionDataOption{}] + | %Nostrum.Struct.ApplicationCommandInteractionDataOption{} + ) :: String.t() + def get_subcommand_name(%Nostrum.Struct.Interaction{data: data}), do: get_subcommand_name(data) + + def get_subcommand_name(%Nostrum.Struct.ApplicationCommandInteractionData{options: options}), + do: get_subcommand_name(options) + + def get_subcommand_name([option | _]), do: get_subcommand_name(option) + + def get_subcommand_name(%Nostrum.Struct.ApplicationCommandInteractionDataOption{name: name}), + do: name + + ## Consumer impl use Nostrum.Consumer + use FleetBot.Gettext def start_link, do: Consumer.start_link(__MODULE__) @@ -54,6 +73,24 @@ defmodule FleetBot.Discord do :noop end + @impl Nostrum.Consumer + def handle_event( + {:INTERACTION_CREATE, + %Nostrum.Struct.Interaction{ + data: %Nostrum.Struct.ApplicationCommandInteractionData{name: name} + } = interaction, _ws_state} + ) + when is_binary(name) do + RegisterManager.get_module(name) + |> case do + module when is_atom(module) -> + apply(module, :command, [name, interaction]) + + nil -> + LGettext.error("No module found for command `%{command}`", command: name) + end + end + @impl true def handle_event(_event) do # event diff --git a/lib/fleet_bot/discord/Fleetyards.ex b/lib/fleet_bot/discord/Fleetyards.ex new file mode 100644 index 0000000..4050368 --- /dev/null +++ b/lib/fleet_bot/discord/Fleetyards.ex @@ -0,0 +1,67 @@ +defmodule FleetBot.Discord.Fleetyards do + alias Nostrum.Api + + use FleetBot.Discord.Command + use FleetBot.Gettext + alias FleetBot.Repo.Discord.FleetyardsAccount + + @impl Command + def command("fleetyards", interaction) do + Api.create_interaction_response( + interaction, + create_interaction_response( + :deferred_channel_message_with_source, + create_interaction_response_data(flags: :ephemeral) + ) + ) + + FleetBot.Discord.get_subcommand_name(interaction) + |> fleetyards_command(interaction) + end + + def fleetyards_command("link", interaction) do + Api.edit_interaction_response( + interaction, + create_interaction_response_data(content: "todo", flags: :ephemeral) + ) + end + + def fleetyards_command("unlink", %Nostrum.Struct.Interaction{user: user} = interaction) do + FleetyardsAccount.get_account(user) + |> int_fleetyards_unlink(interaction) + end + + @impl Command + def global_commands do + [ + # create_command("fleetyards", "Fleetyards account management", + # member_permission: :SEND_MESSAGES, + # dm_permission: true, + # options: [ + # create_option("link", "Link Fleetyards account", + # type: :sub_command, + # channel_types: [:guild_text, :dm, :guild_directory], + # options: [ + # create_option("token", "Fleetyards access token", type: :string), + # create_option("username", "Fleetyards username or email address", type: :string), + # create_option("password", "Fleetyards password", type: :string) + # ] + # ), + # create_option("unlink", "Unlink Fleetyards accounts", + # type: :sub_command, + # channel_types: [:guild_text, :dm, :guild_directory] + # ) + # ] + # ) + ] + end + + # Internal helpers + defp int_fleetyards_unlink(nil, interaction) do + content = LGettext.dgettext("discord_fleetyards", "Account not linked, cannot unlink.") + Api.edit_interaction_response(interaction, create_interaction_response_data(content: content)) + end + + defp int_fleetyards_unlink(account, interaction) do + end +end diff --git a/lib/fleet_bot/discord/command.ex b/lib/fleet_bot/discord/command.ex new file mode 100644 index 0000000..af1137c --- /dev/null +++ b/lib/fleet_bot/discord/command.ex @@ -0,0 +1,102 @@ +defmodule FleetBot.Discord.Command do + @callback command(String.t(), %Nostrum.Struct.Interaction{}) :: term() + + @callback global_commands() :: [Nostrum.Struct.ApplicationCommand.application_command_map()] + @callback remove_global_commands() :: [String.t()] + + @optional_callbacks global_commands: 0, remove_global_commands: 0 + + # Macros + defmacro __using__(_opts) do + quote do + @behaviour unquote(__MODULE__) + alias unquote(__MODULE__) + require unquote(__MODULE__) + + import unquote(__MODULE__), + only: [ + create_interaction_response: 1, + create_interaction_response: 2, + create_interaction_response_data: 1 + ] + + require FleetBot.Discord.Commands.Register + import FleetBot.Discord.Commands.Register + + use FleetBot.Gettext + end + end + + defmacro create_interaction_response(type, data \\ nil) do + type = interaction_response_type(type) + + quote do + %{ + type: unquote(type), + data: unquote(data) + } + end + end + + def create_interaction_response_data(opts) do + tts = Keyword.get(opts, :tts) + content = Keyword.get(opts, :content) + embeds = Keyword.get(opts, :embeds) + allowed_mentions = Keyword.get(opts, :allowed_mentions) + + flags = + Keyword.get(opts, :flags) + |> interaction_response_data_flags() + + components = Keyword.get(opts, :components) + attachements = Keyword.get(opts, :attachements) + + # quote do + # %{ + # tts: unquote(tts), + # content: unquote( + # content + # ), + # embeds: unquote(embeds), + # allowed_mentions: unquote(allowed_mentions), + # flags: unquote(flags), + # components: unquote(components), + # attachements: unquote(attachements) + # } + # end + %{ + tts: tts, + content: content, + embeds: embeds, + allowed_mentions: allowed_mentions, + flags: flags, + components: components, + attachements: attachements + } + end + + ## Macro helpers + use Bitwise, only_operators: true + + def interaction_response_type(:pong), do: 1 + def interaction_response_type(:channel_message_with_source), do: 4 + def interaction_response_type(:deferred_channel_message_with_source), do: 5 + def interaction_response_type(:deferred_update_channel), do: 6 + def interaction_response_type(:update_message), do: 7 + def interaction_response_type(:application_command_autocomplete_result), do: 8 + def interaction_response_type(:modal), do: 9 + def interaction_response_type(v) when is_number(v), do: v + + def interaction_response_data_flags(flags) when is_list(flags) do + flags + |> Enum.map(&interaction_response_data_flags/1) + |> Enum.reduce(0, fn flag, acc -> + flag ||| acc + end) + end + + def interaction_response_data_flags(:suppress_embeds), do: 1 <<< 3 + def interaction_response_data_flags(:ephemeral), do: 1 <<< 6 + def interaction_response_data_flags(v) when is_number(v), do: v + def interaction_response_data_flags(nil), do: nil +end diff --git a/lib/fleet_bot/discord/commands.ex b/lib/fleet_bot/discord/commands.ex index 6ffeb5c..4dc08bd 100644 --- a/lib/fleet_bot/discord/commands.ex +++ b/lib/fleet_bot/discord/commands.ex @@ -1,13 +1,28 @@ defmodule FleetBot.Discord.Commands do - use FleetBot.Discord.Commands.Register - - @impl Register - def global_commands() do - [ - create_command("login", "Login with fleetyards account", - member_permission: :SEND_MESSAGES, - dm_permission: true - ) + use Supervisor + + def get_commands, + do: [ + FleetBot.Discord.Fleetyards ] + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + @impl Supervisor + def init(_opts) do + children = + [ + FleetBot.Discord.Commands.RegisterManager + ] ++ get_supervised_commands() + + Supervisor.init(children, strategy: :one_for_one) + end + + def get_supervised_commands(commands \\ get_commands()) do + commands + |> Enum.filter(&Kernel.function_exported?(&1, :start_link, 1)) + |> Enum.into([]) end end diff --git a/lib/fleet_bot/discord/commands/register.ex b/lib/fleet_bot/discord/commands/register.ex index 533eaff..5ef597e 100644 --- a/lib/fleet_bot/discord/commands/register.ex +++ b/lib/fleet_bot/discord/commands/register.ex @@ -1,4 +1,5 @@ defmodule FleetBot.Discord.Commands.Register do + alias Hex.API.Key use Bitwise, only_operators: true ## Callback interface @@ -15,8 +16,7 @@ defmodule FleetBot.Discord.Commands.Register do alias unquote(__MODULE__) require unquote(__MODULE__) - import unquote(__MODULE__), - only: [create_command: 2, create_command: 3, localization_dict: 1] + import unquote(__MODULE__) require FleetBot.Gettext end @@ -90,7 +90,23 @@ defmodule FleetBot.Discord.Commands.Register do |> Macro.escape() required = Keyword.get(opts, :required, nil) - type = :todo + + type = + Keyword.get(opts, :type) + |> command_option_type() + + choices = Keyword.get(opts, :choices) + options = Keyword.get(opts, :options) + + channel_types = + Keyword.get(opts, :channel_types) + |> command_channel_types() + + min_value = Keyword.get(opts, :min_value) + max_value = Keyword.get(opts, :max_value) + min_length = Keyword.get(opts, :min_length) + max_length = Keyword.get(opts, :max_length) + autocomplete = Keyword.get(opts, :autocomplete) quote do Map.merge(unquote(extra), %{ @@ -99,7 +115,29 @@ defmodule FleetBot.Discord.Commands.Register do name_localizations: localization_dict(unquote(name)), description: unquote(description), description_localizations: localization_dict(unquote(description)), - required: unquote(required) + required: unquote(required), + choices: unquote(choices), + options: unquote(options), + channel_types: unquote(channel_types), + min_value: unquote(min_value), + max_value: unquote(max_value), + min_length: unquote(min_length), + max_length: unquote(max_length), + autocomplete: unquote(autocomplete) + }) + end + end + + defmacro create_choice(name, value, opts \\ []) do + extra = + Keyword.get(opts, :extra, %{}) + |> Macro.escape() + + quote do + Map.merge(unquote(extra), %{ + name: unquote(name), + name_localizations: localization_dict(unquote(name)), + value: unquote(value) }) end end @@ -114,5 +152,38 @@ defmodule FleetBot.Discord.Commands.Register do defp default_member_permission_conv(v) when is_integer(v), do: Integer.to_string(v) defp default_member_permission_conv(:VIEW_CHANNEL), do: 1 <<< 10 defp default_member_permission_conv(:SEND_MESSAGES), do: 1 <<< 11 + + defp command_option_type(:sub_command), do: 1 + defp command_option_type(:sub_command_group), do: 2 + defp command_option_type(:string), do: 3 + defp command_option_type(:integer), do: 4 + defp command_option_type(:boolean), do: 5 + defp command_option_type(:user), do: 6 + defp command_option_type(:channel), do: 7 + defp command_option_type(:role), do: 8 + defp command_option_type(:mentionable), do: 9 + defp command_option_type(:number), do: 10 + defp command_option_type(:attachment), do: 11 + defp command_option_type(v) when is_number(v), do: v + + defp command_channel_types(nil), do: nil + defp command_channel_types(v) when is_atom(v) or is_number(v), do: [command_channel_type(v)] + + defp command_channel_types(v) when is_list(v), + do: Enum.map(v, fn v -> command_channel_type(v) end) |> Enum.into([]) + + defp command_channel_type(:guild_text), do: 0 + defp command_channel_type(:dm), do: 1 + defp command_channel_type(:guild_voice), do: 2 + defp command_channel_type(:group_dm), do: 3 + defp command_channel_type(:guild_category), do: 4 + defp command_channel_type(:guild_announcement), do: 5 + defp command_channel_type(:announcement_thread), do: 10 + defp command_channel_type(:public_thread), do: 11 + defp command_channel_type(:private_thread), do: 12 + defp command_channel_type(:guild_stage_voice), do: 13 + defp command_channel_type(:guild_directory), do: 14 + defp command_channel_type(:guild_forum), do: 15 + defp command_channel_type(v) when is_number(v), do: v # TODO: import all from https://discord.com/developers/docs/topics/permissions and allow arrays end diff --git a/lib/fleet_bot/discord/commands/register_manager.ex b/lib/fleet_bot/discord/commands/register_manager.ex index 3b83be5..d0d68e3 100644 --- a/lib/fleet_bot/discord/commands/register_manager.ex +++ b/lib/fleet_bot/discord/commands/register_manager.ex @@ -1,15 +1,21 @@ defmodule FleetBot.Discord.Commands.RegisterManager do - require Logger - require FleetBot.Gettext + use FleetBot.Gettext alias FleetBot.Repo.Discord.Command ## Public Interface def set_ready() do - # TODO: call :ready, which then casts :register_global? - GenServer.cast(__MODULE__, {:register_global}) + register_global_commands() + # FIXME: remove_global_commands() end + def get_module(command), do: GenServer.call(__MODULE__, {:get_module, command}) + + def register_global_commands(), do: GenServer.cast(__MODULE__, {:register_global}) + def register_global_command(module), do: GenServer.cast(__MODULE__, {:register_global, module}) + + def remove_global_commands(), do: GenServer.cast(__MODULE__, {:remove_global}) + ## GenServer use GenServer @@ -19,95 +25,201 @@ defmodule FleetBot.Discord.Commands.RegisterManager do @impl GenServer def init(module) when is_atom(module) do - {:ok, %{module: module}} + command_modules = + FleetBot.Discord.Commands.get_commands() + |> Enum.map(&{&1, :none}) + + {:ok, %{command_modules: command_modules, module_commands: %{}}} end @impl GenServer - def handle_cast({:register_global}, state), do: {:noreply, register_global_commands(state)} + def handle_call({:get_module, command}, _from, %{module_commands: module_commands} = state) do + {:reply, Map.get(module_commands, command), state} + end + + @impl GenServer + def handle_cast({:register_global}, %{command_modules: modules} = state) do + state = + Enum.reduce(modules, state, fn + {module, :none}, state -> + int_register_global_commands(module, state) + + _, state -> + state + end) + + LGettext.debug("Registered all global commands") + + {:noreply, state} + end + + @impl GenServer + def handle_cast({:register_global, module}, state), + do: {:noreply, int_register_global_commands(module, state)} + + @impl GenServer + def handle_cast({:remove_global}, %{module: module} = state) do + if Kernel.function_exported?(module, :delete_global_commands, 0) do + # TODO: count errors/deletes/missing + apply(module, :delete_global_commands, []) + |> Stream.map(&remove_global_command/1) + |> Enum.into([]) + end + + {:noreply, state} + end ## Internal Functions - ### Register - def register_global_commands(%{globale_init: true} = state) do - Logger.debug("Global commands already initialized") - state + + # region + defp int_register_global_commands( + module, + %{command_modules: modules, module_commands: module_commands} = state + ) do + new_state = + Keyword.get(modules, module) + |> int_register_global_commands(module) + |> case do + {:global, commands} -> + modules = Keyword.put(modules, module, :global) + + commands = + commands + |> Enum.map(&{&1, module}) + |> Enum.into(%{}) + |> Map.merge(module_commands) + + state + |> Map.put(:command_modules, modules) + |> Map.put(:module_commands, commands) + + v when is_atom(v) -> + Keyword.put(modules, module, :global) + + state + |> Map.put(:command_modules, modules) + + _ -> + state + end end - def register_global_commands(%{module: module} = state) do - Logger.debug("Initializing global commands") + defp int_register_global_commands(:none, module) when is_atom(module) do + int_register_global_commands_module(module) + end + + defp int_register_global_commands(nil, module) when is_atom(module) do + LGettext.error("Module `%{module}` not found, cannot register commands", module: module) + nil + end + + defp int_register_global_commands(:global, module) when is_atom(module) do + LGettext.debug("Global commands for `%{module}` already registered", module: module) + nil + end + + defp int_register_global_commands_module(module) when is_atom(module) do + LGettext.debug("Initializing global commands: %{module}", module: module) # TODO: count knowns/errors/inserts - commands_num = + commands = apply(module, :global_commands, []) - |> Stream.map(®ister_global_command/1) + |> Stream.map(&int_register_global_command/1) + |> Stream.filter(fn + {_, {:ok, _}} -> true + _ -> false + end) |> Enum.into([]) + + commands_num = + commands |> Enum.count() - Logger.info( - FleetBot.Gettext.dgettext("logger", "Global commands initialized: %{num}", num: commands_num) + commands = + commands + |> Enum.map(fn {name, _} -> name end) + + LGettext.info("Global commands initialized (%{module}): %{num}", + module: module, + num: commands_num ) # Logger.info("Global commands initialized") - Map.put(state, :global_init, true) + # return nil on error + {:global, commands} end - @spec register_global_command(Nostrum.Struct.ApplicationCommand.application_command_map()) :: + @spec int_register_global_command(Nostrum.Struct.ApplicationCommand.application_command_map()) :: nil | :ok - def register_global_command(%{name: name} = command) do + defp int_register_global_command(%{name: name} = command) do old_command = Command.get_command(name) hash = :erlang.phash2(command) - register_global_command(command, old_command, hash) + int_register_global_command(command, old_command, hash) end - def register_global_command(command, nil, hash) do + defp int_register_global_command(command, nil, hash) do # Create new command - commit_global_command(command, hash) + int_commit_global_command(command, hash) end - def register_global_command(%{name: name} = command, %{cmd_hash: old_hash} = old_command, hash) do + defp int_register_global_command( + %{name: name} = command, + %{cmd_hash: old_hash} = old_command, + hash + ) do # Update old command - IO.inspect("hash = #{hash}, old: #{old_hash}") - if hash == old_hash do - Logger.debug( - FleetBot.Gettext.dgettext( - "logger", - "Global Command `%{name}` already correctly registerd, skipping.", - name: name - ) + LGettext.debug( + "Global Command `%{name}` already correctly registerd, skipping.", + name: name ) - {:ok, :known} + {name, {:ok, :known}} else - update_global_command(command, old_command, hash) + int_update_global_command(command, old_command, hash) end end - defp commit_global_command(%{} = command, hash) do + defp int_commit_global_command(%{} = command, hash) do with {:ok, %{id: command_id, name: name}} <- Nostrum.Api.create_global_application_command(command) do - Logger.info( - FleetBot.Gettext.dgettext("logger", "Registering Global Command `%{name}`", name: name) - ) + LGettext.info("Registering Global Command `%{name}`", name: name) - Command.add_command(name, command_id, hash) + {name, Command.add_command(name, command_id, hash)} else {:error, e} -> {:error, e} end end - defp update_global_command(%{} = command, %{command_id: old_id} = old_command, hash) do + defp int_update_global_command(%{} = command, %{command_id: old_id} = old_command, hash) do with {:ok, %{id: command_id, name: name}} <- Nostrum.Api.edit_global_application_command(old_id, command) do - Logger.info( - FleetBot.Gettext.dgettext("logger", "Updating Global Command `%{name}`", name: name) - ) + LGettext.info("Updating Global Command `%{name}`", name: name) + + {name, Command.update_command(old_command, hash, command_id)} + else + {:error, _} = e -> e + end + end + + # endregion - Command.update_command(old_command, hash, command_id) + # region + def remove_global_command(name) when is_binary(name), + do: remove_global_command(Command.get_command(name)) + + def remove_global_command(%Command{command_id: id, command: name} = command) do + with {:ok} <- Nostrum.Api.delete_global_application_command(id), + {:ok, _} = v <- Command.delete_command(command) do + LGettext.info("Deleted global command: `%{name}", name: name) + v else {:error, _} = e -> e end end + + # endregion end diff --git a/lib/fleet_bot/fleetyards.ex b/lib/fleet_bot/fleetyards.ex new file mode 100644 index 0000000..4626059 --- /dev/null +++ b/lib/fleet_bot/fleetyards.ex @@ -0,0 +1,11 @@ +defmodule FleetBot.Fleetyards do + # do: {"Authorization", "Bearer " <> token} + def get_auth_header(token), do: {""} + + defmacro __using__(_opts) do + quote do + @backend Application.compile_env(:fleet_bot, [FleetBot.Fleetyards, :client]) + import unquote(__MODULE__), only: [get_auth_header: 1] + end + end +end diff --git a/lib/fleet_bot/fleetyards/client.ex b/lib/fleet_bot/fleetyards/client.ex new file mode 100644 index 0000000..0adbb55 --- /dev/null +++ b/lib/fleet_bot/fleetyards/client.ex @@ -0,0 +1,41 @@ +defmodule FleetBot.Fleetyards.Client do + use HTTPoison.Base + + def api_url(), do: Application.fetch_env!(:fleet_bot, FleetBot.Fleetyards)[:api_url] + + @impl HTTPoison.Base + def process_request_url(path) do + api_url() <> path + end + + @impl HTTPoison.Base + def process_response_body(body) do + case Jason.decode(body) do + {:ok, v} -> v + _ -> body + end + end + + @impl HTTPoison.Base + def process_request_headers(headers) do + [ + {"Content-Type", "application/json"}, + {"User-Agent", + "FleetBot/#{get_version} (#{:erlang.system_info(:system_architecture)}) OTP/#{:erlang.system_info(:otp_release)} (#{String.trim(:binary.list_to_bin(:erlang.system_info(:system_version)))})"} + | Enum.filter(headers, fn {header, _value} -> String.downcase(header) != "content-type" end) + |> Enum.filter(fn {header, _value} -> String.downcase(header) != "user-agent" end) + ] + end + + @impl HTTPoison.Base + def process_request_body(%{} = body) do + Jason.encode!(body) + end + + @impl HTTPoison.Base + def process_request_body(body), do: body + + def get_version() do + Application.spec(:fleet_bot)[:vsn] + end +end diff --git a/lib/fleet_bot/fleetyards/session.ex b/lib/fleet_bot/fleetyards/session.ex new file mode 100644 index 0000000..2758124 --- /dev/null +++ b/lib/fleet_bot/fleetyards/session.ex @@ -0,0 +1,38 @@ +defmodule FleetBot.Fleetyards.Session do + use FleetBot.Fleetyards + + def login(username, password) do + body = + %{ + "login" => username, + "password" => password, + "remember_me" => true + } + |> IO.inspect() + + with {:ok, %HTTPoison.Response{status_code: 200, body: %{"token" => token} = body}} <- + @backend.post("/v1/sessions", body, [{"Content-Type", "application/json"}]) do + IO.inspect(token) + else + {:ok, + %HTTPoison.Response{ + status_code: 400, + body: %{"code" => "session.create.not_found_in_database"} + } = resp} -> + IO.inspect(resp) + {:error, :invalid_username_password} + + v -> + IO.inspect(v) + end + end + + def logout(token) do + with {:ok, %HTTPoison.Response{status_code: 200, body: %{"code" => "sessions.destroy"}}} <- + @backend.delete("/v1/sessions", [get_auth_header(token)]) do + :ok + else + v -> v + end + end +end diff --git a/lib/fleet_bot/gettext.ex b/lib/fleet_bot/gettext.ex index 05deeb7..ef0342e 100644 --- a/lib/fleet_bot/gettext.ex +++ b/lib/fleet_bot/gettext.ex @@ -1,3 +1,29 @@ defmodule FleetBot.Gettext do use Gettext, otp_app: :fleet_bot + + defmacro debug(msg, opts \\ []) do + quote do + Logger.debug(FleetBot.Gettext.dgettext("logger", unquote(msg), unquote(opts))) + end + end + + defmacro info(msg, opts \\ []) do + quote do + Logger.info(FleetBot.Gettext.dgettext("logger", unquote(msg), unquote(opts))) + end + end + + defmacro error(msg, opts \\ []) do + quote do + Logger.error(unquote(__MODULE__).dgettext("logger", unquote(msg), unquote(opts))) + end + end + + defmacro __using__(_opts) do + quote do + alias unquote(__MODULE__), as: LGettext + require unquote(__MODULE__) + require Logger + end + end end diff --git a/lib/fleet_bot/repo/discord/command.ex b/lib/fleet_bot/repo/discord/command.ex index 087e3f5..f14a847 100644 --- a/lib/fleet_bot/repo/discord/command.ex +++ b/lib/fleet_bot/repo/discord/command.ex @@ -45,6 +45,15 @@ defmodule FleetBot.Repo.Discord.Command do |> Repo.update() end + def delete_command(command) when is_binary(command) do + get_command(command) + |> delete_command() + end + + def delete_command(%__MODULE__{} = command) do + Repo.delete(command) + end + def get_command(name, guild_id \\ nil) def get_command(name, nil) when is_binary(name) do diff --git a/lib/fleet_bot/repo/discord/fleetyards_account.ex b/lib/fleet_bot/repo/discord/fleetyards_account.ex new file mode 100644 index 0000000..2533233 --- /dev/null +++ b/lib/fleet_bot/repo/discord/fleetyards_account.ex @@ -0,0 +1,41 @@ +defmodule FleetBot.Repo.Discord.FleetyardsAccount do + alias FleetBot.Discord + use Ecto.Schema + import Ecto.Changeset + + alias FleetBot.Repo + + @type t() :: %__MODULE__{ + user_id: Nostrum.Snowflake.t(), + fleetyards_token: String.t() + } + + schema "fleetyards_accounts" do + field :user_id, :integer + field :fleetyards_token, :string, redact: true + end + + ## Api + def add_aoount(%Nostrum.Struct.User{id: user_id}, token), do: add_account(user_id, token) + + def add_account(user_id, token) when is_integer(user_id) and is_binary(token) do + create_changeset(%{user_id: user_id, fleetyards_token: token}) + |> Repo.insert() + end + + def get_account(%Nostrum.Struct.User{id: user_id}), do: get_account(user_id) + + def get_account(user_id) when is_integer(user_id) do + Repo.get_by(__MODULE__, user_id: user_id) + end + + ## Changeset + def create_changeset(account \\ %__MODULE__{}, attrs) do + account + |> cast(attrs, [:user_id, :fleetyards_token]) + |> Discord.validate_snowflake(:user_id) + |> unique_constraint(:user_id) + |> unique_constraint(:fleetyards_token) + |> validate_required([:user_id, :fleetyards_token]) + end +end diff --git a/priv/repo/migrations/20221123133928_fleetyards_accounts.exs b/priv/repo/migrations/20221123133928_fleetyards_accounts.exs new file mode 100644 index 0000000..60efed1 --- /dev/null +++ b/priv/repo/migrations/20221123133928_fleetyards_accounts.exs @@ -0,0 +1,12 @@ +defmodule FleetBot.Repo.Migrations.FleetyardsAccounts do + use Ecto.Migration + + def change do + create table(:fleetyards_accounts) do + add :user_id, :bigint, null: false + add :fleetyards_token, :string, null: false + end + + create unique_index(:fleetyards_accounts, [:user_id]) + end +end