diff --git a/pom.xml b/pom.xml index 7346825..dbf03e6 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ net.dv8tion JDA - 4.0.0_79 + 4.1.0_81 org.json diff --git a/src/main/java/com/bbn/hadder/Hadder.java b/src/main/java/com/bbn/hadder/Hadder.java index f96792f..f89a23c 100644 --- a/src/main/java/com/bbn/hadder/Hadder.java +++ b/src/main/java/com/bbn/hadder/Hadder.java @@ -57,6 +57,7 @@ public class Hadder { new RebootCommand(), new EqualsCommand(), new InviteCommand(), + new ScreenShareCommand(), new NickCommand(), new PrefixCommand(), new BlowjobCommand(), @@ -88,7 +89,11 @@ public class Hadder { new AboutCommand(), new LanguageCommand(), new ClydeCommand(), - new StarBoardCommand()), config, helpCommand); + new PlayCommand(), + new StarBoardCommand(), + new QueueCommand(), + new InfoCommand(), + new StopCommand()), config, helpCommand); builder.addEventListeners( new MentionListener(rethink), diff --git a/src/main/java/com/bbn/hadder/audio/AudioInfo.java b/src/main/java/com/bbn/hadder/audio/AudioInfo.java new file mode 100644 index 0000000..8aa35e9 --- /dev/null +++ b/src/main/java/com/bbn/hadder/audio/AudioInfo.java @@ -0,0 +1,28 @@ +package com.bbn.hadder.audio; + +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import net.dv8tion.jda.api.entities.Member; + +/** + * @author Skidder / GregTCLTK + */ + +public class AudioInfo { + + private final AudioTrack track; + private final Member author; + + AudioInfo(AudioTrack track, Member author) { + this.track = track; + this.author = author; + } + + public AudioTrack getTrack() { + return track; + } + + public Member getAuthor() { + return author; + } + +} diff --git a/src/main/java/com/bbn/hadder/audio/AudioManager.java b/src/main/java/com/bbn/hadder/audio/AudioManager.java new file mode 100644 index 0000000..7c0d6dd --- /dev/null +++ b/src/main/java/com/bbn/hadder/audio/AudioManager.java @@ -0,0 +1,118 @@ +package com.bbn.hadder.audio; + +import com.bbn.hadder.commands.CommandEvent; +import com.bbn.hadder.utils.MessageEditor; +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; + +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @author Skidder / GregTCLTK + */ + +public class AudioManager { + + public AudioManager() { + AudioSourceManagers.registerRemoteSources(myManager); + } + + public final Map> players = new HashMap<>(); + public final AudioPlayerManager myManager = new DefaultAudioPlayerManager(); + + public void loadTrack(String identifier, CommandEvent event, Message msg) { + + Guild guild = event.getGuild(); + getPlayer(guild); + + myManager.loadItemOrdered(guild, identifier, new AudioLoadResultHandler() { + + @Override + public void trackLoaded(AudioTrack track) { + getTrackManager(guild).queue(track, event.getMember()); + msg.editMessage(event.getMessageEditor().getMessage(MessageEditor.MessageType.INFO, + "commands.music.play.success.loading.title", "⏯", + "", "") + .addField(event.getMessageEditor().getTerm("commands.music.play.success.title"), track.getInfo().title, false) + .addField(event.getMessageEditor().getTerm("commands.music.play.success.author"), track.getInfo().author, true) + .addField(event.getMessageEditor().getTerm("commands.music.play.success.length"), + String.format("%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(track.getInfo().length), + TimeUnit.MILLISECONDS.toMinutes(track.getInfo().length) % TimeUnit.HOURS.toMinutes(1), + TimeUnit.MILLISECONDS.toSeconds(track.getInfo().length) % TimeUnit.MINUTES.toSeconds(1)), true) + .build()).queue(); + } + + @Override + public void playlistLoaded(AudioPlaylist playlist) { + if (playlist.getSelectedTrack() != null) { + trackLoaded(playlist.getSelectedTrack()); + } else if (playlist.isSearchResult()) { + trackLoaded(playlist.getTracks().get(0)); + } else { + for (int i = 0; i < Math.min(playlist.getTracks().size(), 200); i++) { + getTrackManager(guild).queue(playlist.getTracks().get(i), event.getMember()); + } + } + } + + @Override + public void noMatches() { + msg.editMessage(event.getMessageEditor().getMessage(MessageEditor.MessageType.INFO, + "commands.music.play.error.match.title", "❌", + "commands.music.play.error.match.description", "") + .build()).queue(); + } + + @Override + public void loadFailed(FriendlyException e) { + msg.editMessage(event.getMessageEditor().getMessage(MessageEditor.MessageType.INFO, + "commands.music.play.error.load.title", "❌", + "commands.music.play.error.load.description", "") + .build()).queue(); + } + }); + } + + public boolean hasPlayer(Guild guild) { + return players.containsKey(guild.getId()); + } + + public AudioPlayer getPlayer(Guild guild) { + AudioPlayer p; + if (hasPlayer(guild)) { + p = players.get(guild.getId()).getKey(); + } else { + p = createPlayer(guild); + } + return p; + } + + public TrackManager getTrackManager(Guild guild) { + return players.get(guild.getId()).getValue(); + } + + public AudioPlayer createPlayer(Guild guild) { + AudioPlayer nPlayer = myManager.createPlayer(); + TrackManager manager = new TrackManager(nPlayer); + nPlayer.addListener(manager); + guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(nPlayer)); + players.put(guild.getId(), new AbstractMap.SimpleEntry<>(nPlayer, manager)); + return nPlayer; + } + + public String getOrNull(String s) { + return s.isEmpty() ? "N/A" : s; + } + +} diff --git a/src/main/java/com/bbn/hadder/audio/AudioPlayerSendHandler.java b/src/main/java/com/bbn/hadder/audio/AudioPlayerSendHandler.java new file mode 100644 index 0000000..1863de2 --- /dev/null +++ b/src/main/java/com/bbn/hadder/audio/AudioPlayerSendHandler.java @@ -0,0 +1,49 @@ +package com.bbn.hadder.audio; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; +import net.dv8tion.jda.api.audio.AudioSendHandler; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; + +/** + * @author Skidder / GregTCLTK + */ + +public class AudioPlayerSendHandler implements AudioSendHandler { + + private final AudioPlayer audioPlayer; + private AudioFrame lastFrame; + + public AudioPlayerSendHandler(AudioPlayer audioPlayer) { + this.audioPlayer = audioPlayer; + } + + @Override + public boolean canProvide() { + if (lastFrame == null) { + lastFrame = audioPlayer.provide(); + } + + return lastFrame != null; + } + + @Nullable + @Override + public ByteBuffer provide20MsAudio() { + if (lastFrame == null) { + lastFrame = audioPlayer.provide(); + } + + byte[] data = lastFrame != null ? lastFrame.getData() : null; + lastFrame = null; + + return ByteBuffer.wrap(data); + } + + @Override + public boolean isOpus() { + return true; + } +} diff --git a/src/main/java/com/bbn/hadder/audio/TrackManager.java b/src/main/java/com/bbn/hadder/audio/TrackManager.java new file mode 100644 index 0000000..b4c1e9a --- /dev/null +++ b/src/main/java/com/bbn/hadder/audio/TrackManager.java @@ -0,0 +1,73 @@ +package com.bbn.hadder.audio; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.VoiceChannel; + +import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * @author Skidder / GregTCLTK + */ + +public class TrackManager extends AudioEventAdapter { + + private final AudioPlayer player; + private final Queue queue; + + public TrackManager(AudioPlayer player) { + this.player = player; + this.queue = new LinkedBlockingQueue<>(); + } + + public void queue(AudioTrack track, Member author) { + AudioInfo info = new AudioInfo(track, author); + queue.add(info); + + if (player.getPlayingTrack() == null) { + player.playTrack(track); + } + } + + @Override + public void onTrackStart(AudioPlayer player, AudioTrack track) { + AudioInfo info = queue.element(); + VoiceChannel vChan = info.getAuthor().getVoiceState().getChannel(); + if (vChan == null) { + player.stopTrack(); + } else { + info.getAuthor().getGuild().getAudioManager().openAudioConnection(vChan); + } + } + + @Override + public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { + Guild g = queue.poll().getAuthor().getGuild(); + if (queue.isEmpty()) { + g.getAudioManager().closeAudioConnection(); + } else { + player.playTrack(queue.element().getTrack()); + } + } + + public Set getQueuedTracks() { + return new LinkedHashSet<>(queue); + } + + public void purgeQueue() { + queue.clear(); + } + + public void remove(AudioInfo entry) { + queue.remove(entry); + } + + public AudioInfo getTrackInfo(AudioTrack track) { + return queue.stream().filter(audioInfo -> audioInfo.getTrack().equals(track)).findFirst().orElse(null); + } +} diff --git a/src/main/java/com/bbn/hadder/commands/general/AboutCommand.java b/src/main/java/com/bbn/hadder/commands/general/AboutCommand.java index 82d17eb..3800836 100644 --- a/src/main/java/com/bbn/hadder/commands/general/AboutCommand.java +++ b/src/main/java/com/bbn/hadder/commands/general/AboutCommand.java @@ -24,7 +24,7 @@ public class AboutCommand implements Command { @Override public String[] labels() { - return new String[]{"about", "info"}; + return new String[]{"about"}; } @Override diff --git a/src/main/java/com/bbn/hadder/commands/moderation/StarBoardCommand.java b/src/main/java/com/bbn/hadder/commands/moderation/StarBoardCommand.java index 50d6987..3371eec 100644 --- a/src/main/java/com/bbn/hadder/commands/moderation/StarBoardCommand.java +++ b/src/main/java/com/bbn/hadder/commands/moderation/StarBoardCommand.java @@ -14,7 +14,7 @@ public class StarBoardCommand implements Command { event.getChannel().sendMessage( event.getMessageEditor().getMessage( MessageEditor.MessageType.INFO, - "commands.moderation.starboard.successchannel","") + "commands.moderation.starboard.success.title","") .build()) .queue(); } else { diff --git a/src/main/java/com/bbn/hadder/commands/music/InfoCommand.java b/src/main/java/com/bbn/hadder/commands/music/InfoCommand.java new file mode 100644 index 0000000..85605b3 --- /dev/null +++ b/src/main/java/com/bbn/hadder/commands/music/InfoCommand.java @@ -0,0 +1,57 @@ +package com.bbn.hadder.commands.music; + +import com.bbn.hadder.audio.AudioManager; +import com.bbn.hadder.commands.Command; +import com.bbn.hadder.commands.CommandEvent; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; + +/** + * @author Skidder / GregTCLTK + */ + +public class InfoCommand implements Command { + + private static final String CD = "\uD83D\uDCBF"; + private static final String MIC = "\uD83C\uDFA4"; + + private static final String QUEUE_TITLE = "__%s has added %d new track%s to the Queue:__"; + private static final String QUEUE_DESCRIPTION = "%s **|>** %s\n%s\n%s %s\n%s"; + private static final String QUEUE_INFO = "Info about the Queue: (Size - %d)"; + private static final String ERROR = "Error while loading \"%s\""; + @Override + public void executed(String[] args, CommandEvent event) { + if (!new AudioManager().hasPlayer(event.getGuild()) || new AudioManager().getPlayer(event.getGuild()).getPlayingTrack() == null) { + event.getTextChannel().sendMessage("No song is being played at the moment! *It's your time to shine..*").queue(); + } else { + AudioTrack track = new AudioManager().getPlayer(event.getGuild()).getPlayingTrack(); + event.getTextChannel().sendMessage("Track Info" + String.format(QUEUE_DESCRIPTION, CD, new AudioManager().getOrNull(track.getInfo().title), + "\n\u23F1 **|>** `[ " + getTimestamp(track.getPosition()) + " / " + getTimestamp(track.getInfo().length) + " ]`", + "\n" + MIC, new AudioManager().getOrNull(track.getInfo().author), + "\n\uD83C\uDFA7 **|>** " + "")).queue(); + } + } + + private String getTimestamp(long milis) { + long seconds = milis / 1000; + long hours = Math.floorDiv(seconds, 3600); + seconds = seconds - (hours * 3600); + long mins = Math.floorDiv(seconds, 60); + seconds = seconds - (mins * 60); + return (hours == 0 ? "" : hours + ":") + String.format("%02d", mins) + ":" + String.format("%02d", seconds); + } + + @Override + public String[] labels() { + return new String[]{"info"}; + } + + @Override + public String description() { + return "Shows information about the playing song"; + } + + @Override + public String usage() { + return ""; + } +} diff --git a/src/main/java/com/bbn/hadder/commands/music/PlayCommand.java b/src/main/java/com/bbn/hadder/commands/music/PlayCommand.java new file mode 100644 index 0000000..ddb5c92 --- /dev/null +++ b/src/main/java/com/bbn/hadder/commands/music/PlayCommand.java @@ -0,0 +1,58 @@ +package com.bbn.hadder.commands.music; + +import com.bbn.hadder.audio.AudioManager; +import com.bbn.hadder.commands.Command; +import com.bbn.hadder.commands.CommandEvent; +import com.bbn.hadder.utils.MessageEditor; +import net.dv8tion.jda.api.entities.Message; + +import java.net.URL; + +/** + * @author Skidder / GregTCLTK + */ + +public class PlayCommand implements Command { + + @Override + public void executed(String[] args, CommandEvent event) { + if (args.length > 0) { + if (event.getMember().getVoiceState().inVoiceChannel()) { + String input = event.getMessage().getContentRaw().replaceFirst(event.getRethink().getGuildPrefix(event.getGuild().getId()) + "play ", "").replaceFirst(event.getRethink().getUserPrefix(event.getAuthor().getId()) + "play ", ""); + try { + new URL(input).toURI(); + Message msg = event.getTextChannel().sendMessage(event.getMessageEditor().getMessage(MessageEditor.MessageType.INFO, + "commands.music.play.load.title", "⭕", + "commands.music.play.load.description", "").build()).complete(); + new AudioManager().loadTrack(input, event, msg); + } catch (Exception ignore) { + Message msg = event.getTextChannel().sendMessage(event.getMessageEditor().getMessage(MessageEditor.MessageType.INFO, + "commands.music.play.load.title", "⭕", + "commands.music.play.load.description", "").build()).complete(); + new AudioManager().loadTrack("ytsearch: " + input, event, msg); + } + } else { + event.getTextChannel().sendMessage(event.getMessageEditor().getMessage( + MessageEditor.MessageType.WARNING, + "commands.music.join.error.channel.title", + "commands.music.join.error.channel.description") + .build()).queue(); + } + } else event.getHelpCommand().sendHelp(this, event); + } + + @Override + public String[] labels() { + return new String[]{"play"}; + } + + @Override + public String description() { + return "commands.music.play.help.description"; + } + + @Override + public String usage() { + return "song"; + } +} diff --git a/src/main/java/com/bbn/hadder/commands/music/QueueCommand.java b/src/main/java/com/bbn/hadder/commands/music/QueueCommand.java new file mode 100644 index 0000000..d2178f9 --- /dev/null +++ b/src/main/java/com/bbn/hadder/commands/music/QueueCommand.java @@ -0,0 +1,52 @@ +package com.bbn.hadder.commands.music; + +import com.bbn.hadder.audio.AudioInfo; +import com.bbn.hadder.audio.AudioManager; +import com.bbn.hadder.commands.Command; +import com.bbn.hadder.commands.CommandEvent; +import com.bbn.hadder.utils.MessageEditor; +import net.dv8tion.jda.api.EmbedBuilder; + +import java.util.Set; + +/** + * @author Skidder / GregTCLTK + */ + +public class QueueCommand implements Command { + + @Override + public void executed(String[] args, CommandEvent event) { + if (!new AudioManager().hasPlayer(event.getGuild()) || new AudioManager().getTrackManager(event.getGuild()).getQueuedTracks().isEmpty()) { + event.getTextChannel().sendMessage(event.getMessageEditor().getMessage(MessageEditor.MessageType.WARNING, + "commands.music.queue.error.title", + "commands.music.queue.error.description" + ).build()).queue(); + } else { + Set queue = new AudioManager().getTrackManager(event.getGuild()).getQueuedTracks(); + EmbedBuilder b = event.getMessageEditor().getMessage(MessageEditor.MessageType.INFO, + "commands.music.queue.success.title", + "commands.music.queue.success.description") + .addField("Queued songs", String.valueOf(queue.size()), true); + for (AudioInfo g : queue) { + b.addField(g.getTrack().getInfo().author, g.getTrack().getInfo().title, true); + } + event.getTextChannel().sendMessage(b.build()).queue(); + } + } + + @Override + public String[] labels() { + return new String[]{"queue"}; + } + + @Override + public String description() { + return "Shows the music queue."; + } + + @Override + public String usage() { + return ""; + } +} diff --git a/src/main/java/com/bbn/hadder/commands/music/StopCommand.java b/src/main/java/com/bbn/hadder/commands/music/StopCommand.java new file mode 100644 index 0000000..ab03799 --- /dev/null +++ b/src/main/java/com/bbn/hadder/commands/music/StopCommand.java @@ -0,0 +1,39 @@ +package com.bbn.hadder.commands.music; + +import com.bbn.hadder.audio.AudioManager; +import com.bbn.hadder.commands.Command; +import com.bbn.hadder.commands.CommandEvent; +import com.bbn.hadder.utils.MessageEditor; + +/** + * @author Skidder / GregTCLTK + */ + +public class StopCommand implements Command { + + @Override + public void executed(String[] args, CommandEvent event) { + new AudioManager().players.remove(event.getGuild().getId()); + new AudioManager().getPlayer(event.getGuild()).destroy(); + new AudioManager().getTrackManager(event.getGuild()).purgeQueue(); + event.getGuild().getAudioManager().closeAudioConnection(); + event.getTextChannel().sendMessage(event.getMessageEditor().getMessage(MessageEditor.MessageType.INFO, + "commands.music.stop.success.title", + "commands.music.stop.success.description").build()).queue(); + } + + @Override + public String[] labels() { + return new String[]{"stop"}; + } + + @Override + public String description() { + return "commands.music.stop.help.description"; + } + + @Override + public String usage() { + return ""; + } +} diff --git a/src/main/java/com/bbn/hadder/utils/AudioPlayerSendHandler.java b/src/main/java/com/bbn/hadder/utils/AudioPlayerSendHandler.java deleted file mode 100644 index ff3f483..0000000 --- a/src/main/java/com/bbn/hadder/utils/AudioPlayerSendHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.bbn.hadder.utils; - -/* - * @author Skidder / GregTCLTK - */ - -import net.dv8tion.jda.api.audio.AudioSendHandler; - -import javax.annotation.Nullable; -import java.nio.ByteBuffer; - -public class AudioPlayerSendHandler implements AudioSendHandler { - - @Override - public boolean canProvide() { - return false; - } - - @Nullable - @Override - public ByteBuffer provide20MsAudio() { - - return null; - } -} diff --git a/src/main/java/com/bbn/hadder/utils/MessageEditor.java b/src/main/java/com/bbn/hadder/utils/MessageEditor.java index 2f22d85..c7205f1 100644 --- a/src/main/java/com/bbn/hadder/utils/MessageEditor.java +++ b/src/main/java/com/bbn/hadder/utils/MessageEditor.java @@ -56,7 +56,7 @@ public class MessageEditor { switch (type) { case INFO: builder - .setColor(new Color(47, 94, 105)) + .setColor(new Color(78, 156, 174)) .setFooter("Hadder", "https://bigbotnetwork.com/images/Hadder.png") .setTimestamp(Instant.now()); break; diff --git a/src/main/resources/Translations/Translations_en.properties b/src/main/resources/Translations/Translations_en.properties index 1ffdea2..e6824ff 100644 --- a/src/main/resources/Translations/Translations_en.properties +++ b/src/main/resources/Translations/Translations_en.properties @@ -15,6 +15,7 @@ user+nickname = <@user> region = guildid = content = +song = error = Error none = None @@ -114,16 +115,9 @@ commands.moderation.kick.success.description = I successfully kicked %extra%. commands.moderation.kick.error.title = Not possible commands.moderation.kick.myself.error.description = I can not kick myself\! commands.moderation.kick.yourself.error.description = You can't kick yourself. -commands.moderation.kick.mass.success.description = I successfully kicked 69 Members\! +commands.moderation.kick.mass.success.description = I successfully kicked %extra% Members\! commands.moderation.kick.help.description = Kicks one or more user from the server. commands.moderation.kick.masskick.success.description = I successfully kicked %extra% members. -commands.moderation.link.request.success.description = If i'm on this guild i sent a message to accept the link. -commands.moderation.link.error.title = Wait that's illegal. -commands.moderation.link.request.error.description = You specified the same guild as the guild on which you're reading this -commands.moderation.link.request.accept.title = ) wants to link guilds\! -commands.moderation.link.request.accept.description = React with the reactions to accept or decline it -commands.moderation.link.set.title = Set the thing boi -commands.moderation.link.help.description = Links two or more servers. commands.moderation.nick.success.title = %extra% Successfully nicked %extra% commands.moderation.nick.success.description = I successfully nicked %extra%. commands.moderation.nick.myself.success.description = I successfully changed my nickname. @@ -162,7 +156,7 @@ commands.moderation.rules.emote.error.equal.description = The 1st and 2nd emote commands.moderation.rules.emoji.decline.description = The first emote has been successfully set. Please send me now the decline emote. commands.moderation.rules.emoji.error.description = The given emote can't be used. commands.moderation.rules.help.description = Setup the rules on your Discord server -commands.moderation.starboard.successchannel=Successfully set the Channel\! +commands.moderation.starboard.success.title = Successfully set the Channel\! commands.music.join.success.title = Successfully connected commands.music.join.success.description = I successfully connected to %extra%. @@ -173,11 +167,29 @@ commands.music.join.error.connecting.trying.description = Hadder is already tryi commands.music.join.error.channel.title = No Voice Channel commands.music.join.error.channel.description = You aren't in a Voice Channel. commands.music.join.help.description = Joins your voice channel -commands.music.leave.success.title = \= Successfully disconnected +commands.music.leave.success.title = Successfully disconnected commands.music.leave.success.description = I successfully disconnected from the Voice Channel commands.music.leave.error.tile = Not connected commands.music.leave.error.description = I'm currently in no Voice Channel on this Guild commands.music.leave.help.description = Leaves a voice channel +commands.music.play.load.title = %extra% Now loading %extra% +commands.music.play.load.description = Trying to load the song... +commands.music.play.success.loading.title = %extra% Now playing %extra% +commands.music.play.error.load.title = %extra% Load failed %extra% +commands.music.play.error.load.description = Unfortunately I can not load the given song. +commands.music.play.error.match.title = %extra% No matches %extra% +commands.music.play.error.match.description = I can not find a song named this on YouTube. +commands.music.play.success.title = Title +commands.music.play.success.author = Author +commands.music.play.success.length = Length +commands.music.play.help.description = Plays a song +commands.music.stop.success.title = Successfully stopped +commands.music.stop.success.description = I successfully stopped the song. +commands.music.stop.help.description = Stops the song +commands.music.queue.error.title = No queue +commands.music.queue.error.description = There are no queued songs at the moment +commands.music.queue.success.title = Queue +commands.music.queue.success.description = This is the queue: %extra% commands.nsfw.gif.error.title = GIF not showing? Click here commands.nsfw.img.error.title = Image not showing? Click here