diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..9beb865
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,48 @@
+
+
+ 4.0.0
+
+ net.agspace.handiebot
+ handiebot
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.7
+
+
+
+
+ jar
+
+
+
+ jcenter
+ http://jcenter.bintray.com
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
+
+
+ com.github.austinv11
+ Discord4J
+ 2.8.2
+
+
+ com.sedmelluq
+ lavaplayer
+ 1.2.39
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java
new file mode 100644
index 0000000..274ccce
--- /dev/null
+++ b/src/main/java/handiebot/HandieBot.java
@@ -0,0 +1,149 @@
+package handiebot;
+
+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 handiebot.command.CommandHandler;
+import handiebot.lavaplayer.GuildMusicManager;
+import sx.blah.discord.api.ClientBuilder;
+import sx.blah.discord.api.IDiscordClient;
+import sx.blah.discord.api.events.EventSubscriber;
+import sx.blah.discord.handle.audio.IAudioManager;
+import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;
+import sx.blah.discord.handle.obj.IChannel;
+import sx.blah.discord.handle.obj.IGuild;
+import sx.blah.discord.handle.obj.IVoiceChannel;
+import sx.blah.discord.util.DiscordException;
+import sx.blah.discord.util.MissingPermissionsException;
+import sx.blah.discord.util.RateLimitException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Andrew Lalis
+ * Main Class for the discord bot. Contains client loading information and general event processing.
+ */
+public class HandieBot {
+
+ private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY";
+
+ private static IDiscordClient client;
+
+ private CommandHandler commandHandler;
+
+ public static void main(String[] args) throws DiscordException, RateLimitException {
+ System.out.println("Logging bot in...");
+ client = new ClientBuilder().withToken(TOKEN).build();
+ client.getDispatcher().registerListener(new HandieBot());
+ client.login();
+ }
+
+ private final AudioPlayerManager playerManager;
+ private final Map musicManagers;
+
+ private HandieBot() {
+ this.musicManagers = new HashMap<>();
+ this.playerManager = new DefaultAudioPlayerManager();
+ AudioSourceManagers.registerRemoteSources(playerManager);
+ AudioSourceManagers.registerLocalSource(playerManager);
+
+ this.commandHandler = new CommandHandler(this);
+ }
+
+ private synchronized GuildMusicManager getGuildAudioPlayer(IGuild guild) {
+ long guildId = Long.parseLong(guild.getID());
+ GuildMusicManager musicManager = musicManagers.get(guildId);
+
+ if (musicManager == null) {
+ musicManager = new GuildMusicManager(playerManager);
+ musicManagers.put(guildId, musicManager);
+ }
+
+ guild.getAudioManager().setAudioProvider(musicManager.getAudioProvider());
+
+ return musicManager;
+ }
+
+ @EventSubscriber
+ public void onMessageReceived(MessageReceivedEvent event) {
+ this.commandHandler.handleCommand(event);
+ }
+
+ public void loadAndPlay(final IChannel channel, final String trackUrl) {
+ GuildMusicManager musicManager = getGuildAudioPlayer(channel.getGuild());
+
+ playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler() {
+ @Override
+ public void trackLoaded(AudioTrack track) {
+ sendMessageToChannel(channel, "Adding to queue " + track.getInfo().title);
+
+ play(channel.getGuild(), musicManager, track);
+ }
+
+ @Override
+ public void playlistLoaded(AudioPlaylist playlist) {
+ AudioTrack firstTrack = playlist.getSelectedTrack();
+
+ if (firstTrack == null) {
+ firstTrack = playlist.getTracks().get(0);
+ }
+
+ sendMessageToChannel(channel, "Adding to queue " + firstTrack.getInfo().title + " (first track of playlist " + playlist.getName() + ")");
+
+ play(channel.getGuild(), musicManager, firstTrack);
+ }
+
+ @Override
+ public void noMatches() {
+ sendMessageToChannel(channel, "Nothing found by " + trackUrl);
+ }
+
+ @Override
+ public void loadFailed(FriendlyException exception) {
+ sendMessageToChannel(channel, "Could not play: " + exception.getMessage());
+ }
+ });
+ }
+
+ private void play(IGuild guild, GuildMusicManager musicManager, AudioTrack track) {
+ connectToFirstVoiceChannel(guild.getAudioManager());
+
+ musicManager.scheduler.queue(track);
+ }
+
+ public void skipTrack(IChannel channel) {
+ GuildMusicManager musicManager = getGuildAudioPlayer(channel.getGuild());
+ musicManager.scheduler.nextTrack();
+
+ sendMessageToChannel(channel, "Skipped to next track.");
+ }
+
+ private void sendMessageToChannel(IChannel channel, String message) {
+ try {
+ channel.sendMessage(message);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void connectToFirstVoiceChannel(IAudioManager audioManager) {
+ for (IVoiceChannel voiceChannel : audioManager.getGuild().getVoiceChannels()) {
+ if (voiceChannel.isConnected()) {
+ return;
+ }
+ }
+
+ for (IVoiceChannel voiceChannel : audioManager.getGuild().getVoiceChannels()) {
+ try {
+ voiceChannel.join();
+ } catch (MissingPermissionsException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java
new file mode 100644
index 0000000..2567e3f
--- /dev/null
+++ b/src/main/java/handiebot/command/CommandHandler.java
@@ -0,0 +1,88 @@
+package handiebot.command;
+
+import com.sun.istack.internal.NotNull;
+import handiebot.HandieBot;
+import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;
+import sx.blah.discord.handle.obj.*;
+import sx.blah.discord.util.EmbedBuilder;
+
+/**
+ * @author Andrew Lalis
+ * Class to process commands.
+ */
+public class CommandHandler {
+
+ private static String PREFIX = "!";
+
+ private final HandieBot bot;
+
+ public CommandHandler(HandieBot bot){
+ this.bot = bot;
+ }
+
+ /**
+ * Main method to handle user messages.
+ * @param event The event generated by the message.
+ */
+ public void handleCommand(MessageReceivedEvent event){
+ IMessage message = event.getMessage();
+ IUser user = event.getAuthor();
+ IChannel channel = event.getChannel();
+ IGuild guild = event.getGuild();
+ String command = extractCommand(message);
+ String[] args = extractArgs(message);
+ if (guild != null && command != null){
+ if (command.equals("play") && args.length == 1){
+ this.bot.loadAndPlay(channel, args[0]);
+ } else if (command.equals("help")){
+
+ }
+ }
+ }
+
+ /**
+ * Returns a command word, if one exists, from a given message.
+ * @param message The message to get a command from.
+ * @return The command word, minus the prefix, or null.
+ */
+ private String extractCommand(IMessage message){
+ String[] words = message.getContent().split(" ");
+ if (words[0].startsWith(PREFIX)){
+ return words[0].replaceFirst(PREFIX, "").toLowerCase();
+ }
+ return null;
+ }
+
+ /**
+ * Extracts a list of arguments from a message, assuming a command exists.
+ * @param message The message to parse.
+ * @return A list of strings representing args.
+ */
+ @NotNull
+ private String[] extractArgs(IMessage message){
+ String[] words = message.getContent().split(" ");
+ if (words[0].startsWith(PREFIX)){
+ String[] args = new String[words.length-1];
+ for (int i = 0; i < words.length-1; i++){
+ args[i] = words[i+1];
+ }
+ return args;
+ }
+ return new String[0];
+ }
+
+ private void sendHelpInfo(IUser user){
+ IPrivateChannel pm = user.getOrCreatePMChannel();
+ EmbedBuilder builder = new EmbedBuilder();
+
+ }
+
+ /**
+ * Sets the prefix used to identify commands.
+ * @param prefix The prefix appended to the beginning of commands.
+ */
+ public void setPrefix(String prefix){
+ PREFIX = prefix;
+ }
+
+}
diff --git a/src/main/java/handiebot/lavaplayer/AudioLoadResultHandler.java b/src/main/java/handiebot/lavaplayer/AudioLoadResultHandler.java
new file mode 100644
index 0000000..a07d21d
--- /dev/null
+++ b/src/main/java/handiebot/lavaplayer/AudioLoadResultHandler.java
@@ -0,0 +1,40 @@
+package handiebot.lavaplayer;
+
+import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
+import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+
+/**
+ * @author Andrew Lalis
+ */
+public class AudioLoadResultHandler implements com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler {
+
+ private TrackScheduler scheduler;
+
+ public AudioLoadResultHandler(TrackScheduler scheduler){
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public void trackLoaded(AudioTrack audioTrack) {
+ System.out.println("Adding to queue "+ audioTrack.getInfo().title);
+ scheduler.queue(audioTrack);
+ }
+
+ @Override
+ public void playlistLoaded(AudioPlaylist audioPlaylist) {
+ System.out.println("Adding playlist to queue.");
+ audioPlaylist.getTracks().forEach(track -> this.scheduler.queue(track));
+ }
+
+ @Override
+ public void noMatches() {
+ System.out.println("No matches!");
+ }
+
+ @Override
+ public void loadFailed(FriendlyException e) {
+ System.out.println("Load failed.");
+ e.printStackTrace();
+ }
+}
diff --git a/src/main/java/handiebot/lavaplayer/AudioProvider.java b/src/main/java/handiebot/lavaplayer/AudioProvider.java
new file mode 100644
index 0000000..0155f4e
--- /dev/null
+++ b/src/main/java/handiebot/lavaplayer/AudioProvider.java
@@ -0,0 +1,52 @@
+package handiebot.lavaplayer;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
+import sx.blah.discord.handle.audio.AudioEncodingType;
+import sx.blah.discord.handle.audio.IAudioProvider;
+
+/**
+ * Created by Andrew's Computer on 18-Jun-17.
+ */
+public class AudioProvider implements IAudioProvider {
+ private final AudioPlayer audioPlayer;
+ private AudioFrame lastFrame;
+
+ /**
+ * @param audioPlayer Audio player to wrap.
+ */
+ public AudioProvider(AudioPlayer audioPlayer) {
+ this.audioPlayer = audioPlayer;
+ }
+
+ @Override
+ public boolean isReady() {
+ if (lastFrame == null) {
+ lastFrame = audioPlayer.provide();
+ }
+
+ return lastFrame != null;
+ }
+
+ @Override
+ public byte[] provide() {
+ if (lastFrame == null) {
+ lastFrame = audioPlayer.provide();
+ }
+
+ byte[] data = lastFrame != null ? lastFrame.data : null;
+ lastFrame = null;
+
+ return data;
+ }
+
+ @Override
+ public int getChannels() {
+ return 2;
+ }
+
+ @Override
+ public AudioEncodingType getAudioEncodingType() {
+ return AudioEncodingType.OPUS;
+ }
+}
diff --git a/src/main/java/handiebot/lavaplayer/GuildMusicManager.java b/src/main/java/handiebot/lavaplayer/GuildMusicManager.java
new file mode 100644
index 0000000..06af150
--- /dev/null
+++ b/src/main/java/handiebot/lavaplayer/GuildMusicManager.java
@@ -0,0 +1,26 @@
+package handiebot.lavaplayer;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
+
+/**
+ * @author Andrew Lalis
+ * Holds the player and track scheduler for a guild.
+ */
+public class GuildMusicManager {
+
+ public final AudioPlayer player;
+
+ public final TrackScheduler scheduler;
+
+ public GuildMusicManager(AudioPlayerManager manager){
+ this.player = manager.createPlayer();
+ this.scheduler = new TrackScheduler(this.player);
+ this.player.addListener(this.scheduler);
+ }
+
+ public AudioProvider getAudioProvider(){
+ return new AudioProvider(this.player);
+ }
+
+}
diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java
new file mode 100644
index 0000000..555a6f4
--- /dev/null
+++ b/src/main/java/handiebot/lavaplayer/TrackScheduler.java
@@ -0,0 +1,65 @@
+package handiebot.lavaplayer;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
+import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * @author Andrew Lalis
+ */
+public class TrackScheduler extends AudioEventAdapter {
+
+ private final AudioPlayer player;
+ private final BlockingQueue queue;
+
+ /**
+ * Constructs a new track scheduler with the given player.
+ * @param player The audio player this scheduler uses.
+ */
+ public TrackScheduler(AudioPlayer player){
+ this.player = player;
+ this.queue = new LinkedBlockingQueue<>();
+ }
+
+ /**
+ * Add the next track to the queue or play right away if nothing is in the queue.
+ * @param track The track to play or add to the queue.
+ */
+ public void queue(AudioTrack track){
+ if (!player.startTrack(track, true)){
+ System.out.println("Unable to start track immediately, adding to queue.");
+ queue.offer(track);
+ }
+ }
+
+ /**
+ * Starts the next track, stopping the current one if it's playing.
+ */
+ public void nextTrack(){
+ player.startTrack(queue.poll(), false);
+ }
+
+ @Override
+ public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
+ if (endReason.mayStartNext){
+ nextTrack();
+ } else {
+ System.out.println(endReason.toString());
+ }
+ }
+
+ @Override
+ public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception){
+ exception.printStackTrace();
+ }
+
+ @Override
+ public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) {
+ super.onTrackStuck(player, track, thresholdMs);
+ }
+}
diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png
new file mode 100644
index 0000000..f11fbb9
Binary files /dev/null and b/src/main/resources/icon.png differ
diff --git a/src/main/resources/icon.svg b/src/main/resources/icon.svg
new file mode 100644
index 0000000..5ab890d
--- /dev/null
+++ b/src/main/resources/icon.svg
@@ -0,0 +1,121 @@
+
+
+
+