Version 1: Basic functionality #1
|
@ -21,7 +21,7 @@ public class HandieBot {
|
||||||
|
|
||||||
private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY";
|
private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY";
|
||||||
|
|
||||||
private static IDiscordClient client;
|
public static IDiscordClient client;
|
||||||
|
|
||||||
private CommandHandler commandHandler;
|
private CommandHandler commandHandler;
|
||||||
private MusicPlayer musicPlayer;
|
private MusicPlayer musicPlayer;
|
||||||
|
|
|
@ -35,10 +35,14 @@ public class CommandHandler {
|
||||||
String command = extractCommand(message);
|
String command = extractCommand(message);
|
||||||
String[] args = extractArgs(message);
|
String[] args = extractArgs(message);
|
||||||
if (guild != null && command != null){
|
if (guild != null && command != null){
|
||||||
DisappearingMessage.deleteMessageAfter(2000, message);
|
DisappearingMessage.deleteMessageAfter(1000, message);
|
||||||
if (command.equals("play") && args.length == 1){
|
if (command.equals("play")){
|
||||||
//Play or queue a song.
|
//Play or queue a song.
|
||||||
|
if (args.length == 1) {
|
||||||
this.bot.getMusicPlayer().loadToQueue(guild, args[0]);
|
this.bot.getMusicPlayer().loadToQueue(guild, args[0]);
|
||||||
|
} else if (args.length == 0){
|
||||||
|
this.bot.getMusicPlayer().playQueue(guild);
|
||||||
|
}
|
||||||
} else if (command.equals("skip") && args.length == 0){
|
} else if (command.equals("skip") && args.length == 0){
|
||||||
//Skip the current song.
|
//Skip the current song.
|
||||||
this.bot.getMusicPlayer().skipTrack(guild);
|
this.bot.getMusicPlayer().skipTrack(guild);
|
||||||
|
@ -53,6 +57,12 @@ public class CommandHandler {
|
||||||
//TODO implement repeat command.
|
//TODO implement repeat command.
|
||||||
} else if (command.equals("clear")){
|
} else if (command.equals("clear")){
|
||||||
//TODO clear command.
|
//TODO clear command.
|
||||||
|
} else if (command.equals("quit")){
|
||||||
|
//Quit the application.
|
||||||
|
channel.sendMessage("Quitting HandieBot functions.");
|
||||||
|
this.bot.getMusicPlayer().quit(guild);
|
||||||
|
} else if (command.equals("playlist")){
|
||||||
|
//Do playlist actions.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,12 +100,7 @@ public class MusicPlayer {
|
||||||
this.voiceChannels.put(guild, channels.get(0));
|
this.voiceChannels.put(guild, channels.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IVoiceChannel vc = this.voiceChannels.get(guild);
|
return this.voiceChannels.get(guild);
|
||||||
if (!vc.isConnected()){
|
|
||||||
System.out.println("Joined voice channel.");
|
|
||||||
vc.join();
|
|
||||||
}
|
|
||||||
return vc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -136,7 +131,8 @@ public class MusicPlayer {
|
||||||
}
|
}
|
||||||
builder.withTimestamp(System.currentTimeMillis());
|
builder.withTimestamp(System.currentTimeMillis());
|
||||||
builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track"+(tracks.size() > 1 ? "s" : "")+".", sb.toString(), false);
|
builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track"+(tracks.size() > 1 ? "s" : "")+".", sb.toString(), false);
|
||||||
getChatChannel(guild).sendMessage(builder.build());
|
IMessage message = getChatChannel(guild).sendMessage(builder.build());
|
||||||
|
DisappearingMessage.deleteMessageAfter(6000, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,11 +178,8 @@ public class MusicPlayer {
|
||||||
public void addToQueue(IGuild guild, AudioTrack track){
|
public void addToQueue(IGuild guild, AudioTrack track){
|
||||||
IVoiceChannel voiceChannel = getVoiceChannel(guild);
|
IVoiceChannel voiceChannel = getVoiceChannel(guild);
|
||||||
if (voiceChannel != null){
|
if (voiceChannel != null){
|
||||||
//Check to make sure sound can be played.
|
if (!voiceChannel.isConnected()) {
|
||||||
if (guild.getAudioManager().getAudioProvider() == null){
|
voiceChannel.join();
|
||||||
new DisappearingMessage(getChatChannel(guild), "Audio provider not set. Please try again.", 3000);
|
|
||||||
guild.getAudioManager().setAudioProvider(getMusicManager(guild).getAudioProvider());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
long timeUntilPlay = getMusicManager(guild).scheduler.getTimeUntilDone();
|
long timeUntilPlay = getMusicManager(guild).scheduler.getTimeUntilDone();
|
||||||
getMusicManager(guild).scheduler.queue(track);
|
getMusicManager(guild).scheduler.queue(track);
|
||||||
|
@ -206,6 +199,17 @@ public class MusicPlayer {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If possible, try to begin playing from the track scheduler's queue.
|
||||||
|
*/
|
||||||
|
public void playQueue(IGuild guild){
|
||||||
|
IVoiceChannel vc = this.getVoiceChannel(guild);
|
||||||
|
if (!vc.isConnected()){
|
||||||
|
vc.join();
|
||||||
|
}
|
||||||
|
getMusicManager(guild).scheduler.nextTrack();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips the current track.
|
* Skips the current track.
|
||||||
*/
|
*/
|
||||||
|
@ -214,4 +218,16 @@ public class MusicPlayer {
|
||||||
new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000);
|
new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops playback and disconnects from the voice channel, to cease music actions.
|
||||||
|
* @param guild The guild to quit from.
|
||||||
|
*/
|
||||||
|
public void quit(IGuild guild){
|
||||||
|
getMusicManager(guild).scheduler.quit();
|
||||||
|
IVoiceChannel vc = this.getVoiceChannel(guild);
|
||||||
|
if (vc.isConnected()){
|
||||||
|
vc.leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
package handiebot.lavaplayer;
|
||||||
|
|
||||||
|
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.tools.FriendlyException;
|
||||||
|
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||||
|
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andrew Lalis
|
||||||
|
* A Playlist is a list of AudioTracks which a track scheduler can pull from to create a queue filled with songs. The
|
||||||
|
* playlist is persistent, i.e. it is saved into a file.
|
||||||
|
*/
|
||||||
|
public class Playlist {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private long creatorUID;
|
||||||
|
|
||||||
|
List<AudioTrack> tracks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty playlist template.
|
||||||
|
* @param name The name of the playlist.
|
||||||
|
* @param creatorUID The ID of the user who created it.
|
||||||
|
*/
|
||||||
|
public Playlist(String name, long creatorUID){
|
||||||
|
this.name = name;
|
||||||
|
this.creatorUID = creatorUID;
|
||||||
|
this.tracks = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a playlist from a file with the given name.
|
||||||
|
* @param name The name of the file.
|
||||||
|
*/
|
||||||
|
public Playlist(String name){
|
||||||
|
this.name = name;
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName(){
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCreatorUID(){
|
||||||
|
return this.creatorUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AudioTrack> getTracks(){
|
||||||
|
return this.tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a track to the end of the playlist.
|
||||||
|
* @param track The track to add.
|
||||||
|
*/
|
||||||
|
public void addTrack(AudioTrack track){
|
||||||
|
this.tracks.add(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a track from the playlist.
|
||||||
|
* @param track The track to remove.
|
||||||
|
*/
|
||||||
|
public void removeTrack(AudioTrack track){
|
||||||
|
this.tracks.remove(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next track, i.e. the first one in the list, and removes it from the internal list.
|
||||||
|
* @return The AudioTrack that should be played next.
|
||||||
|
*/
|
||||||
|
public AudioTrack getNextTrackAndRemove(boolean shouldShuffle){
|
||||||
|
return this.tracks.remove((shouldShuffle ? getShuffledIndex(this.tracks.size()) : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next track to be played, and re-adds it to the end of the playlist, as it would do in a loop.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public AudioTrack getNextTrackAndRequeue(boolean shouldShuffle){
|
||||||
|
AudioTrack track = this.tracks.remove((shouldShuffle ? getShuffledIndex(this.tracks.size()) : 0));
|
||||||
|
this.tracks.add(track);
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a 'shuffled index' from a given list length. That means:
|
||||||
|
* - A random number from 0 to (listLength-1) - threshold*(listLength), where threshold is some percentage of
|
||||||
|
* recent songs that should be ignored; for example, the most recent 20% of the playlist can be ignored.
|
||||||
|
* - A greater likelihood for numbers closer to 0 (those which have not been played in a while).
|
||||||
|
* @param listLength The number of items in a potential list to choose from.
|
||||||
|
* @return A pseudo-random choice as to which item to pick from the list.
|
||||||
|
*/
|
||||||
|
public static int getShuffledIndex(int listLength){
|
||||||
|
float threshold = 0.2f;
|
||||||
|
int trueLength = listLength - (int)threshold*listLength;
|
||||||
|
Random rand = new Random();
|
||||||
|
//TODO Add in a small gradient in chance for a song to be picked.
|
||||||
|
return rand.nextInt(trueLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the playlist to a file in its name. The playlists are saved into a file in the user's home directory.
|
||||||
|
*/
|
||||||
|
public void save(){
|
||||||
|
String homeDir = System.getProperty("user.home");
|
||||||
|
File playlistDir = new File(homeDir+"/.handiebot/playlist");
|
||||||
|
if (!playlistDir.exists()){
|
||||||
|
if (!playlistDir.mkdirs()){
|
||||||
|
System.out.println("Unable to make directory: "+playlistDir.getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File playlistFile = new File(playlistDir.getPath()+"/"+this.name.replace(" ", "_")+".txt");
|
||||||
|
System.out.println("Saving playlist to: "+playlistFile.getAbsolutePath());
|
||||||
|
try(Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(playlistFile)))){
|
||||||
|
writer.write(this.name+'\n');
|
||||||
|
writer.write(Long.toString(this.creatorUID)+'\n');
|
||||||
|
writer.write(Integer.toString(this.tracks.size())+'\n');
|
||||||
|
for (AudioTrack track : this.tracks){
|
||||||
|
writer.write(track.getInfo().uri);
|
||||||
|
writer.write('\n');
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the playlist from a file with the playlist's name.
|
||||||
|
*/
|
||||||
|
public void load(){//TODO Make load work!!!
|
||||||
|
String path = System.getProperty("user.home")+"/.handiebot/playlist/"+this.name.replace(" ", "_")+".txt";
|
||||||
|
File playlistFile = new File(path);
|
||||||
|
if (playlistFile.exists()){
|
||||||
|
try {
|
||||||
|
List<String> lines = Files.readAllLines(Paths.get(playlistFile.toURI()));
|
||||||
|
this.name = lines.remove(0);
|
||||||
|
this.creatorUID = Long.parseLong(lines.remove(0));
|
||||||
|
int trackCount = Integer.parseInt(lines.remove(0));
|
||||||
|
this.tracks = new ArrayList<>(trackCount);
|
||||||
|
AudioPlayerManager pm = new DefaultAudioPlayerManager();
|
||||||
|
for (int i = 0; i < trackCount; i++){
|
||||||
|
System.out.println("Loading item "+i);
|
||||||
|
String url = lines.remove(0);
|
||||||
|
pm.loadItem(url, new AudioLoadResultHandler() {
|
||||||
|
@Override
|
||||||
|
public void trackLoaded(AudioTrack audioTrack) {
|
||||||
|
System.out.println("Added track");
|
||||||
|
tracks.add(audioTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playlistLoaded(AudioPlaylist audioPlaylist) {
|
||||||
|
System.out.println("Playlist loaded.");
|
||||||
|
//Do nothing. This should not happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noMatches() {
|
||||||
|
System.out.println("No matches for: "+url);
|
||||||
|
//Do nothing. This should not happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadFailed(FriendlyException e) {
|
||||||
|
System.out.println("Load failed: "+e.getMessage());
|
||||||
|
//Do nothing. This should not happen.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,10 +8,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
|
||||||
import sx.blah.discord.handle.obj.IChannel;
|
import sx.blah.discord.handle.obj.IChannel;
|
||||||
import sx.blah.discord.handle.obj.IGuild;
|
import sx.blah.discord.handle.obj.IGuild;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andrew Lalis
|
* @author Andrew Lalis
|
||||||
|
@ -19,9 +16,11 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||||
public class TrackScheduler extends AudioEventAdapter {
|
public class TrackScheduler extends AudioEventAdapter {
|
||||||
|
|
||||||
private final AudioPlayer player;
|
private final AudioPlayer player;
|
||||||
private final BlockingQueue<AudioTrack> queue;
|
|
||||||
|
private Playlist activePlaylist;
|
||||||
|
|
||||||
private boolean repeat = false;
|
private boolean repeat = false;
|
||||||
|
private boolean shuffle = false;
|
||||||
|
|
||||||
private IGuild guild;
|
private IGuild guild;
|
||||||
|
|
||||||
|
@ -32,7 +31,8 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||||
public TrackScheduler(AudioPlayer player, IGuild guild){
|
public TrackScheduler(AudioPlayer player, IGuild guild){
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.guild = guild;
|
this.guild = guild;
|
||||||
this.queue = new LinkedBlockingQueue<>();
|
this.activePlaylist = new Playlist("HandieBot Active Playlist", 283652989212688384L);
|
||||||
|
//this.activePlaylist = new Playlist("HandieBot Active Playlist");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,6 +51,22 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||||
return this.repeat;
|
return this.repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether or not to randomize the next track to be played.
|
||||||
|
* @param value True if shuffled should become active.
|
||||||
|
*/
|
||||||
|
public void setShuffle(boolean value){
|
||||||
|
this.shuffle = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not shuffling is active.
|
||||||
|
* @return True if shuffling is active, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isShuffling(){
|
||||||
|
return this.shuffle;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the time until the bot is done playing sound, at the current rate.
|
* Returns the time until the bot is done playing sound, at the current rate.
|
||||||
* @return The milliseconds until music stops.
|
* @return The milliseconds until music stops.
|
||||||
|
@ -72,7 +88,7 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||||
* @return A list of tracks in the queue.
|
* @return A list of tracks in the queue.
|
||||||
*/
|
*/
|
||||||
public List<AudioTrack> queueList(){
|
public List<AudioTrack> queueList(){
|
||||||
return new ArrayList<>(this.queue);
|
return this.activePlaylist.getTracks();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,7 +99,8 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||||
if (player.getPlayingTrack() == null){
|
if (player.getPlayingTrack() == null){
|
||||||
player.startTrack(track, false);
|
player.startTrack(track, false);
|
||||||
} else {
|
} else {
|
||||||
queue.offer(track);
|
this.activePlaylist.addTrack(track);
|
||||||
|
this.activePlaylist.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,11 +108,16 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||||
* Starts the next track, stopping the current one if it's playing.
|
* Starts the next track, stopping the current one if it's playing.
|
||||||
*/
|
*/
|
||||||
public void nextTrack(){
|
public void nextTrack(){
|
||||||
AudioTrack track = queue.poll();
|
AudioTrack track = (this.repeat ? this.activePlaylist.getNextTrackAndRequeue(this.shuffle) : this.activePlaylist.getNextTrackAndRemove(this.shuffle));
|
||||||
|
this.activePlaylist.save();
|
||||||
player.startTrack(track, false);
|
player.startTrack(track, false);
|
||||||
if (this.repeat){
|
|
||||||
this.queue.add(track);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user wishes to quit, stop the currently played track.
|
||||||
|
*/
|
||||||
|
public void quit(){
|
||||||
|
this.player.stopTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -111,6 +133,7 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||||
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
|
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
|
||||||
System.out.println("Track ended.");
|
System.out.println("Track ended.");
|
||||||
if (endReason.mayStartNext){
|
if (endReason.mayStartNext){
|
||||||
|
System.out.println("Moving to next track.");
|
||||||
nextTrack();
|
nextTrack();
|
||||||
} else {
|
} else {
|
||||||
System.out.println(endReason.toString());
|
System.out.println(endReason.toString());
|
||||||
|
@ -122,9 +145,4 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) {
|
|
||||||
super.onTrackStuck(player, track, thresholdMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue