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