diff --git a/lib/sergei/commands.ex b/lib/sergei/commands.ex new file mode 100644 index 0000000..c1ce294 --- /dev/null +++ b/lib/sergei/commands.ex @@ -0,0 +1,129 @@ +defmodule Sergei.Commands do + require Logger + + # Translate params to list of maps + opt = fn type, name, desc, opts -> + %{type: type, name: name, description: desc} + |> Map.merge(Enum.into(opts, %{})) + end + + @play_opts [ + opt.(3, "url", "URL of the audio to play", []) + ] + + @queue_add_opts [ + opt.(3, "url", "URL of the audio to queue", required: true) + ] + + @queue_opts [ + opt.(1, "add", "Add a song to the queue", options: @queue_add_opts) + ] + + @slash_commands [ + {"ping", "Pong", []}, + {"play", "Play a song or resume playback", @play_opts}, + {"queue", "Manage the song queue", @queue_opts}, + {"pause", "Pause media playback", []}, + {"stop", "Stop media playback and leave the voice channel", []}, + {"song", "What song is currently playing?", []} + ] + + def commands() do + Enum.map(@slash_commands, fn {name, description, options} -> + %{ + name: name, + description: description, + options: options + } + end) + end + + # /ping + def do_command(%{data: %{name: "ping"}}) do + {:ok, "Pong"} + end + + # /play + def do_command(%{ + guild_id: guild_id, + user: %{id: invoker_id}, + data: %{name: "play", options: [%{name: "url", value: url}]} + }) do + case Sergei.VoiceStateCache.get_state(invoker_id) do + %{guild_id: id} = _res when guild_id != id -> + {:error, "You're not connected to a voice channel in this server."} + + %{channel_id: channel_id} = _res -> + Sergei.Player.play(guild_id, channel_id, url) + + nil -> + {:error, "You are not in a voice channel."} + end + end + + # /play + def do_command(%{guild_id: guild_id, data: %{name: "play"}}) do + case Sergei.Player.resume(guild_id) do + :ok -> + {:ok, "Resuming playback..."} + + :not_playing -> + {:ok, "I'm not playing anything right now."} + + {:error, err} -> + Logger.error("Failed to resume media: #{err}") + {:error, "This is embarrasing..."} + end + end + + # /queue + def do_command(%{ + guild_id: guild_id, + data: %{name: "queue", options: opts} + }) do + subcommand = List.first(opts) + + Sergei.Commands.Queue.handle(guild_id, subcommand.name, subcommand.options) + end + + # /pause + def do_command(%{guild_id: guild_id, data: %{name: "pause"}}) do + case Sergei.Player.pause(guild_id) do + :ok -> + {:ok, "Pausing..."} + + :not_playing -> + {:ok, "I'm not playing anything right now."} + + {:error, err} -> + Logger.error("Failed to pause media: #{err}") + {:error, "This is embarrasing..."} + end + end + + # /stop + def do_command(%{guild_id: guild_id, data: %{name: "stop"}}) do + case Sergei.Player.stop(guild_id) do + :ok -> + {:ok, "Bye!"} + + :not_playing -> + {:ok, "I'm not playing anything right now."} + + {:error, err} -> + Logger.error("Failed to stop media: #{err}") + {:error, "This is embarrasing..."} + end + end + + # /song + def do_command(%{guild_id: guild_id, data: %{name: "song"}}) do + case Sergei.Player.get_current_song(guild_id) do + :not_playing -> + {:ok, "I'm not playing anything right now."} + + url -> + {:ok, url} + end + end +end diff --git a/lib/sergei/commands/queue.ex b/lib/sergei/commands/queue.ex new file mode 100644 index 0000000..774ffd5 --- /dev/null +++ b/lib/sergei/commands/queue.ex @@ -0,0 +1,23 @@ +defmodule Sergei.Commands.Queue do + require Logger + + @spec handle(integer(), String.t(), [%{name: String.t(), value: String.t()}]) :: + {:ok, String.t()} | {:err, String.t()} + def handle(guild_id, "add", opts) do + [ + %{name: "url", value: url} + ] = opts + + case Sergei.Player.queue_add(guild_id, url) do + :ok -> + {:ok, "Song queued."} + + :not_playing -> + {:ok, "I'm not playing anything right now."} + + {:error, err} -> + Logger.error("Failed to queue media: #{err}") + {:error, "This is embarassing..."} + end + end +end diff --git a/lib/sergei/consumer.ex b/lib/sergei/consumer.ex index bb22f9f..b69ca8d 100644 --- a/lib/sergei/consumer.ex +++ b/lib/sergei/consumer.ex @@ -4,42 +4,12 @@ defmodule Sergei.Consumer do require Logger alias Nostrum.Api - # Translate params to list of maps - opt = fn type, name, desc, opts -> - %{type: type, name: name, description: desc} - |> Map.merge(Enum.into(opts, %{})) - end - - @play_opts [ - opt.(3, "url", "URL of the audio to play", []) - ] - - @queue_opts [ - opt.(3, "url", "URL of the audio to queue", required: true) - ] - - @slash_commands [ - {"ping", "Pong", []}, - {"play", "Play a song or resume playback", @play_opts}, - {"queue", "Queue a song to play next", @queue_opts}, - {"pause", "Pause media playback", []}, - {"stop", "Stop media playback and leave the voice channel", []}, - {"song", "What song is currently playing?", []} - ] - # Initialization of the Discord Client def handle_event({:READY, %{guilds: guilds} = _event, _ws_state}) do # Playing some tunes Api.update_status(:online, "some tunes", 0) - commands = - Enum.map(@slash_commands, fn {name, description, options} -> - %{ - name: name, - description: description, - options: options - } - end) + commands = Sergei.Commands.commands() case Application.get_env(:sergei, :env) do :prod -> @@ -72,7 +42,7 @@ defmodule Sergei.Consumer do # Handle interactions def handle_event({:INTERACTION_CREATE, interaction, _ws_state}) do response = - case do_command(interaction) do + case Sergei.Commands.do_command(interaction) do {:ok, msg} -> %{type: 4, data: %{content: msg, flags: 2 ** 6}} @@ -93,101 +63,4 @@ defmodule Sergei.Consumer do def handle_event(_event) do :noop end - - # /ping - def do_command(%{data: %{name: "ping"}}) do - {:ok, "Pong"} - end - - # /play - def do_command(%{ - guild_id: guild_id, - user: %{id: invoker_id}, - data: %{name: "play", options: [%{name: "url", value: url}]} - }) do - case Sergei.VoiceStateCache.get_state(invoker_id) do - %{guild_id: id} = _res when guild_id != id -> - {:error, "You're not connected to a voice channel in this server."} - - %{channel_id: channel_id} = _res -> - Sergei.Player.play(guild_id, channel_id, url) - - nil -> - {:error, "You are not in a voice channel."} - end - end - - # /play - def do_command(%{guild_id: guild_id, data: %{name: "play"}}) do - case Sergei.Player.resume(guild_id) do - :ok -> - {:ok, "Resuming playback..."} - - :not_playing -> - {:ok, "I'm not playing anything right now."} - - {:error, err} -> - Logger.error("Failed to resume media: #{err}") - {:error, "This is embarrasing..."} - end - end - - # /queue - def do_command(%{ - guild_id: guild_id, - data: %{name: "queue", options: [%{name: "url", value: url}]} - }) do - case Sergei.Player.queue(guild_id, url) do - :ok -> - {:ok, "Song queued."} - - :not_playing -> - {:ok, "I'm not playing anything right now."} - - {:error, err} -> - Logger.error("Failed to queue media: #{err}") - {:error, "This is embarassing..."} - end - end - - # /pause - def do_command(%{guild_id: guild_id, data: %{name: "pause"}}) do - case Sergei.Player.pause(guild_id) do - :ok -> - {:ok, "Pausing..."} - - :not_playing -> - {:ok, "I'm not playing anything right now."} - - {:error, err} -> - Logger.error("Failed to pause media: #{err}") - {:error, "This is embarrasing..."} - end - end - - # /stop - def do_command(%{guild_id: guild_id, data: %{name: "stop"}}) do - case Sergei.Player.stop(guild_id) do - :ok -> - {:ok, "Bye!"} - - :not_playing -> - {:ok, "I'm not playing anything right now."} - - {:error, err} -> - Logger.error("Failed to stop media: #{err}") - {:error, "This is embarrasing..."} - end - end - - # /song - def do_command(%{guild_id: guild_id, data: %{name: "song"}}) do - case Sergei.Player.get_current_song(guild_id) do - :not_playing -> - {:ok, "I'm not playing anything right now."} - - url -> - {:ok, url} - end - end end diff --git a/lib/sergei/player.ex b/lib/sergei/player.ex index 7a3f032..f2cf471 100644 --- a/lib/sergei/player.ex +++ b/lib/sergei/player.ex @@ -24,9 +24,9 @@ defmodule Sergei.Player do GenServer.call(__MODULE__, {:play, guild_id, channel_id, url}) end - @spec queue(integer(), String.t()) :: :ok | :not_playing | {:error, String.t()} - def queue(guild_id, url) do - GenServer.call(__MODULE__, {:queue, guild_id, url}) + @spec queue_add(integer(), String.t()) :: :ok | :not_playing | {:error, String.t()} + def queue_add(guild_id, url) do + GenServer.call(__MODULE__, {:queue_add, guild_id, url}) end @spec pause(integer()) :: :ok | :not_playing | {:error, String.t()} @@ -128,7 +128,7 @@ defmodule Sergei.Player do # Queue @impl true - def handle_call({:queue, guild_id, url}, _from, state) do + def handle_call({:queue_add, guild_id, url}, _from, state) do %{queue: queue} = Map.fetch!(state, guild_id) {