First Commit, working play feature.

This commit is contained in:
Andrew Lalis 2017-06-18 10:29:17 +02:00
parent cad267befa
commit c0477a4d17
9 changed files with 589 additions and 0 deletions

48
pom.xml Normal file
View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.agspace.handiebot</groupId>
<artifactId>handiebot</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<repositories>
<repository>
<id>jcenter</id>
<url>http://jcenter.bintray.com</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.austinv11</groupId>
<artifactId>Discord4J</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>com.sedmelluq</groupId>
<artifactId>lavaplayer</artifactId>
<version>1.2.39</version>
</dependency>
</dependencies>
</project>

View File

@ -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<Long, GuildMusicManager> 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();
}
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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<AudioTrack> 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);
}
}

BIN
src/main/resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

121
src/main/resources/icon.svg Normal file
View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256"
height="256"
viewBox="0 0 256 256"
id="svg4136"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="icon.svg"
inkscape:export-filename="C:\Users\AndrewComputer\Documents\Programming\IntelliJ_Projects\handiebot\src\main\resources\icon.png"
inkscape:export-xdpi="360"
inkscape:export-ydpi="360">
<defs
id="defs4138">
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Lstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4701"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#ffffff;stroke-width:1pt;stroke-opacity:1;fill:#ffffff;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#00024b"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="104.72807"
inkscape:cy="108.47214"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata4141">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-796.36219)">
<path
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 26.071429,935.93362 C 83.571429,805.57648 182.26443,803.4602 238.57143,935.57647 226.96157,777.68558 38.855639,775.90407 26.071429,935.93362 Z"
id="path4689"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<ellipse
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.58631611;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path4979"
cx="223.82457"
cy="925.21936"
rx="19.245705"
ry="31.492556" />
<use
x="0"
y="0"
xlink:href="#path4979"
id="use4986"
transform="translate(-181.78571,-4.4666172e-7)"
width="100%"
height="100%" />
<g
id="g4981">
<ellipse
ry="73.582115"
rx="83.534172"
cy="936.95587"
cx="132.5"
id="path4691"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ff6900;stroke-width:5.30872345;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path4695"
d="m 78.571429,919.50505 c 0.357143,-21.78572 30.000001,-21.42857 30.357151,0"
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:7.99999905;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<use
height="100%"
width="100%"
transform="matrix(-1,0,0,1,260.35717,0.35714286)"
id="use4977"
xlink:href="#path4695"
y="0"
x="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB