commit 9b93ed88a95afee1fb78c2a1bdf5c32429cd6b0e Author: Jim Date: Fri Sep 24 10:30:37 2021 +0100 First commit diff --git a/.syncthing.Cave Story OST - T03 - Mimiga Town-BdUZkTLE06A.wav.tmp b/.syncthing.Cave Story OST - T03 - Mimiga Town-BdUZkTLE06A.wav.tmp new file mode 100644 index 0000000..304c8b1 Binary files /dev/null and b/.syncthing.Cave Story OST - T03 - Mimiga Town-BdUZkTLE06A.wav.tmp differ diff --git a/.syncthing.Cave Story OST - T06 - Gravity (Boss Theme #1)-594aUuWMiOE.wav.tmp b/.syncthing.Cave Story OST - T06 - Gravity (Boss Theme #1)-594aUuWMiOE.wav.tmp new file mode 100644 index 0000000..a276307 Binary files /dev/null and b/.syncthing.Cave Story OST - T06 - Gravity (Boss Theme #1)-594aUuWMiOE.wav.tmp differ diff --git a/.syncthing.Cave Story OST - T09 - Mischievious Robot (Egg Corridor)-cy9ejJiSH2Y.wav.tmp b/.syncthing.Cave Story OST - T09 - Mischievious Robot (Egg Corridor)-cy9ejJiSH2Y.wav.tmp new file mode 100644 index 0000000..124edd2 Binary files /dev/null and b/.syncthing.Cave Story OST - T09 - Mischievious Robot (Egg Corridor)-cy9ejJiSH2Y.wav.tmp differ diff --git a/.syncthing.Cave Story OST - T10 - Pulse-mK4v0XkNrVE.wav.tmp b/.syncthing.Cave Story OST - T10 - Pulse-mK4v0XkNrVE.wav.tmp new file mode 100644 index 0000000..599ceab Binary files /dev/null and b/.syncthing.Cave Story OST - T10 - Pulse-mK4v0XkNrVE.wav.tmp differ diff --git a/nbactions.xml b/nbactions.xml new file mode 100644 index 0000000..3517b23 --- /dev/null +++ b/nbactions.xml @@ -0,0 +1,46 @@ + + + + run + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.5.0:exec + + + -classpath %classpath moe.nekojimi.chords.Main ODkwNjU5MjI2NDE2OTg0MDY0.YUzBCw.jHZWpIZSYeaYA7Sc7h93W_jV-rk + java + + + + debug + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.5.0:exec + + + -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath moe.nekojimi.chords.Main ODkwNjU5MjI2NDE2OTg0MDY0.YUzBCw.jHZWpIZSYeaYA7Sc7h93W_jV-rk + java + true + + + + profile + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.5.0:exec + + + -classpath %classpath ${packageClassName} ODkwNjU5MjI2NDE2OTg0MDY0.YUzBCw.jHZWpIZSYeaYA7Sc7h93W_jV-rk + java + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2323ef7 --- /dev/null +++ b/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + moe.nekojimi + Chords + 1.0 + jar + + + dv8tion + m2-dv8tion + https://m2.dv8tion.net/releases + + + + + net.dv8tion + JDA + 4.3.0_277 + + + + UTF-8 + 11 + 11 + + \ No newline at end of file diff --git a/src/main/java/moe/nekojimi/chords/Main.java b/src/main/java/moe/nekojimi/chords/Main.java new file mode 100644 index 0000000..973b71a --- /dev/null +++ b/src/main/java/moe/nekojimi/chords/Main.java @@ -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 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 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 songQueue = new ConcurrentLinkedQueue<>(); + private File currentSong; + + private AudioInputStream din = null; + private final Queue queue = new ConcurrentLinkedQueue<>(); + private int byteCount; + + public MusicHandler() + { + // load whatever songs we have in WAV format +// File folder = new File("/home/jimj316/Music/"); +// List 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(); + } + + } +} diff --git a/target/Chords-1.0.jar b/target/Chords-1.0.jar new file mode 100644 index 0000000..afe78c7 Binary files /dev/null and b/target/Chords-1.0.jar differ diff --git a/target/classes/.netbeans_automatic_build b/target/classes/.netbeans_automatic_build new file mode 100644 index 0000000..e69de29 diff --git a/target/classes/moe/nekojimi/chords/Main$EchoHandler.class b/target/classes/moe/nekojimi/chords/Main$EchoHandler.class new file mode 100644 index 0000000..bc0bf05 Binary files /dev/null and b/target/classes/moe/nekojimi/chords/Main$EchoHandler.class differ diff --git a/target/classes/moe/nekojimi/chords/Main$MusicHandler.class b/target/classes/moe/nekojimi/chords/Main$MusicHandler.class new file mode 100644 index 0000000..5d1cace Binary files /dev/null and b/target/classes/moe/nekojimi/chords/Main$MusicHandler.class differ diff --git a/target/classes/moe/nekojimi/chords/Main.class b/target/classes/moe/nekojimi/chords/Main.class new file mode 100644 index 0000000..df917c4 Binary files /dev/null and b/target/classes/moe/nekojimi/chords/Main.class differ diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..e7a6acf --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Thu Sep 23 22:35:54 BST 2021 +groupId=moe.nekojimi +artifactId=Chords +version=1.0 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..de1a456 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,4 @@ +moe/nekojimi/chords/Main$MusicHandler.class +moe/nekojimi/chords/Main.class +moe/nekojimi/chords/Main$EchoHandler.class +.netbeans_automatic_build diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..b7adbdb --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1 @@ +/home/jimj316/ownCloud/Programming/Chords/src/main/java/moe/nekojimi/chords/Main.java diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/target/test-classes/.netbeans_automatic_build b/target/test-classes/.netbeans_automatic_build new file mode 100644 index 0000000..e69de29