diff --git a/src/main/java/moe/nekojimi/chords/CommandOptions.java b/src/main/java/moe/nekojimi/chords/CommandOptions.java
new file mode 100644
index 0000000..27c1848
--- /dev/null
+++ b/src/main/java/moe/nekojimi/chords/CommandOptions.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 jimj316
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package moe.nekojimi.chords;
+
+import com.beust.jcommander.Parameter;
+
+/**
+ *
+ * @author jimj316
+ */
+public class CommandOptions
+{
+
+ @Parameter(description = "The API token for Discord.", required = true)
+ private String token;
+
+ @Parameter(names = "-local-addr", description = "The local address to bind to.")
+ private String localAddress;
+
+ public String getToken()
+ {
+ return token;
+ }
+
+ public String getLocalAddress()
+ {
+ return localAddress;
+ }
+
+}
diff --git a/src/main/java/moe/nekojimi/chords/LocalBindSocketFactory.java b/src/main/java/moe/nekojimi/chords/LocalBindSocketFactory.java
new file mode 100644
index 0000000..09e1d06
--- /dev/null
+++ b/src/main/java/moe/nekojimi/chords/LocalBindSocketFactory.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 jimj316
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package moe.nekojimi.chords;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import javax.net.SocketFactory;
+
+/**
+ *
+ * @author jimj316
+ */
+public class LocalBindSocketFactory extends SocketFactory
+{
+
+ private InetAddress localAddress;
+
+ public InetAddress getLocalAddress()
+ {
+ return localAddress;
+ }
+
+ public void setLocalAddress(InetAddress localAddress)
+ {
+ this.localAddress = localAddress;
+ }
+
+ @Override
+ public Socket createSocket(String remoteAddr, int remotePort) throws IOException, UnknownHostException
+ {
+ return createSocket(remoteAddr, remotePort, localAddress, findFreePort(localAddress));
+ }
+
+ @Override
+ public Socket createSocket(String remoteAddr, int remotePort, InetAddress x, int localPort) throws IOException, UnknownHostException
+ {
+ return createSocket(InetAddress.getByName(remoteAddr), remotePort, localAddress, localPort);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress remoteAddr, int remotePort) throws IOException
+ {
+ return createSocket(remoteAddr, remotePort, localAddress, findFreePort(localAddress));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress remoteAddr, int remotePort, InetAddress x, int localPort) throws IOException
+ {
+ return new Socket(remoteAddr, remotePort, localAddress, localPort);
+ }
+
+ private static int findFreePort(InetAddress localAddr)
+ {
+ int port = 0;
+ // For ServerSocket port number 0 means that the port number is automatically allocated.
+ try (ServerSocket socket = new ServerSocket(0, 0, localAddr))
+ {
+ // Disable timeout and reuse address after closing the socket.
+ socket.setReuseAddress(true);
+ port = socket.getLocalPort();
+ } catch (IOException ignored)
+ {
+ }
+ if (port > 0)
+ {
+ return port;
+ }
+ throw new RuntimeException("Could not find a free port");
+ }
+
+}
diff --git a/src/main/java/moe/nekojimi/chords/Main.java b/src/main/java/moe/nekojimi/chords/Main.java
index c4aa11b..5bc3107 100644
--- a/src/main/java/moe/nekojimi/chords/Main.java
+++ b/src/main/java/moe/nekojimi/chords/Main.java
@@ -7,11 +7,13 @@ package moe.nekojimi.chords;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.neovisionaries.ws.client.WebSocketFactory;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.*;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -20,6 +22,8 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
import javax.security.auth.login.LoginException;
import moe.nekojimi.chords.commands.*;
import moe.nekojimi.musicsearcher.providers.MetaSearcher;
@@ -42,6 +46,8 @@ import net.dv8tion.jda.api.utils.cache.CacheFlag;
public final class Main extends ListenerAdapter
{
+ private static final CommandOptions options = new CommandOptions();
+
private final File dataDirectory;
private final File playlistsDirectory;
@@ -66,6 +72,13 @@ public final class Main extends ListenerAdapter
*/
public static void main(String[] args) throws LoginException, IOException
{
+
+ JCommander commander = JCommander.newBuilder()
+ .args(args)
+ .acceptUnknownOptions(true)
+ .addObject(options)
+ .build();
+
// We only need 2 gateway intents enabled for this example:
EnumSet intents = EnumSet.of(
// We need messages in guilds to accept commands from users
@@ -91,49 +104,21 @@ public final class Main extends ListenerAdapter
builder.addEventListeners(listener);
builder.setAutoReconnect(true);
+ if (options.getLocalAddress() != null)
+ {
+ final WebSocketFactory webSocketFactory = new WebSocketFactory();
+ final LocalBindSocketFactory localBindSocketFactory = new LocalBindSocketFactory();
+ localBindSocketFactory.setLocalAddress(InetAddress.getByName(options.getLocalAddress()));
+ webSocketFactory.setSocketFactory(localBindSocketFactory);
+ builder.setWebsocketFactory(webSocketFactory);
+ }
+
JDA jda = builder.build();
listener.setJda(jda);
}
- private final Consumer nowPlayingConsumer = (Song song) ->
- {
- if (song != null)
- jda.getPresence().setActivity(Activity.of(Activity.ActivityType.LISTENING, song.toString()));
- else
- jda.getPresence().setActivity(null);
- };
+ private final Consumer nowPlayingConsumer = new NowPlayingConsumer();
- private final BiConsumer downloaderMessageHandler = (SongRequest request, Exception ex) ->
- {
- Song song = request.getSong();
-// TextChannel channel = jda.getTextChannelById(song.getRequestedIn());
-// String bracketNo = "[" + song.getNumber() + "] ";
- if (ex == null)
- if (song.getLocation() != null)
- {
- request.getInvocation().respond("Finished downloading " + song + ", added to queue!");
- log("DOWN", "Downloaded " + song);
- } else
- {
- Format format = song.getBestFormat();
- String formatDetails = "";
- if (format != null)
- {
- final int bitrate = format.getSampleRate() / 1000;
- final long size = format.getSize();
- String sizeFmt = (size <= 0 ? "?.??" : String.format("%.2f", size / (1024.0 * 1024.0))) + "MiB";
- String bitFmt = (bitrate <= 0 ? "??" : bitrate) + "k";
- formatDetails = " (" + bitFmt + ", " + sizeFmt + ")";
- }
- request.getInvocation().respond("Now downloading " + song + formatDetails + " ...");
- log("DOWN", "Downloading " + song + "...");
- }
- else
- {
- request.getInvocation().respond("Failed to download " + song + "! Reason: " + ex.getMessage());
- log("DOWN", "Failed to download " + song + "! Reason: " + ex.getMessage());
- }
-
- };
+ private final BiConsumer downloaderMessageHandler = new DownloaderMessageHandler();
public Main() throws IOException
{
@@ -171,7 +156,6 @@ public final class Main extends ListenerAdapter
log("INFO", "Started OK!");
}
-
private void addCommand(Command command)
{
commands.put(command.getKeyword(), command);
@@ -407,6 +391,7 @@ public final class Main extends ListenerAdapter
{
return currentVoiceChannel;
}
+
public QueueManager getQueueManager()
{
return queueManager;
@@ -417,5 +402,62 @@ public final class Main extends ListenerAdapter
return trackNumber;
}
+ private class DownloaderMessageHandler implements BiConsumer
+ {
+
+ public DownloaderMessageHandler()
+ {
+ }
+
+ @Override
+ public void accept(SongRequest request, Exception ex)
+ {
+ Song song = request.getSong();
+// TextChannel channel = jda.getTextChannelById(song.getRequestedIn());
+// String bracketNo = "[" + song.getNumber() + "] ";
+ if (ex == null)
+ if (song.getLocation() != null)
+ {
+ request.getInvocation().respond("Finished downloading " + song + ", added to queue!");
+ log("DOWN", "Downloaded " + song);
+ } else
+ {
+ Format format = song.getBestFormat();
+ String formatDetails = "";
+ if (format != null)
+ {
+ final int bitrate = format.getSampleRate() / 1000;
+ final long size = format.getSize();
+ String sizeFmt = (size <= 0 ? "?.??" : String.format("%.2f", size / (1024.0 * 1024.0))) + "MiB";
+ String bitFmt = (bitrate <= 0 ? "??" : bitrate) + "k";
+ formatDetails = " (" + bitFmt + ", " + sizeFmt + ")";
+ }
+ request.getInvocation().respond("Now downloading " + song + formatDetails + " ...");
+ log("DOWN", "Downloading " + song + "...");
+ }
+ else
+ {
+ request.getInvocation().respond("Failed to download " + song + "! Reason: " + ex.getMessage());
+ log("DOWN", "Failed to download " + song + "! Reason: " + ex.getMessage());
+ }
+ }
+ }
+
+ private class NowPlayingConsumer implements Consumer
+ {
+
+ public NowPlayingConsumer()
+ {
+ }
+
+ @Override
+ public void accept(Song song)
+ {
+ if (song != null)
+ jda.getPresence().setActivity(Activity.of(Activity.ActivityType.LISTENING, song.toString()));
+ else
+ jda.getPresence().setActivity(null);
+ }
+ }
}