diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7a8565d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.idea/
+**/target/
\ No newline at end of file
diff --git a/README.md b/README.md
index eb830d0..ecc5e9b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# Siren
+A Docker containerized version of [MusicBot](https://github.com/jagrosh/MusicBot)
`docker build .`
-`docker-compose up -d`
+`docker-compose up -d`
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..88c2f64
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,101 @@
+
+
+ 4.0.0
+ com.bensherriff
+ Siren
+ Snapshot
+ jar
+
+
+
+ central
+ Maven Central
+ default
+ https://repo1.maven.org/maven2
+
+
+ dv8tion
+ m2-dv8tion
+ https://m2.dv8tion.net/releases
+
+
+
+
+ UTF-8
+ 17
+ 17
+
+
+
+
+
+ net.dv8tion
+ JDA
+
+ 4.2.1_253
+
+
+ com.sedmelluq
+ lavaplayer
+ 1.3.77
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.5
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.19.0
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.19.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 1.5
+
+
+ package
+
+ shade
+
+
+ true
+ All
+
+
+ *:*
+
+
+
+
+ reference.conf
+
+
+
+ com.bensherriff.siren.MusicBot
+ ${project.artifactId}
+ ${project.version}
+ ${project.artifactId}
+ ${project.version}
+ ${project.groupId}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/bensherriff/siren/AudioPlayerManager.java b/src/main/java/com/bensherriff/siren/AudioPlayerManager.java
new file mode 100644
index 0000000..8564774
--- /dev/null
+++ b/src/main/java/com/bensherriff/siren/AudioPlayerManager.java
@@ -0,0 +1,12 @@
+package com.bensherriff.siren;
+
+import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
+import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
+
+public class AudioPlayerManager extends DefaultAudioPlayerManager {
+
+ public void initialize() {
+ AudioSourceManagers.registerRemoteSources(this);
+ AudioSourceManagers.registerLocalSource(this);
+ }
+}
diff --git a/src/main/java/com/bensherriff/siren/AudioPlayerSendHandler.java b/src/main/java/com/bensherriff/siren/AudioPlayerSendHandler.java
new file mode 100644
index 0000000..bbda831
--- /dev/null
+++ b/src/main/java/com/bensherriff/siren/AudioPlayerSendHandler.java
@@ -0,0 +1,39 @@
+package com.bensherriff.siren;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame;
+import java.nio.Buffer;
+import net.dv8tion.jda.api.audio.AudioSendHandler;
+
+import java.nio.ByteBuffer;
+
+public class AudioPlayerSendHandler implements AudioSendHandler {
+ private final AudioPlayer audioPlayer;
+ private final ByteBuffer buffer;
+ private final MutableAudioFrame frame;
+
+ public AudioPlayerSendHandler(AudioPlayer audioPlayer) {
+ this.audioPlayer = audioPlayer;
+ this.buffer = ByteBuffer.allocate(1024);
+ this.frame = new MutableAudioFrame();
+ this.frame.setBuffer(buffer);
+ }
+
+ @Override
+ public boolean canProvide() {
+ // returns true if audio was provided
+ return audioPlayer.provide(frame);
+ }
+
+ @Override
+ public ByteBuffer provide20MsAudio() {
+ // flip to make it a read buffer
+ ((Buffer) buffer).flip();
+ return buffer;
+ }
+
+ @Override
+ public boolean isOpus() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/bensherriff/siren/MusicBot.java b/src/main/java/com/bensherriff/siren/MusicBot.java
new file mode 100644
index 0000000..6a137ea
--- /dev/null
+++ b/src/main/java/com/bensherriff/siren/MusicBot.java
@@ -0,0 +1,132 @@
+package com.bensherriff.siren;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
+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.JDABuilder;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.TextChannel;
+import net.dv8tion.jda.api.entities.VoiceChannel;
+import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import net.dv8tion.jda.api.managers.AudioManager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static net.dv8tion.jda.api.requests.GatewayIntent.GUILD_MESSAGES;
+import static net.dv8tion.jda.api.requests.GatewayIntent.GUILD_VOICE_STATES;
+
+public class MusicBot extends ListenerAdapter {
+ public static void main(String[] args) throws Exception {
+
+ JDABuilder.create("OTMyMzAxMjQ4NTQ2MzYxMzQ2.YeQ_Mg.n4H8Cl3dQ1u5aFL1ZvTmfcGwpEY", GUILD_MESSAGES, GUILD_VOICE_STATES)
+ .addEventListeners(new MusicBot())
+ .build();
+ }
+
+ private final AudioPlayerManager playerManager;
+ private final Map musicManagers;
+
+ private MusicBot() {
+ this.musicManagers = new HashMap<>();
+
+ this.playerManager = new DefaultAudioPlayerManager();
+ AudioSourceManagers.registerRemoteSources(playerManager);
+ AudioSourceManagers.registerLocalSource(playerManager);
+ }
+
+ private synchronized MusicManager getGuildAudioPlayer(Guild guild) {
+ long guildId = Long.parseLong(guild.getId());
+ MusicManager musicManager = musicManagers.get(guildId);
+
+ if (musicManager == null) {
+ musicManager = new MusicManager(playerManager);
+ musicManagers.put(guildId, musicManager);
+ }
+
+ guild.getAudioManager().setSendingHandler(musicManager.getSendHandler());
+
+ return musicManager;
+ }
+
+ @Override
+ public void onGuildMessageReceived(GuildMessageReceivedEvent event) {
+ String[] command = event.getMessage().getContentRaw().split(" ", 2);
+ if (event.getAuthor().isBot()) return;
+
+ TextChannel channel = event.getChannel();
+
+ if ("!play".equals(command[0]) && command.length == 2) {
+ loadAndPlay(channel, command[1]);
+ } else if ("!skip".equals(command[0])) {
+ skipTrack(channel);
+ } else if ("!stop".equals(command[0])) {
+
+ }
+
+ super.onGuildMessageReceived(event);
+ }
+
+ private void loadAndPlay(final TextChannel channel, final String trackUrl) {
+ MusicManager musicManager = getGuildAudioPlayer(channel.getGuild());
+
+ playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler() {
+ @Override
+ public void trackLoaded(AudioTrack track) {
+ channel.sendMessage("Adding **" + track.getInfo().title + "** to queue").queue();
+
+ play(channel.getGuild(), musicManager, track);
+ }
+
+ @Override
+ public void playlistLoaded(AudioPlaylist playlist) {
+ AudioTrack firstTrack = playlist.getSelectedTrack();
+
+ if (firstTrack == null) {
+ firstTrack = playlist.getTracks().get(0);
+ }
+
+ channel.sendMessage("Adding **" + firstTrack.getInfo().title + "** to queue (first track of playlist " + playlist.getName() + ")").queue();
+
+ play(channel.getGuild(), musicManager, firstTrack);
+ }
+
+ @Override
+ public void noMatches() {
+ channel.sendMessage("Nothing found by " + trackUrl).queue();
+ }
+
+ @Override
+ public void loadFailed(FriendlyException exception) {
+ channel.sendMessage("Could not play: " + exception.getMessage()).queue();
+ }
+ });
+ }
+
+ private void play(Guild guild, MusicManager musicManager, AudioTrack track) {
+ connectToFirstVoiceChannel(guild.getAudioManager());
+
+ musicManager.scheduler.queue(track);
+ }
+
+ private void skipTrack(TextChannel channel) {
+ MusicManager musicManager = getGuildAudioPlayer(channel.getGuild());
+ musicManager.scheduler.nextTrack();
+
+ channel.sendMessage("Skipped to next track.").queue();
+ }
+
+ private static void connectToFirstVoiceChannel(AudioManager audioManager) {
+ if (!audioManager.isConnected()) {
+ for (VoiceChannel voiceChannel : audioManager.getGuild().getVoiceChannels()) {
+ audioManager.openAudioConnection(voiceChannel);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/bensherriff/siren/MusicManager.java b/src/main/java/com/bensherriff/siren/MusicManager.java
new file mode 100644
index 0000000..80dc631
--- /dev/null
+++ b/src/main/java/com/bensherriff/siren/MusicManager.java
@@ -0,0 +1,20 @@
+package com.bensherriff.siren;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
+
+public class MusicManager {
+
+ public final AudioPlayer player;
+ public final TrackScheduler scheduler;
+
+ public MusicManager(AudioPlayerManager manager) {
+ player = manager.createPlayer();
+ scheduler = new TrackScheduler(player);
+ player.addListener(scheduler);
+ }
+
+ public AudioPlayerSendHandler getSendHandler() {
+ return new AudioPlayerSendHandler(player);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bensherriff/siren/TrackScheduler.java b/src/main/java/com/bensherriff/siren/TrackScheduler.java
new file mode 100644
index 0000000..45cd4d0
--- /dev/null
+++ b/src/main/java/com/bensherriff/siren/TrackScheduler.java
@@ -0,0 +1,56 @@
+package com.bensherriff.siren;
+
+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 java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * This class schedules tracks for the audio player. It contains the queue of tracks.
+ */
+public class TrackScheduler extends AudioEventAdapter {
+ private final AudioPlayer player;
+ private final BlockingQueue queue;
+
+ /**
+ * @param player The audio player this scheduler uses
+ */
+ public TrackScheduler(AudioPlayer player) {
+ this.player = player;
+ this.queue = new LinkedBlockingQueue<>();
+ }
+
+ /**
+ * Add the next track to queue or play right away if nothing is in the queue.
+ *
+ * @param track The track to play or add to queue.
+ */
+ public void queue(AudioTrack track) {
+ // Calling startTrack with the noInterrupt set to true will start the track only if nothing is currently playing. If
+ // something is playing, it returns false and does nothing. In that case the player was already playing so this
+ // track goes to the queue instead.
+ if (!player.startTrack(track, true)) {
+ queue.offer(track);
+ }
+ }
+
+ /**
+ * Start the next track, stopping the current one if it is playing.
+ */
+ public void nextTrack() {
+ // Start the next track, regardless of if something is already playing or not. In case queue was empty, we are
+ // giving null to startTrack, which is a valid argument and will simply stop the player.
+ player.startTrack(queue.poll(), false);
+ }
+
+ @Override
+ public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
+ // Only start the next track if the end reason is suitable for it (FINISHED or LOAD_FAILED)
+ if (endReason.mayStartNext) {
+ nextTrack();
+ }
+ }
+}
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..ba459e6
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file