# This file can be placed somewhere in ./lib, and it can be started # by running iex -S mix then calling AudioPlayerSupervisor.start_link([]). defmodule AudioPlayerSupervisor do use Supervisor def start_link(args) do Supervisor.start_link(__MODULE__, args, name: __MODULE__) end @impl true def init(_init_arg) do children = [AudioPlayerConsumer] Supervisor.init(children, strategy: :one_for_one) end end defmodule AudioPlayerConsumer do use Nostrum.Consumer alias Nostrum.Api.ApplicationCommand alias Nostrum.Api.Interaction alias Nostrum.Cache.GuildCache alias Nostrum.Voice require Logger # Soundcloud link will be fed through youtube-dl @soundcloud_url "https://soundcloud.com/fyre-brand/level-up" # Audio file will be fed directly to ffmpeg @nut_file_url "https://brandthill.com/files/nut.wav" # Compile-time helper for defining Discord Application Command options opt = fn type, name, desc, opts -> %{type: type, name: name, description: desc} |> Map.merge(Enum.into(opts, %{})) end @play_opts [ opt.(1, "song", "Play a song", []), opt.(1, "nut", "Play a nut sound", []), opt.(1, "file", "Play a file", options: [opt.(3, "url", "File URL to play", required: true)]), opt.(1, "url", "Play a URL from a common service", options: [opt.(3, "url", "URL to play", required: true)] ) ] @commands [ {"summon", "Summon bot to your voice channel", []}, {"leave", "Tell bot to leave your voice channel", []}, {"play", "Play a sound", @play_opts}, {"stop", "Stop the playing sound", []}, {"pause", "Pause the playing sound", []}, {"resume", "Resume the paused sound", []} ] def get_voice_channel_of_interaction(%{guild_id: guild_id, user: %{id: user_id}} = _interaction) do guild_id |> GuildCache.get!() |> Map.get(:voice_states) |> Enum.find(%{}, fn v -> v.user_id == user_id end) |> Map.get(:channel_id) end # If you are running this example in an iex session where you manually call # AudioPlayerSupervisor.start_link, you will have to call this function # with your guild_id as the argument def create_guild_commands(guild_id) do Enum.each(@commands, fn {name, description, options} -> ApplicationCommand.create_guild_command(guild_id, %{ name: name, description: description, options: options }) end) end def handle_event({:READY, %{guilds: guilds} = _event, _ws_state}) do guilds |> Enum.map(fn guild -> guild.id end) |> Enum.each(&create_guild_commands/1) end def handle_event({:INTERACTION_CREATE, interaction, _ws_state}) do # Run the command, and check for a response message, or default to a checkmark emoji message = case do_command(interaction) do {:msg, msg} -> msg _ -> ":white_check_mark:" end Interaction.create_response(interaction, %{type: 4, data: %{content: message}}) end def handle_event({:VOICE_SPEAKING_UPDATE, payload, _ws_state}) do Logger.debug("VOICE SPEAKING UPDATE #{inspect(payload)}") end # Default event handler, if you don't include this, your consumer WILL crash if # you don't have a method definition for each event type. def handle_event(_event) do :noop end def do_command(%{guild_id: guild_id, data: %{name: "summon"}} = interaction) do case get_voice_channel_of_interaction(interaction) do nil -> {:msg, "You must be in a voice channel to summon me"} voice_channel_id -> Voice.join_channel(guild_id, voice_channel_id) end end def do_command(%{guild_id: guild_id, data: %{name: "leave"}}) do Voice.leave_channel(guild_id) {:msg, "See you later :wave:"} end def do_command(%{guild_id: guild_id, data: %{name: "pause"}}), do: Voice.pause(guild_id) def do_command(%{guild_id: guild_id, data: %{name: "resume"}}), do: Voice.resume(guild_id) def do_command(%{guild_id: guild_id, data: %{name: "stop"}}), do: Voice.stop(guild_id) def do_command(%{guild_id: guild_id, data: %{name: "play", options: options}}) do if Voice.ready?(guild_id) do case options do [%{name: "song"}] -> Voice.play(guild_id, @soundcloud_url, :ytdl) [%{name: "nut"}] -> Voice.play(guild_id, @nut_file_url, :url) [%{name: "file", options: [%{value: url}]}] -> Voice.play(guild_id, url, :url) [%{name: "url", options: [%{value: url}]}] -> Voice.play(guild_id, url, :ytdl) end else {:msg, "I must be in a voice channel before playing audio"} end end end