Skip to content

Instantly share code, notes, and snippets.

@quanticc
Last active June 4, 2018 23:14
Show Gist options
  • Save quanticc/dff8ee374bbbb3b30f28795a651f37b0 to your computer and use it in GitHub Desktop.
Save quanticc/dff8ee374bbbb3b30f28795a651f37b0 to your computer and use it in GitHub Desktop.
Example for YouTube and Discord4J, needs youtube-dl & ffmpeg in your PATH. Credit goes to langerhans#2409
package audio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sx.blah.discord.api.EventSubscriber;
import sx.blah.discord.handle.AudioChannel;
import sx.blah.discord.handle.impl.events.AudioUnqueuedEvent;
import sx.blah.discord.handle.impl.events.MessageReceivedEvent;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.IUser;
import sx.blah.discord.handle.obj.IVoiceChannel;
import sx.blah.discord.util.DiscordException;
import sx.blah.discord.util.MissingPermissionsException;
import sx.blah.discord.util.RequestBuffer;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class YouTube {
private static final Logger log = LoggerFactory.getLogger(YouTube.class);
@EventSubscriber
public void onMessage(MessageReceivedEvent e) {
IMessage message = e.getMessage();
String content = message.getContent();
if (content.startsWith("!q ") || content.startsWith("!queue ")) {
processCommand(() -> queueCommand(e));
} else if (content.equals("!s") || content.equals("!skip")) {
processCommand(() -> skipCommand(e));
} else if (content.startsWith("!v ") || content.startsWith("!volume ")) {
processCommand(() -> volumeCommand(e));
}
}
private void queueCommand(MessageReceivedEvent event) {
IMessage message = event.getMessage();
String content = message.getContent();
IChannel channel = message.getChannel();
if (channel.isPrivate()) {
sendMessage(channel, "This command does not work with private messages");
return;
}
String url = content.trim().split(" ", 2)[1].trim();
if (url.isEmpty()) {
sendMessage(channel, "You have to enter a URL");
return;
}
Optional<IVoiceChannel> voiceChannel = message.getAuthor().getVoiceChannel();
if (voiceChannel.isPresent() && !voiceChannel.get().isConnected()) {
voiceChannel.get().join();
}
try {
AudioChannel audioChannel = message.getGuild().getAudioChannel();
log.debug("Preparing to process URL into queue: {}", url);
if (queueFromYouTube(audioChannel, url)) {
IUser user = message.getAuthor();
sendMessage(channel, user.getName() + "#" + user.getDiscriminator() + " added to the playlist: <" + url + ">");
deleteMessage(message);
}
} catch (DiscordException e) {
log.warn("Could not get audio channel", e);
sendMessage(channel, "Could not get the audio channel for this server");
}
}
private boolean queueFromYouTube(final AudioChannel audioChannel, final String id) {
String name = System.getProperty("os.name").contains("Windows") ? "youtube-dl.exe" : "youtube-dl";
ProcessBuilder builder = new ProcessBuilder(name, "-q", "-f", "worstaudio",
"--exec", "ffmpeg -hide_banner -nostats -loglevel panic -y -i {} -vn -q:a 5 -f mp3 pipe:1", "-o",
"%(id)s", "--", id);
try {
Process process = builder.start();
try {
CompletableFuture.runAsync(() -> logStream(process.getErrorStream()));
audioChannel.queue(AudioSystem.getAudioInputStream(process.getInputStream()));
return true;
} catch (UnsupportedAudioFileException e) {
log.warn("Could not queue audio", e);
process.destroyForcibly();
}
} catch (IOException e) {
log.warn("Could not start process", e);
}
return false;
}
private BufferedReader newProcessReader(InputStream stream) {
return new BufferedReader(new InputStreamReader(stream, Charset.forName("UTF-8")));
}
private void logStream(InputStream stream) {
try (BufferedReader input = newProcessReader(stream)) {
String line;
while ((line = input.readLine()) != null) {
log.info("[yt-dl] " + line);
}
} catch (IOException e) {
log.warn("Could not read from stream", e);
}
}
private void volumeCommand(MessageReceivedEvent event) {
IMessage message = event.getMessage();
String content = message.getContent();
IChannel channel = message.getChannel();
if (message.getChannel().isPrivate()) {
return;
}
try {
int volume = Math.max(0, Math.min(100, parseOrDefault(content.split(" ", 2)[1], 20)));
AudioChannel audioChannel = message.getGuild().getAudioChannel();
log.debug("Setting volume to {}% ({})", volume, volume / 100f);
audioChannel.setVolume(volume / 100f);
sendMessage(channel, ":ok_hand:");
} catch (DiscordException e) {
log.warn("Could not get audio channel", e);
sendMessage(channel, "Could not get audio channel for this server");
}
}
private void skipCommand(MessageReceivedEvent event) {
IMessage message = event.getMessage();
if (!message.getChannel().isPrivate()) {
try {
message.getGuild().getAudioChannel().skip();
} catch (DiscordException e) {
log.warn("Could not get audio channel", e);
}
}
}
@EventSubscriber
public void onAudioDequeue(AudioUnqueuedEvent event) {
AudioInputStream stream = event.getStream();
try {
// Needed to work around an issue present with Discord4J v2.4.x (it's fixed in v2.5)
// that left certain streams open
stream.close();
log.debug("Stream {} was closed", Integer.toHexString(stream.hashCode()));
} catch (IOException e) {
log.warn("Could not close audio stream", e);
}
}
private int parseOrDefault(String input, int defaultValue) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException e) {
log.warn("Attempted to parse non-numeric value: {}", e.toString());
return defaultValue;
}
}
public static RequestBuffer.RequestFuture<IMessage> sendMessage(final IChannel channel, final String content) {
return RequestBuffer.request(() -> {
try {
return channel.sendMessage(content);
} catch (MissingPermissionsException | DiscordException ex) {
log.warn("Could not send message", ex);
}
return null;
});
}
public static void deleteMessage(IMessage message) {
RequestBuffer.request(() -> {
try {
message.delete();
} catch (MissingPermissionsException | DiscordException e) {
log.warn("Failed to delete message", e);
}
return null;
});
}
public static CompletableFuture<Void> processCommand(Runnable runnable) {
return CompletableFuture.runAsync(runnable)
.exceptionally(t -> {
log.warn("Could not complete command", t);
return null;
});
}
}
@MindFlare56
Copy link

MindFlare56 commented Jun 4, 2018

Class AudioChannel is missing and AudioUnquedEvent too

AudioChannel has been replaced with AudioPlayer
ex: AudioPlayer audioPlayer =AudioPlayer.getAudioPlayerForGuild(iGuild);

Voice channel code also changed I use this instead:
    public static IVoiceChannel getVoiceChannel() {
        for (IVoiceChannel iVoiceChannel : iGuild.getVoiceChannels()) {
            for (IUser iUser : iVoiceChannel.getConnectedUsers()) {
                if (iUser.getStringID().equalsIgnoreCase(author.getStringID())) {
                    return iVoiceChannel;
                }
            }
        }
    }

also you can changed this line to make it easier

ProcessBuilder builder = new ProcessBuilder("bot/path-to-where-you-want/youtube-dl/youtube-dl.exe", "-q", "-f", "worstaudio",
                    "--exec", "C:\\FFmpeg\\bin\\ffmpeg -hide_banner -nostats -loglevel panic -y -i {} -vn -q:a 5 -f mp3 pipe:1", "-o",
                    "%(id)s", "--", id);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment