commit
9b93ed88a9
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,46 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<actions> |
||||
<action> |
||||
<actionName>run</actionName> |
||||
<packagings> |
||||
<packaging>jar</packaging> |
||||
</packagings> |
||||
<goals> |
||||
<goal>process-classes</goal> |
||||
<goal>org.codehaus.mojo:exec-maven-plugin:1.5.0:exec</goal> |
||||
</goals> |
||||
<properties> |
||||
<exec.args>-classpath %classpath moe.nekojimi.chords.Main ODkwNjU5MjI2NDE2OTg0MDY0.YUzBCw.jHZWpIZSYeaYA7Sc7h93W_jV-rk</exec.args> |
||||
<exec.executable>java</exec.executable> |
||||
</properties> |
||||
</action> |
||||
<action> |
||||
<actionName>debug</actionName> |
||||
<packagings> |
||||
<packaging>jar</packaging> |
||||
</packagings> |
||||
<goals> |
||||
<goal>process-classes</goal> |
||||
<goal>org.codehaus.mojo:exec-maven-plugin:1.5.0:exec</goal> |
||||
</goals> |
||||
<properties> |
||||
<exec.args>-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath moe.nekojimi.chords.Main ODkwNjU5MjI2NDE2OTg0MDY0.YUzBCw.jHZWpIZSYeaYA7Sc7h93W_jV-rk</exec.args> |
||||
<exec.executable>java</exec.executable> |
||||
<jpda.listen>true</jpda.listen> |
||||
</properties> |
||||
</action> |
||||
<action> |
||||
<actionName>profile</actionName> |
||||
<packagings> |
||||
<packaging>jar</packaging> |
||||
</packagings> |
||||
<goals> |
||||
<goal>process-classes</goal> |
||||
<goal>org.codehaus.mojo:exec-maven-plugin:1.5.0:exec</goal> |
||||
</goals> |
||||
<properties> |
||||
<exec.args>-classpath %classpath ${packageClassName} ODkwNjU5MjI2NDE2OTg0MDY0.YUzBCw.jHZWpIZSYeaYA7Sc7h93W_jV-rk</exec.args> |
||||
<exec.executable>java</exec.executable> |
||||
</properties> |
||||
</action> |
||||
</actions> |
@ -0,0 +1,27 @@ |
||||
<?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>moe.nekojimi</groupId> |
||||
<artifactId>Chords</artifactId> |
||||
<version>1.0</version> |
||||
<packaging>jar</packaging> |
||||
<repositories> |
||||
<repository> |
||||
<id>dv8tion</id> |
||||
<name>m2-dv8tion</name> |
||||
<url>https://m2.dv8tion.net/releases</url> |
||||
</repository> |
||||
</repositories> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>net.dv8tion</groupId> |
||||
<artifactId>JDA</artifactId> |
||||
<version>4.3.0_277</version> |
||||
</dependency> |
||||
</dependencies> |
||||
<properties> |
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
<maven.compiler.source>11</maven.compiler.source> |
||||
<maven.compiler.target>11</maven.compiler.target> |
||||
</properties> |
||||
</project> |
@ -0,0 +1,382 @@ |
||||
/* |
||||
* To change this license header, choose License Headers in Project Properties. |
||||
* To change this template file, choose Tools | Templates |
||||
* and open the template in the editor. |
||||
*/ |
||||
package moe.nekojimi.chords; |
||||
|
||||
import java.io.Closeable; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.nio.ByteBuffer; |
||||
import java.nio.charset.Charset; |
||||
import java.util.*; |
||||
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.logging.Level; |
||||
import java.util.logging.Logger; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
import javax.security.auth.login.LoginException; |
||||
import javax.sound.sampled.*; |
||||
import net.dv8tion.jda.api.JDA; |
||||
import net.dv8tion.jda.api.JDABuilder; |
||||
import net.dv8tion.jda.api.audio.AudioReceiveHandler; |
||||
import net.dv8tion.jda.api.audio.AudioSendHandler; |
||||
import net.dv8tion.jda.api.audio.CombinedAudio; |
||||
import net.dv8tion.jda.api.entities.*; |
||||
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; |
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter; |
||||
import net.dv8tion.jda.api.managers.AudioManager; |
||||
import net.dv8tion.jda.api.requests.GatewayIntent; |
||||
import net.dv8tion.jda.api.utils.Compression; |
||||
import net.dv8tion.jda.api.utils.cache.CacheFlag; |
||||
|
||||
/** |
||||
* |
||||
* @author jimj316 |
||||
*/ |
||||
public class Main extends ListenerAdapter |
||||
{ |
||||
|
||||
/** |
||||
* @param args the command line arguments |
||||
*/ |
||||
public static void main(String[] args) throws LoginException |
||||
{ |
||||
// We only need 2 gateway intents enabled for this example:
|
||||
EnumSet<GatewayIntent> intents = EnumSet.of( |
||||
// We need messages in guilds to accept commands from users
|
||||
GatewayIntent.GUILD_MESSAGES, |
||||
// We need voice states to connect to the voice channel
|
||||
GatewayIntent.GUILD_VOICE_STATES |
||||
); |
||||
|
||||
JDABuilder builder = JDABuilder.createDefault(args[0], intents); |
||||
|
||||
// Disable parts of the cache
|
||||
builder.disableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE); |
||||
// Enable the bulk delete event
|
||||
builder.setBulkDeleteSplittingEnabled(false); |
||||
// Disable compression (not recommended)
|
||||
builder.setCompression(Compression.NONE); |
||||
// Set activity (like "playing Something")
|
||||
builder.setActivity(Activity.playing("music!")); |
||||
|
||||
builder.addEventListeners(new Main()); |
||||
|
||||
JDA jda = builder.build(); |
||||
} |
||||
|
||||
@Override |
||||
public void onGuildMessageReceived(GuildMessageReceivedEvent event) |
||||
{ |
||||
Message message = event.getMessage(); |
||||
User author = message.getAuthor(); |
||||
String content = message.getContentRaw(); |
||||
Guild guild = event.getGuild(); |
||||
|
||||
// Ignore message if bot
|
||||
if (author.isBot()) |
||||
return; |
||||
|
||||
if (content.startsWith("!join ")) |
||||
{ |
||||
String arg = content.substring("!join ".length()); |
||||
onJoinCommand(event, guild, arg); |
||||
} |
||||
else if (content.equals("!join")) |
||||
{ |
||||
onJoinCommand(event); |
||||
} |
||||
else if (content.startsWith("!play ")) |
||||
{ |
||||
String arg = content.substring("!join ".length()); |
||||
onPlayCommand(event, guild, arg); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Handle command without arguments. |
||||
* |
||||
* @param event |
||||
* The event for this command |
||||
*/ |
||||
private void onJoinCommand(GuildMessageReceivedEvent event) |
||||
{ |
||||
// Note: None of these can be null due to our configuration with the JDABuilder!
|
||||
Member member = event.getMember(); // Member is the context of the user for the specific guild, containing voice state and roles
|
||||
GuildVoiceState voiceState = member.getVoiceState(); // Check the current voice state of the user
|
||||
VoiceChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to
|
||||
if (channel != null) |
||||
{ |
||||
connectTo(channel); // Join the channel of the user
|
||||
onConnecting(channel, event.getChannel()); // Tell the user about our success
|
||||
} |
||||
else |
||||
{ |
||||
onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure
|
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Handle command with arguments. |
||||
* |
||||
* @param event |
||||
* The event for this command |
||||
* @param guild |
||||
* The guild where its happening |
||||
* @param arg |
||||
* The input argument |
||||
*/ |
||||
private void onJoinCommand(GuildMessageReceivedEvent event, Guild guild, String arg) |
||||
{ |
||||
boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits
|
||||
VoiceChannel channel = null; |
||||
if (isNumber) // The input is an id?
|
||||
{ |
||||
channel = guild.getVoiceChannelById(arg); |
||||
} |
||||
if (channel == null) // Then the input must be a name?
|
||||
{ |
||||
List<VoiceChannel> channels = guild.getVoiceChannelsByName(arg, true); |
||||
if (!channels.isEmpty()) // Make sure we found at least one exact match
|
||||
channel = channels.get(0); // We found a channel! This cannot be null.
|
||||
} |
||||
|
||||
TextChannel textChannel = event.getChannel(); |
||||
if (channel == null) // I have no idea what you want mr user
|
||||
{ |
||||
onUnknownChannel(textChannel, arg); // Let the user know about our failure
|
||||
return; |
||||
} |
||||
connectTo(channel); // We found a channel to connect to!
|
||||
onConnecting(channel, textChannel); // Let the user know, we were successful!
|
||||
} |
||||
|
||||
private void onPlayCommand(GuildMessageReceivedEvent event, Guild guild, String arg) |
||||
{ |
||||
try |
||||
{ |
||||
event.getChannel().sendMessage("Downloading ...").queue(); |
||||
String destination = downloadSong(arg); |
||||
musicHandler.addSong(new File(destination)); |
||||
event.getChannel().sendMessage("Downloaded and added to queue!").queue(); |
||||
|
||||
} catch (IOException | InterruptedException | RuntimeException ex) |
||||
{ |
||||
event.getChannel().sendMessage("Failed to download! Reason: " + ex.getMessage()).queue(); |
||||
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); |
||||
} |
||||
} |
||||
|
||||
private String downloadSong(String arg) throws IOException, RuntimeException, InterruptedException |
||||
{ |
||||
String cmd = "/usr/bin/youtube-dl -x --audio-format=wav "+arg; |
||||
System.out.println("Running command: " + cmd); |
||||
// Process exec = Runtime.getRuntime().exec().split(" "));
|
||||
Process exec = new ProcessBuilder(cmd.split(" ")).redirectOutput(ProcessBuilder.Redirect.PIPE).start(); |
||||
exec.waitFor(10000, TimeUnit.MILLISECONDS); |
||||
InputStream in = exec.getInputStream(); |
||||
String output = new String(in.readAllBytes(), Charset.defaultCharset()); |
||||
System.out.println(output); |
||||
if (exec.exitValue() != 0) |
||||
throw new RuntimeException("youtube-dl failed with error " + exec.exitValue()); |
||||
Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output); |
||||
matcher.find(); |
||||
String destination = matcher.group(1); |
||||
return destination; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Inform user about successful connection. |
||||
* |
||||
* @param channel |
||||
* The voice channel we connected to |
||||
* @param textChannel |
||||
* The text channel to send the message in |
||||
*/ |
||||
private void onConnecting(VoiceChannel channel, TextChannel textChannel) |
||||
{ |
||||
textChannel.sendMessage("Connecting to " + channel.getName()).queue(); // never forget to queue()!
|
||||
} |
||||
|
||||
/** |
||||
* The channel to connect to is not known to us. |
||||
* |
||||
* @param channel |
||||
* The message channel (text channel abstraction) to send failure information to |
||||
* @param comment |
||||
* The information of this channel |
||||
*/ |
||||
private void onUnknownChannel(MessageChannel channel, String comment) |
||||
{ |
||||
channel.sendMessage("Unable to connect to ``" + comment + "``, no such channel!").queue(); // never forget to queue()!
|
||||
} |
||||
|
||||
/** |
||||
* Connect to requested channel and start echo handler |
||||
* |
||||
* @param channel |
||||
* The channel to connect to |
||||
*/ |
||||
private void connectTo(VoiceChannel channel) |
||||
{ |
||||
Guild guild = channel.getGuild(); |
||||
// Get an audio manager for this guild, this will be created upon first use for each guild
|
||||
AudioManager audioManager = guild.getAudioManager(); |
||||
musicHandler = new MusicHandler(); |
||||
// Create our Send/Receive handler for the audio connection
|
||||
// EchoHandler handler = new EchoHandler();
|
||||
|
||||
// The order of the following instructions does not matter!
|
||||
|
||||
// Set the sending handler to our echo system
|
||||
audioManager.setSendingHandler(musicHandler); |
||||
// Set the receiving handler to the same echo system, otherwise we can't echo anything
|
||||
// audioManager.setReceivingHandler(handler);
|
||||
// Connect to the voice channel
|
||||
audioManager.openAudioConnection(channel); |
||||
} |
||||
private MusicHandler musicHandler; |
||||
|
||||
public static class MusicHandler implements AudioSendHandler, Closeable |
||||
{ |
||||
private final Queue<File> songQueue = new ConcurrentLinkedQueue<>(); |
||||
private File currentSong; |
||||
|
||||
private AudioInputStream din = null; |
||||
private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>(); |
||||
private int byteCount; |
||||
|
||||
public MusicHandler() |
||||
{ |
||||
// load whatever songs we have in WAV format
|
||||
// File folder = new File("/home/jimj316/Music/");
|
||||
// List<File> wavs = Arrays.asList(folder.listFiles((file, string) ->
|
||||
// {
|
||||
// return string.endsWith(".wav");
|
||||
// }));
|
||||
// Collections.shuffle(wavs);
|
||||
// songQueue.addAll(wavs);
|
||||
|
||||
// nextSong();
|
||||
} |
||||
|
||||
public void addSong(File file) |
||||
{ |
||||
System.out.println("Song added to queue: " + file.getAbsolutePath()); |
||||
|
||||
songQueue.add(file); |
||||
|
||||
if (!canProvide()) |
||||
nextSong(); |
||||
} |
||||
|
||||
private boolean nextSong() |
||||
{ |
||||
AudioInputStream in = null; |
||||
try |
||||
{ |
||||
if (din != null) |
||||
{ |
||||
din.close(); |
||||
din = null; |
||||
} |
||||
|
||||
if (currentSong != null) |
||||
{ |
||||
currentSong.delete(); |
||||
currentSong = null; |
||||
} |
||||
|
||||
currentSong = songQueue.poll(); |
||||
if (currentSong == null) |
||||
return false; |
||||
System.out.println("Playing song " + currentSong.getAbsolutePath()); |
||||
in = AudioSystem.getAudioInputStream(currentSong); |
||||
AudioFormat decodedFormat = AudioSendHandler.INPUT_FORMAT; |
||||
din = AudioSystem.getAudioInputStream(decodedFormat, in); |
||||
byteCount = 3840; |
||||
while (queue.size() < 500) |
||||
{ |
||||
if (!readData()) |
||||
break; |
||||
} |
||||
System.out.println("Queue filled to " + queue.size()); |
||||
|
||||
return true; |
||||
} catch (UnsupportedAudioFileException | IOException ex) |
||||
{ |
||||
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); |
||||
} finally |
||||
{ |
||||
|
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean canProvide() |
||||
{ |
||||
// If we have something in our buffer we can provide it to the send system
|
||||
return !queue.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public ByteBuffer provide20MsAudio() |
||||
{ |
||||
// use what we have in our buffer to send audio as PCM
|
||||
while (queue.size() < 500) |
||||
{ |
||||
if (!readData()) |
||||
{ |
||||
if (!nextSong()) |
||||
break; |
||||
} |
||||
} |
||||
byte[] data = queue.poll(); |
||||
return data == null ? null : ByteBuffer.wrap(data); // Wrap this in a java.nio.ByteBuffer
|
||||
} |
||||
|
||||
private boolean readData() |
||||
{ |
||||
if (din == null) |
||||
return false; |
||||
try |
||||
{ |
||||
// if (din.available() == 0)
|
||||
// return false;
|
||||
int bytesToRead = byteCount; |
||||
if (din.available() > 0 && din.available() < bytesToRead) |
||||
bytesToRead = din.available(); |
||||
byte[] bytes = new byte[bytesToRead]; |
||||
// byte[] bytes = din.readNBytes(bytesToRead);
|
||||
int read = din.read(bytes); |
||||
if (read < 0) |
||||
return false; |
||||
queue.add(bytes); |
||||
} catch (IOException ex) |
||||
{ |
||||
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isOpus() |
||||
{ |
||||
return false; //To change body of generated methods, choose Tools | Templates.
|
||||
} |
||||
|
||||
@Override |
||||
public void close() throws IOException |
||||
{ |
||||
din.close(); |
||||
} |
||||
|
||||
} |
||||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,5 @@ |
||||
#Generated by Maven |
||||
#Thu Sep 23 22:35:54 BST 2021 |
||||
groupId=moe.nekojimi |
||||
artifactId=Chords |
||||
version=1.0 |
@ -0,0 +1,4 @@ |
||||
moe/nekojimi/chords/Main$MusicHandler.class |
||||
moe/nekojimi/chords/Main.class |
||||
moe/nekojimi/chords/Main$EchoHandler.class |
||||
.netbeans_automatic_build |
@ -0,0 +1 @@ |
||||
/home/jimj316/ownCloud/Programming/Chords/src/main/java/moe/nekojimi/chords/Main.java |
Loading…
Reference in new issue