From 4ab52d8bf51fc4d5ab3b0c4a0c0580c34dfc2612 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sat, 13 Jan 2018 21:23:23 +0100 Subject: [PATCH 01/50] Optimize the number of audio send factories --- .../lavalink/server/io/SocketContext.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java index b215e05fb..fdbbb5c52 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java @@ -55,6 +55,8 @@ public class SocketContext { private final Map players = new ConcurrentHashMap<>(); private ScheduledExecutorService statsExecutor; public final ScheduledExecutorService playerUpdateService; + private static final int audioSendFactoryCount = Runtime.getRuntime().availableProcessors() * 2; + private final ConcurrentHashMap sendFactories = new ConcurrentHashMap<>(); SocketContext(WebSocket socket, String userId, int shardCount) { this.socket = socket; @@ -76,7 +78,7 @@ Core getCore(int shardId) { return cores.computeIfAbsent(shardId, __ -> { if (nasSupported) - return new Core(userId, new CoreClientImpl(socket, shardId), createAudioSendFactory()); + return new Core(userId, new CoreClientImpl(socket, shardId), getAudioSendFactory(shardId)); else return new Core(userId, new CoreClientImpl(socket, shardId)); } @@ -126,16 +128,18 @@ void shutdown() { players.values().forEach(Player::stop); } - private IAudioSendFactory createAudioSendFactory() { - Integer customBuffer = Launcher.config.getBufferDurationMs(); - NativeAudioSendFactory nativeAudioSendFactory; - if (customBuffer != null) { - nativeAudioSendFactory = new NativeAudioSendFactory(customBuffer); - } else { - nativeAudioSendFactory = new NativeAudioSendFactory(); - } + private IAudioSendFactory getAudioSendFactory(int shardId) { + return sendFactories.computeIfAbsent(shardId % audioSendFactoryCount, integer -> { + Integer customBuffer = Launcher.config.getBufferDurationMs(); + NativeAudioSendFactory nativeAudioSendFactory; + if (customBuffer != null) { + nativeAudioSendFactory = new NativeAudioSendFactory(customBuffer); + } else { + nativeAudioSendFactory = new NativeAudioSendFactory(); + } - return AsyncPacketProviderFactory.adapt(nativeAudioSendFactory); + return AsyncPacketProviderFactory.adapt(nativeAudioSendFactory); + }); } } From c84636c59e458c5b2a3231bf92de076eaef3053f Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Sun, 14 Jan 2018 00:33:08 +0100 Subject: [PATCH 02/50] Log guildId when receiving voice server update on client --- .../java/lavalink/client/io/VoiceServerUpdateInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java index bdff98722..7a4793637 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java @@ -62,7 +62,7 @@ protected Long handleInternally(JSONObject content) { json.put("event", content); lavalink.getLink(guild).getNode(true).send(json.toString()); - log.info("Sent voice update"); + log.info("Sent voice update for guild {}", idLong); return null; } From 5b9b27fbf0cd64a078399306822751140e471a32 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sun, 14 Jan 2018 16:14:47 +0100 Subject: [PATCH 03/50] V2.0 draft implementation spec --- IMPLEMENTATION.md | 63 ++++------------------------------------------- 1 file changed, 5 insertions(+), 58 deletions(-) diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 5d0d3c50e..63f739dd8 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -21,16 +21,7 @@ User-Id: The user id of the bot you are playing music with ``` ### Outgoing messages -Make the server queue a voice connection -```json -{ - "op": "connect", - "guildId": "...", - "channelId": "..." -} -``` - -Provide an intercepted voice server update +Provide an intercepted voice server update. This causes the server to connect to the voice channel ```json { "op": "voiceUpdate", @@ -40,33 +31,6 @@ Provide an intercepted voice server update } ``` -Close a voice connection -```json -{ - "op": "disconnect", - "guildId": "123" -} -``` - -Response to `validationReq`. `channelId` is omitted if the request does not display the channel id. -```json -{ - "op": "validationRes", - "guildId": "...", - "channelId": "...", - "valid": true -} -``` - -Response to `isConnectedRes`. -```json -{ - "op": "isConnectedRes", - "shardId": 1337, - "connected": true -} -``` - Cause the player to play a track. `startTime` is an optional setting that determines the number of milliseconds to offset the track by. Defaults to 0. `endTime` is an optional setting that determines at the number of milliseconds at which point the track should stop playing. Helpful if you only want to play a snippet of a bigger track. By default the track plays until it's end as per the encoded data. @@ -117,32 +81,15 @@ Set player volume. Volume may range from 0 to 150. 100 is default. ### Incoming messages See -[LavalinkSocket.java](https://github.com/Frederikam/Lavalink/blob/91bc0ef4dab6ca5d5efcba12203ee4054bb55ae9/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java) +[LavalinkSocket.java](https://github.com/Frederikam/Lavalink/blob/dev/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java) for client implementation -Incoming message to forward to mainWS -```json -{ - "op": "sendWS", - "shardId": 1337, - "message": "..." -} -``` - -Request to check if the VC or Guild exists, and that we have access to the VC. Note that the channelId may be omitted, in which case you should only check if we are in the guild. +Received when the voice connection is disconnected (and unable to resume). +The client must queue a new voice connection if it desires to reconnect. ```json { - "op": "validationReq", + "op": "disconnected", "guildId": "...", - "channelId": "..." -} -``` - -Request to check if a shard's mainWS is connected -```json -{ - "op": "isConnectedReq", - "shardId": 1337 } ``` From 35e488e2ca0e7278f94271c28a25ad1378f89aae Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sun, 14 Jan 2018 19:38:49 +0100 Subject: [PATCH 04/50] Turned Link into a stub --- .../java/lavalink/client/io/Lavalink.java | 8 +- .../main/java/lavalink/client/io/Link.java | 211 +++--------------- 2 files changed, 36 insertions(+), 183 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index 553849d0c..a95ad13e8 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -164,12 +164,12 @@ LavalinkSocket getSocket(String guildId) { @Deprecated public VoiceChannel getConnectedChannel(Guild guild) { - return getLink(guild).getCurrentChannel(); + return getLink(guild).getChannel(); } @Deprecated public String getConnectedChannel(String guildId) { - return getLink(guildId).getCurrentChannel().getId(); + return getLink(guildId).getChannel().getId(); } @Deprecated @@ -251,9 +251,9 @@ private void reconnectVoiceConnections(JDA jda) { links.forEach((guildId, link) -> { try { //Note: We also ensure that the link belongs to the JDA object - if (link.getCurrentChannel() != null + if (link.getChannel() != null && jda.getGuildById(guildId) != null) { - link.connect(link.getCurrentChannel()); + link.connect(link.getChannel()); } } catch (Exception e) { log.error("Caught exception while trying to reconnect link " + link, e); diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index e88e14d15..dc76051a9 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -25,17 +25,11 @@ import lavalink.client.player.LavalinkPlayer; import net.dv8tion.jda.core.JDA; import net.dv8tion.jda.core.entities.VoiceChannel; -import net.dv8tion.jda.core.entities.impl.JDAImpl; import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; /** * Indicates which node we are linked to, what voice channel to use, and what player we are using @@ -43,11 +37,6 @@ public class Link { private static final Logger log = LoggerFactory.getLogger(Link.class); - private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - /** - * Time before we forcefully reconnect if we don't receive our leave event - */ - private static final int TIMEOUT_MS = 5000; private final Lavalink lavalink; private final String guild; @@ -55,23 +44,15 @@ public class Link { /** * Channel we are currently connected to or disconnecting/connecting from/to, if any */ - private volatile String currentChannel = null; + private volatile String channel = null; /** * The node we are currently connected to. Automatically assigned. * Can only be reassigned by reconnecting any existing voice connection. */ - private volatile LavalinkSocket currentNode = null; - private volatile LavalinkSocket pendingNode = null; + private volatile LavalinkSocket node = null; /* May only be set by setState() */ - private volatile State state = State.NO_CHANNEL; - - - /** - * Used for making sure we properly time out disconnect attempts - */ - private final AtomicInteger disconnectCounter = new AtomicInteger(0); - private final AtomicInteger connectCounter = new AtomicInteger(0); + private volatile State state = State.NOT_CONNECTED; Link(Lavalink lavalink, String guildId) { this.lavalink = lavalink; @@ -86,14 +67,15 @@ public LavalinkPlayer getPlayer() { return player; } - public void resetPlayer() { - player = null; - } - public Lavalink getLavalink() { return lavalink; } + @SuppressWarnings("unused") + public void resetPlayer() { + player = null; + } + public String getGuildId() { return guild; } @@ -105,97 +87,17 @@ public String getGuildId() { */ @SuppressWarnings("WeakerAccess") public void connect(VoiceChannel channel) { - if (currentNode == null) { - currentNode = lavalink.loadBalancer.determineBestSocket(channel.getGuild().getIdLong()); - } - connectNow(channel.getId()); - } - - /** - * Invoked when we are ready too establish a connection - * - * @param channelId Channel to connect to - */ - private void connectNow(String channelId) { - if (state == State.DESTROYED) { - log.warn("Attempted to connect to vc for guild " + guild + " while Link is destroyed. Ignoring..."); - return; - } - - int startCount = connectCounter.get(); - - executor.schedule(() -> { - if (startCount == connectCounter.get()) { - log.warn("Connecting timed out for guild " + guild + ". Forcefully sending OP 4..."); - forcefullyDisconnect(); - } - }, TIMEOUT_MS, TimeUnit.MILLISECONDS); - - VoiceChannel channel = getJda() - .getVoiceChannelById(channelId); - - assert channel.getGuild().getId().equals(guild); - - currentChannel = channelId; - setState(State.CONNECTING); - - JSONObject json = new JSONObject(); - json.put("op", "connect"); - json.put("guildId", channel.getGuild().getId()); - json.put("channelId", channel.getId()); - currentNode.send(json.toString()); + // TODO } public void disconnect() { - // Make sure we eventually reconnect - int startCount = disconnectCounter.get(); - - if (currentNode == null) { - log.warn("Current node is somehow null while we are trying to disconnect! " + - "Did someone try to disconnect before connecting in the first place? " + - "Guild: " + guild + " State: " + state); - setState(State.NO_CHANNEL); - return; - } - - executor.schedule(() -> { - if (startCount == disconnectCounter.get()) { - // We didn't get the leave event, so we *must* not be connected - log.warn("Attempted to disconnect from voice but timed out after " + TIMEOUT_MS - + "ms. Are we already disconnected?" + - " Pretending that we left so we don't get stuck... Guild: " + guild); - forcefullyDisconnect(); - onVoiceLeave(); - } - }, TIMEOUT_MS, TimeUnit.MILLISECONDS); - - if (state == State.NO_CHANNEL) { - log.info("Attempt to disconnect from channel when not connected. Guild: " + guild); - sendDisconnectOp(); - return; - } - - if (state != State.DESTROYED) - setState(State.DISCONNECTING); - - sendDisconnectOp(); + // TODO } - private void sendDisconnectOp() { - JSONObject json = new JSONObject(); - json.put("op", "disconnect"); - json.put("guildId", guild); - currentNode.send(json.toString()); - } - - void changeNode(LavalinkSocket newNode) { - if (state == State.NO_CHANNEL) { - changeNode0(newNode); - } else { - // Since we are connected, we will need to disconnect before truly changing node - pendingNode = newNode; - sendDisconnectOp(); - } + public void changeNode(LavalinkSocket newNode) { + disconnect(); + node = newNode; + connect(getJda().getVoiceChannelById(channel)); } /** @@ -205,34 +107,9 @@ void changeNode(LavalinkSocket newNode) { */ @SuppressWarnings("unused") public void destroy() { - log.debug("Destroying Link for " + guild); - lavalink.removeDestroyedLink(this); setState(State.DESTROYED); - if (state != State.NO_CHANNEL) { - try { - disconnect(); - } catch (Exception e) { - // Shouldn't happen. This is to prevent a regression of a previous bug - log.error("Caught exception while trying to disconnect a destroyed link!", e); - } - } - } - - private void changeNode0(LavalinkSocket newNode) { - currentNode = newNode; - pendingNode = null; - VoiceChannel currentChannel = getCurrentChannel(); - if (currentChannel != null) { - connect(currentChannel); - getPlayer().onNodeChange(); - } - } - - @Deprecated - @SuppressWarnings("WeakerAccess") - @Nullable - public LavalinkSocket getCurrentSocket() { - return currentNode; + disconnect(); + lavalink.removeDestroyedLink(this); } /** @@ -251,61 +128,40 @@ public LavalinkSocket getNode() { @Nullable @SuppressWarnings("WeakerAccess") public LavalinkSocket getNode(boolean selectIfAbsent) { - if (selectIfAbsent && currentNode == null) { - currentNode = lavalink.loadBalancer.determineBestSocket(Long.parseLong(guild)); + if (selectIfAbsent && node == null) { + node = lavalink.loadBalancer.determineBestSocket(Long.parseLong(guild)); } - return currentNode; + return node; } void onVoiceJoin() { - connectCounter.incrementAndGet(); - setState(State.CONNECTED); + } void onVoiceLeave() { - disconnectCounter.incrementAndGet(); - - if (pendingNode != null) { - // Disconnecting means we can change to the pending node, if any - changeNode0(pendingNode); - } else { - setState(State.NO_CHANNEL); - currentChannel = null; - } + } void onGuildVoiceMove(GuildVoiceMoveEvent event) { - connectCounter.incrementAndGet(); - log.info("Moved from " + event.getChannelLeft() + " to " + event.getChannelJoined()); - currentChannel = event.getChannelJoined().getId(); + } void onNodeDisconnected() { - changeNode(lavalink.loadBalancer.determineBestSocket(Long.parseLong(this.guild))); - - // This will trigger a leave - forcefullyDisconnect(); - } - /** - * This sends OP 4 to Discord. This should close any voice connection for our guild by setting the channel to null. - */ - private void forcefullyDisconnect() { - ((JDAImpl) getJda()).getClient() - .send("{\"op\":4,\"d\":{\"self_deaf\":false,\"guild_id\":\"" + guild + "\",\"channel_id\":null,\"self_mute\":false}}"); } /** - * @return The channel we are currently connect to, even if we are trying to connect to a different one + * @return The channel we are currently connect to */ @SuppressWarnings("WeakerAccess") - public VoiceChannel getCurrentChannel() { - if (currentChannel == null) return null; + public VoiceChannel getChannel() { + if (channel == null) return null; return getJda() - .getVoiceChannelById(currentChannel); + .getVoiceChannelById(channel); } + @SuppressWarnings("WeakerAccess") public JDA getJda() { return lavalink.getJdaFromSnowflake(guild); } @@ -313,6 +169,7 @@ public JDA getJda() { /** * @return The {@link State} of this {@link Link} */ + @SuppressWarnings("unused") public State getState() { return state; } @@ -329,7 +186,7 @@ private void setState(State state) { public String toString() { return "Link{" + "guild='" + guild + '\'' + - ", currentChannel='" + currentChannel + '\'' + + ", channel='" + channel + '\'' + ", state=" + state + '}'; } @@ -338,22 +195,18 @@ public enum State { /** * Default, means we are not trying to use voice at all */ - NO_CHANNEL, + NOT_CONNECTED, /** - * Connecting to voice + * Waiting for VOICE_SERVER_UPDATE */ CONNECTING, /** - * Connected (or attempting to reconnect) to voice + * We have dispatched the voice server info to the server, and it should (soon) be connected. */ CONNECTED, - /** - * We are trying to disconnect, after which we will switch to NO_CHANNEL - */ - DISCONNECTING, /** * This {@link Link} has been destroyed and will soon (if not already) be unmapped from {@link Lavalink} */ From 7e4f60246c4bccd4d5e4b5bad320cf16ae207dd3 Mon Sep 17 00:00:00 2001 From: Repulser Date: Mon, 15 Jan 2018 19:34:51 +0200 Subject: [PATCH 05/50] Initial commit --- LavalinkServer/build.gradle | 2 +- .../server/io/ConnectionManagerImpl.java | 40 +++++ .../lavalink/server/io/CoreClientImpl.java | 148 ++---------------- .../lavalink/server/io/SocketContext.java | 6 +- .../java/lavalink/server/io/SocketServer.java | 27 ---- 5 files changed, 54 insertions(+), 169 deletions(-) create mode 100644 LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 981c7c8a0..dc3e89f52 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,7 +17,7 @@ publishToMavenLocal.dependsOn 'bootRepackage' dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: '7a3f9d40a32b74b6e85e0e0c8f9a051f004b5584' + compile group: 'com.github.Repulser', name: 'JDA-Audio', version: '7589a36ffd308f8035fd2318d93bb20465080ef4' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.4' diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java new file mode 100644 index 000000000..0251f5469 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -0,0 +1,40 @@ +package lavalink.server.io; + +import net.dv8tion.jda.manager.ConnectionManager; +import org.java_websocket.WebSocket; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by Repulser + * https://github.com/Repulser + */ +public class ConnectionManagerImpl implements ConnectionManager { + private static final Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class); + private final WebSocket socket; + + ConnectionManagerImpl(WebSocket socket) { + this.socket = socket; + } + + @Override + public void removeAudioConnection(String guildId) { + log.warn("removeAudioConnection was requested, this shouldn't happen"); + } + + @Override + public void queueAudioConnect(String guildId, String channelId) { + + } + + @Override + public void shouldReconnect(String guildId) { + JSONObject obj = new JSONObject() + .put("op", "disconnected") + .put("guildId", guildId); + socket.send(obj.toString()); + } + + +} diff --git a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java index a6b85c58b..54450603b 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java @@ -22,171 +22,41 @@ package lavalink.server.io; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import lavalink.server.util.ResetableCountDownLatch; import net.dv8tion.jda.CoreClient; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.java_websocket.WebSocket; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.TimeUnit; - public class CoreClientImpl implements CoreClient { private static final Logger log = LoggerFactory.getLogger(CoreClientImpl.class); - private static final int TIMEOUT = 60 * 1000; - - private final WebSocket socket; - private int shardId; - - private final ResetableCountDownLatch validationLatch = new ResetableCountDownLatch(1); - private final ResetableCountDownLatch isConnectedLatch = new ResetableCountDownLatch(1); - private boolean connected = false; - - private LoadingCache guildValidMap = CacheBuilder.newBuilder() - .concurrencyLevel(4) - .expireAfterWrite(5, TimeUnit.SECONDS) - .build( - new CacheLoader() { - @Override - public Boolean load(@SuppressWarnings("NullableProblems") String guild) throws Exception { - return requestValidationSync(guild, null); - } - } - ); - - private LoadingCache, Boolean> channelValidMap = CacheBuilder.newBuilder() - .concurrencyLevel(4) - .expireAfterWrite(5, TimeUnit.SECONDS) - .build( - new CacheLoader, Boolean>() { - @Override - public Boolean load(@SuppressWarnings("NullableProblems") ImmutablePair key) throws Exception { - return requestValidationSync(key.left, key.right); - } - } - ); - - - CoreClientImpl(WebSocket socket, int shardId) { - this.socket = socket; - this.shardId = shardId; - } @Override public void sendWS(String message) { - log.info(message); - JSONObject json = new JSONObject(); - json.put("op", "sendWS"); - json.put("shardId", shardId); - json.put("message", message); - socket.send(json.toString()); + log.warn("sendWS was requested, this shouldn't happen"); } @Override public boolean isConnected() { - return requestIsConnectedSync(); + log.warn("isConnected was requested, this shouldn't happen"); + return true; } @Override public boolean inGuild(String guildId) { - log.info("Requested guild check"); - boolean val = guildValidMap.getUnchecked(guildId); - if (!val) { - log.warn("Requested guild check but validation was false!"); - } - return val; + log.warn("inGuild was requested, this shouldn't happen"); + return true; } @Override public boolean voiceChannelExists(String guildId, String channelId) { - log.info("Requested channel check"); - boolean val = channelValidMap.getUnchecked(new ImmutablePair<>(guildId, channelId)); - if (!val) { - log.warn("Requested channel check but validation was false!"); - } - return val; + log.warn("voiceChannelExists was requested, this shouldn't happen"); + return true; } @Override public boolean hasPermissionInChannel(String guildId, String channelId, long l) { - log.info("Requested permission check"); - boolean val = channelValidMap.getUnchecked(new ImmutablePair<>(guildId, channelId)); - if (!val) { - log.warn("Requested permission check but validation was false!"); - } - return val; - } - - - - private boolean requestValidationSync(String guildId, String channelId) { - JSONObject json = new JSONObject(); - json.put("op", "validationReq"); - json.put("guildId", guildId); - - if (channelId != null) { - json.put("channelId", channelId); - } - - long startTime = System.currentTimeMillis(); - socket.send(json.toString()); - - try { - validationLatch.await(TIMEOUT, TimeUnit.MILLISECONDS); - validationLatch.reset(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - if (System.currentTimeMillis() - startTime >= TIMEOUT) { - log.error("Validation timed out after " + TIMEOUT + " millis"); - return false; - } - - return channelId == null - ? guildValidMap.getIfPresent(guildId) == Boolean.TRUE - : channelValidMap.getIfPresent(new ImmutablePair<>(guildId, channelId)) == Boolean.TRUE; - } - - void provideValidation(String guildId, String channelId, boolean valid) { - guildValidMap.put(guildId, valid); - if (channelId != null) - channelValidMap.put(new ImmutablePair<>(guildId, channelId), valid); - - validationLatch.countDown(); - } - - private boolean requestIsConnectedSync() { - JSONObject json = new JSONObject(); - json.put("op", "isConnectedReq"); - json.put("shardId", shardId); - - long startTime = System.currentTimeMillis(); - socket.send(json.toString()); - - try { - isConnectedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS); - isConnectedLatch.reset(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - if (System.currentTimeMillis() - startTime >= TIMEOUT) { - throw new RuntimeException("Connection checking timed out after " + TIMEOUT + " millis"); - } - - return connected; - } - - void provideIsConnected(boolean connected) { - this.connected = connected; - - isConnectedLatch.countDown(); + log.warn("hasPermissionInChannel was requested, this shouldn't happen"); + return true; } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java index fdbbb5c52..c533958ce 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java @@ -30,6 +30,8 @@ import net.dv8tion.jda.Core; import net.dv8tion.jda.audio.factory.IAudioSendFactory; import net.dv8tion.jda.manager.AudioManager; +import net.dv8tion.jda.manager.ConnectionManager; +import net.dv8tion.jda.manager.ConnectionManagerBuilder; import org.java_websocket.WebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,9 +80,9 @@ Core getCore(int shardId) { return cores.computeIfAbsent(shardId, __ -> { if (nasSupported) - return new Core(userId, new CoreClientImpl(socket, shardId), getAudioSendFactory(shardId)); + return new Core(userId, new CoreClientImpl(), core -> new ConnectionManagerImpl(socket), getAudioSendFactory(shardId)); else - return new Core(userId, new CoreClientImpl(socket, shardId)); + return new Core(userId, new CoreClientImpl(), (ConnectionManagerBuilder) core -> new ConnectionManagerImpl(socket)); } ); } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java index fe2629312..f17032657 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java @@ -26,9 +26,7 @@ import com.sedmelluq.discord.lavaplayer.track.TrackMarker; import lavalink.server.player.Player; import lavalink.server.player.TrackEndMarkerHandler; -import lavalink.server.util.DebugConnectionListener; import lavalink.server.util.Util; -import net.dv8tion.jda.manager.AudioManager; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; @@ -113,37 +111,12 @@ public void onMessage(WebSocket webSocket, String s) { switch (json.getString("op")) { /* JDAA ops */ - case "connect": - long guildId = Long.parseLong(json.getString("guildId")); - AudioManager manager = contextMap.get(webSocket).getCore(getShardId(webSocket, json)) - .getAudioManager(json.getString("guildId")); - if (manager.getConnectionListener() == null) { - manager.setConnectionListener(new DebugConnectionListener(guildId)); - } - manager.openAudioConnection(json.getString("channelId")); - break; case "voiceUpdate": contextMap.get(webSocket).getCore(getShardId(webSocket, json)).provideVoiceServerUpdate( json.getString("sessionId"), json.getJSONObject("event") ); break; - case "disconnect": - contextMap.get(webSocket).getCore(getShardId(webSocket, json)).getAudioManager(json.getString("guildId")) - .closeAudioConnection(); - break; - case "validationRes": - ((CoreClientImpl) contextMap.get(webSocket).getCore(getShardId(webSocket, json)).getClient()).provideValidation( - json.getString("guildId"), - json.optString("channelId"), - json.getBoolean("valid") - ); - break; - case "isConnectedRes": - ((CoreClientImpl) contextMap.get(webSocket).getCore(json.getInt("shardId")).getClient()).provideIsConnected( - json.getBoolean("connected") - ); - break; /* Player ops */ case "play": From b58a9a1595aa8250c145840b8993319278a69e60 Mon Sep 17 00:00:00 2001 From: Repulser Date: Mon, 15 Jan 2018 19:51:01 +0200 Subject: [PATCH 06/50] Fix --- LavalinkServer/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index dc3e89f52..0190f57e8 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,7 +17,7 @@ publishToMavenLocal.dependsOn 'bootRepackage' dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'com.github.Repulser', name: 'JDA-Audio', version: '7589a36ffd308f8035fd2318d93bb20465080ef4' + compile group: 'com.github.Repulser', name: 'JDA-Audio', version: 'a94f6bd' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.4' From 03b7154d069ce5242d8244199526dd1f73e7ee78 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Mon, 15 Jan 2018 23:19:05 +0100 Subject: [PATCH 07/50] VoiceConnectionManager stub --- .../main/java/lavalink/client/io/Link.java | 23 +++--- .../client/io/VoiceConnectionManager.java | 73 +++++++++++++++++++ .../io/VoiceServerUpdateInterceptor.java | 1 + 3 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index dc76051a9..12a28f821 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -39,24 +39,16 @@ public class Link { private static final Logger log = LoggerFactory.getLogger(Link.class); private final Lavalink lavalink; - private final String guild; + private final long guild; private LavalinkPlayer player; - /** - * Channel we are currently connected to or disconnecting/connecting from/to, if any - */ private volatile String channel = null; - /** - * The node we are currently connected to. Automatically assigned. - * Can only be reassigned by reconnecting any existing voice connection. - */ private volatile LavalinkSocket node = null; - /* May only be set by setState() */ private volatile State state = State.NOT_CONNECTED; Link(Lavalink lavalink, String guildId) { this.lavalink = lavalink; - this.guild = guildId; + this.guild = Long.parseLong(guildId); } public LavalinkPlayer getPlayer() { @@ -77,6 +69,10 @@ public void resetPlayer() { } public String getGuildId() { + return Long.toString(guild); + } + + public long getGuildIdLong() { return guild; } @@ -129,7 +125,7 @@ public LavalinkSocket getNode() { @SuppressWarnings("WeakerAccess") public LavalinkSocket getNode(boolean selectIfAbsent) { if (selectIfAbsent && node == null) { - node = lavalink.loadBalancer.determineBestSocket(Long.parseLong(guild)); + node = lavalink.loadBalancer.determineBestSocket(guild); } return node; } @@ -157,13 +153,12 @@ void onNodeDisconnected() { public VoiceChannel getChannel() { if (channel == null) return null; - return getJda() - .getVoiceChannelById(channel); + return getJda().getVoiceChannelById(channel); } @SuppressWarnings("WeakerAccess") public JDA getJda() { - return lavalink.getJdaFromSnowflake(guild); + return lavalink.getJdaFromSnowflake(String.valueOf(guild)); } /** diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java new file mode 100644 index 000000000..3a49bec2e --- /dev/null +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java @@ -0,0 +1,73 @@ +package lavalink.client.io; + +import net.dv8tion.jda.core.entities.impl.JDAImpl; +import org.json.JSONObject; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.concurrent.ConcurrentHashMap; + +public class VoiceConnectionManager { + + private final Lavalink lavalink; + private ConcurrentHashMap pendingLinks = new ConcurrentHashMap<>(); + + public VoiceConnectionManager(Lavalink lavalink) { + this.lavalink = lavalink; + } + + void requestVoiceConnection(Link link) { + pendingLinks.put(link.getGuildIdLong(), link); + + sendOp4((JDAImpl) link.getJda(), + link.getGuildIdLong(), + link.getChannel().getIdLong()); + } + + void requestVoiceMove(Link link, long newChannel) { + sendOp4((JDAImpl) link.getJda(), + link.getGuildIdLong(), + newChannel); + } + + void disconnectVoiceConnection(Link link) { + pendingLinks.remove(Long.parseLong(link.getGuildId())); + sendOp4((JDAImpl) link.getJda(), + link.getGuildIdLong(), + null); // Null = disconnect + } + + /** + * Invoked by {@link VoiceServerUpdateInterceptor} + * @return true if the Voice_SERVER_UPDATE is for a channel we expect to join + */ + boolean onServerUpdate(long guildId, long expectedChannel) { + /* + This lambda atomically checks the pendingLinks map for the Link, and if it is in the expected channel. + If it *is* for the expected channel, this lambda will return null, and otherwise the link. + As a side effect, this removes the Link if we proceed. + */ + Link result = pendingLinks.computeIfPresent(guildId, (__, link) -> { + if (link.getChannel() != null + && link.getChannel().getIdLong() == expectedChannel) { + return null; + } else { + return link; + } + }); + + return result == null; + } + + private void sendOp4(@Nonnull JDAImpl jda, + long guildId, + @Nullable Long channel) { + + jda.getClient().send(new JSONObject() + .put("op", 4) + .put("d", new JSONObject() + .put("guild_id", guildId) + .put("channel_id", channel == null ? JSONObject.NULL : channel)) + .toString()); + } +} diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java index 7a4793637..4e9104dc0 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java @@ -60,6 +60,7 @@ protected Long handleInternally(JSONObject content) { json.put("sessionId", sessionId); json.put("guildId", guild.getId()); json.put("event", content); + //noinspection ConstantConditions lavalink.getLink(guild).getNode(true).send(json.toString()); log.info("Sent voice update for guild {}", idLong); From 632169382be8ee5b8f20c54d6b80dd3c002a37e5 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Tue, 16 Jan 2018 11:32:23 +0100 Subject: [PATCH 08/50] Update JDA --- LavalinkClient/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index adb4ca612..1eb8216a2 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -8,7 +8,7 @@ dependencies { compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.4' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' compile group: 'org.json', name: 'json', version: '20170516' - compile group: 'net.dv8tion', name: 'JDA', version: '3.3.1_307' + compile group: 'net.dv8tion', name: 'JDA', version: '3.5.0_327' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.0.0-M4' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.0.0-M4' testCompile group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.0.0-M4' From 51d3c691dae13693dcf098badbf02078b0f101b5 Mon Sep 17 00:00:00 2001 From: Repulser Date: Tue, 16 Jan 2018 12:33:56 +0200 Subject: [PATCH 09/50] Fixups --- .../main/java/lavalink/server/io/ConnectionManagerImpl.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index 0251f5469..8e022cc65 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -3,15 +3,12 @@ import net.dv8tion.jda.manager.ConnectionManager; import org.java_websocket.WebSocket; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Created by Repulser * https://github.com/Repulser */ public class ConnectionManagerImpl implements ConnectionManager { - private static final Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class); private final WebSocket socket; ConnectionManagerImpl(WebSocket socket) { @@ -20,12 +17,10 @@ public class ConnectionManagerImpl implements ConnectionManager { @Override public void removeAudioConnection(String guildId) { - log.warn("removeAudioConnection was requested, this shouldn't happen"); } @Override public void queueAudioConnect(String guildId, String channelId) { - } @Override From 7d5d96da87ddfd2b245041e8d704af5d336425c3 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Tue, 16 Jan 2018 16:00:51 +0100 Subject: [PATCH 10/50] Sprinkled in more work on VoiceConnectionManager during school --- .../client/io/VoiceConnectionManager.java | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java index 3a49bec2e..0b07ad6c7 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java @@ -1,19 +1,37 @@ package lavalink.client.io; import net.dv8tion.jda.core.entities.impl.JDAImpl; +import net.dv8tion.jda.core.requests.WebSocketClient; import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.concurrent.ConcurrentHashMap; -public class VoiceConnectionManager { +class VoiceConnectionManager { + + private static final Logger log = LoggerFactory.getLogger(VoiceConnectionManager.class); + private static final Method JDA_SEND_METHOD; private final Lavalink lavalink; - private ConcurrentHashMap pendingLinks = new ConcurrentHashMap<>(); + private final ConcurrentHashMap pendingLinks = new ConcurrentHashMap<>(); + + static { + try { + JDA_SEND_METHOD = WebSocketClient.class.getDeclaredMethod("send", String.class, boolean.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + JDA_SEND_METHOD.setAccessible(true); + } - public VoiceConnectionManager(Lavalink lavalink) { + VoiceConnectionManager(Lavalink lavalink) { this.lavalink = lavalink; + new VoiceConnectionQueueController().start(); } void requestVoiceConnection(Link link) { @@ -24,14 +42,9 @@ void requestVoiceConnection(Link link) { link.getChannel().getIdLong()); } - void requestVoiceMove(Link link, long newChannel) { - sendOp4((JDAImpl) link.getJda(), - link.getGuildIdLong(), - newChannel); - } - void disconnectVoiceConnection(Link link) { pendingLinks.remove(Long.parseLong(link.getGuildId())); + sendOp4((JDAImpl) link.getJda(), link.getGuildIdLong(), null); // Null = disconnect @@ -39,7 +52,8 @@ void disconnectVoiceConnection(Link link) { /** * Invoked by {@link VoiceServerUpdateInterceptor} - * @return true if the Voice_SERVER_UPDATE is for a channel we expect to join + * + * @return true if the VOICE_SERVER_UPDATE is for a channel we expect to join */ boolean onServerUpdate(long guildId, long expectedChannel) { /* @@ -63,11 +77,33 @@ private void sendOp4(@Nonnull JDAImpl jda, long guildId, @Nullable Long channel) { - jda.getClient().send(new JSONObject() + String message = new JSONObject() .put("op", 4) .put("d", new JSONObject() .put("guild_id", guildId) .put("channel_id", channel == null ? JSONObject.NULL : channel)) - .toString()); + .toString(); + + try { + JDA_SEND_METHOD.invoke(jda.getClient(), message, false); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private class VoiceConnectionQueueController extends Thread { + + private VoiceConnectionQueueController() { + setName("VoiceConnectionQueueController"); + setDaemon(true); + setUncaughtExceptionHandler( + (__, e) -> log.error("Caught exception in VoiceConnectionQueueController. NOT GOOD", e) + ); + } + + @Override + public void run() { + //TODO + } } } From dc3c382bfb67dd406c640fda8ce7c80c8c3bc262 Mon Sep 17 00:00:00 2001 From: Repulser Date: Tue, 16 Jan 2018 21:36:42 +0200 Subject: [PATCH 11/50] destroy op --- .../src/main/java/lavalink/server/io/SocketServer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java index f17032657..b0ea77621 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java @@ -161,6 +161,9 @@ public void onMessage(WebSocket webSocket, String s) { Player player4 = contextMap.get(webSocket).getPlayer(json.getString("guildId")); player4.setVolume(json.getInt("volume")); break; + case "destroy": + Player player5 = contextMap.get(webSocket).getPlayers().remove(json.getString("guildId")); + if (player5 != null) player5.stop(); default: log.warn("Unexpected operation: " + json.getString("op")); break; From 3ed17ac008bca03fd791323d198d9da5e6dac356 Mon Sep 17 00:00:00 2001 From: Repulser Date: Tue, 16 Jan 2018 22:15:01 +0200 Subject: [PATCH 12/50] extra warnings + update JDAA --- LavalinkServer/build.gradle | 2 +- .../java/lavalink/server/io/ConnectionManagerImpl.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 0190f57e8..c42031ef4 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,7 +17,7 @@ publishToMavenLocal.dependsOn 'bootRepackage' dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'com.github.Repulser', name: 'JDA-Audio', version: 'a94f6bd' + compile group: 'com.github.Repulser', name: 'JDA-Audio', version: 'f9b06ee0e4c1c0cdd9c801c6023fcfed8beb34cb' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.4' diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index 8e022cc65..2c670cdf1 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -3,14 +3,18 @@ import net.dv8tion.jda.manager.ConnectionManager; import org.java_websocket.WebSocket; import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Created by Repulser * https://github.com/Repulser */ public class ConnectionManagerImpl implements ConnectionManager { + private static final Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class); private final WebSocket socket; + ConnectionManagerImpl(WebSocket socket) { this.socket = socket; } @@ -21,15 +25,15 @@ public void removeAudioConnection(String guildId) { @Override public void queueAudioConnect(String guildId, String channelId) { + log.warn("queueAudioConnect was requested, this shouldn't happen"); } @Override - public void shouldReconnect(String guildId) { + public void onDisconnect(String guildId) { JSONObject obj = new JSONObject() .put("op", "disconnected") .put("guildId", guildId); socket.send(obj.toString()); } - } From 09b91dfaafed9ccec85d6306ca85398a88e561dd Mon Sep 17 00:00:00 2001 From: Frederikam Date: Tue, 16 Jan 2018 23:38:14 +0100 Subject: [PATCH 13/50] More client work + paradigm shift --- LavalinkClient/.gitignore | 3 +- .../java/lavalink/client/io/Lavalink.java | 97 +++------------- .../client/io/LavalinkLoadBalancer.java | 4 +- .../main/java/lavalink/client/io/Link.java | 68 +++++++---- .../client/io/VoiceConnectionManager.java | 109 ------------------ 5 files changed, 63 insertions(+), 218 deletions(-) delete mode 100644 LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java diff --git a/LavalinkClient/.gitignore b/LavalinkClient/.gitignore index 3fe8b671e..b1386aa33 100644 --- a/LavalinkClient/.gitignore +++ b/LavalinkClient/.gitignore @@ -2,4 +2,5 @@ /logs/ /target/ /dependency-reduced-pom.xml -build/* \ No newline at end of file +build/* +/out/ diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index a95ad13e8..054ca3916 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -41,6 +41,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.net.URI; import java.util.Collection; import java.util.HashMap; @@ -104,16 +105,20 @@ public void removeNode(int key) { node.close(); } + @SuppressWarnings("unused") + @Nonnull + public LavalinkLoadBalancer getLoadBalancer() { + return loadBalancer; + } + @SuppressWarnings("WeakerAccess") + @Nonnull public Link getLink(String guildId) { return links.computeIfAbsent(guildId, __ -> new Link(this, guildId)); } - public LavalinkLoadBalancer getLoadBalancer() { - return loadBalancer; - } - @SuppressWarnings("WeakerAccess") + @Nonnull public Link getLink(Guild guild) { return getLink(guild.getId()); } @@ -124,21 +129,25 @@ public int getNumShards() { } @SuppressWarnings("WeakerAccess") + @Nonnull public Collection getLinks() { return links.values(); } @SuppressWarnings("WeakerAccess") + @Nonnull public List getNodes() { return nodes; } @SuppressWarnings("WeakerAccess") + @Nonnull public JDA getJda(int shardId) { return jdaProvider.apply(shardId); } @SuppressWarnings("WeakerAccess") + @Nonnull public JDA getJdaFromSnowflake(String snowflake) { return jdaProvider.apply(LavalinkUtil.getShardFromSnowflake(snowflake, numShards)); } @@ -153,45 +162,6 @@ void removeDestroyedLink(Link link) { links.remove(link.getGuildId()); } - /* - * Deprecated, will be removed in v2.0 - */ - - @Deprecated - LavalinkSocket getSocket(String guildId) { - return getLink(guildId).getNode(false); - } - - @Deprecated - public VoiceChannel getConnectedChannel(Guild guild) { - return getLink(guild).getChannel(); - } - - @Deprecated - public String getConnectedChannel(String guildId) { - return getLink(guildId).getChannel().getId(); - } - - @Deprecated - public LavalinkSocket getNodeForGuild(Guild guild) { - return getLink(guild).getNode(false); - } - - @Deprecated - public LavalinkPlayer getPlayer(String guildId) { - return getLink(guildId).getPlayer(); - } - - @Deprecated - public void openVoiceConnection(VoiceChannel channel) { - getLink(channel.getGuild()).connect(channel); - } - - @Deprecated - public void closeVoiceConnection(Guild guild) { - getLink(guild).disconnect(); - } - /* * JDA event handling */ @@ -202,50 +172,11 @@ public void onReady(ReadyEvent event) { .put("VOICE_SERVER_UPDATE", new VoiceServerUpdateInterceptor(this, (JDAImpl) event.getJDA())); } - @Override - public void onDisconnect(DisconnectEvent event) { - disconnectVoiceConnection(event.getJDA()); - } - - @Override - public void onShutdown(ShutdownEvent event) { - disconnectVoiceConnection(event.getJDA()); - } - @Override public void onReconnect(ReconnectedEvent event) { reconnectVoiceConnections(event.getJDA()); } - @Override - public void onResume(ResumedEvent event) { - reconnectVoiceConnections(event.getJDA()); - } - - @Override - public void onGuildVoiceJoin(GuildVoiceJoinEvent event) { - // Check if not ourselves - if (!event.getMember().getUser().equals(event.getJDA().getSelfUser())) return; - - getLink(event.getGuild()).onVoiceJoin(); - } - - @Override - public void onGuildVoiceLeave(GuildVoiceLeaveEvent event) { - // Check if not ourselves - if (!event.getMember().getUser().equals(event.getJDA().getSelfUser())) return; - - getLink(event.getGuild()).onVoiceLeave(); - } - - @Override - public void onGuildVoiceMove(GuildVoiceMoveEvent event) { - // Check if not ourselves - if (!event.getMember().getUser().equals(event.getJDA().getSelfUser())) return; - - getLink(event.getGuild()).onGuildVoiceMove(event); - } - private void reconnectVoiceConnections(JDA jda) { if (autoReconnect) { links.forEach((guildId, link) -> { @@ -262,7 +193,7 @@ private void reconnectVoiceConnections(JDA jda) { } } - private void disconnectVoiceConnection(JDA jda) { + private void disconnectVoiceConnections(JDA jda) { links.forEach((guildId, link) -> { try { if (jda.getGuildById(guildId) != null) { diff --git a/LavalinkClient/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java b/LavalinkClient/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java index caad5d92a..af499b2e0 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java @@ -57,10 +57,12 @@ record = total; return leastPenalty; } + @SuppressWarnings("unused") public void addPenalty(PenaltyProvider penalty) { this.penaltyProviders.add(penalty); } + @SuppressWarnings("unused") public void removePenalty(PenaltyProvider penalty) { this.penaltyProviders.remove(penalty); } @@ -68,7 +70,7 @@ public void removePenalty(PenaltyProvider penalty) { void onNodeDisconnect(LavalinkSocket disconnected) { lavalink.getLinks().forEach(link -> { if (disconnected.equals(link.getNode(false))) - link.onNodeDisconnected(); + link.changeNode(lavalink.loadBalancer.determineBestSocket(link.getGuildIdLong())); }); } diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 12a28f821..d5ba475bd 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -24,11 +24,19 @@ import lavalink.client.player.LavalinkPlayer; import net.dv8tion.jda.core.JDA; +import net.dv8tion.jda.core.Permission; +import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.Member; import net.dv8tion.jda.core.entities.VoiceChannel; +import net.dv8tion.jda.core.entities.impl.JDAImpl; import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent; +import net.dv8tion.jda.core.exceptions.GuildUnavailableException; +import net.dv8tion.jda.core.exceptions.InsufficientPermissionException; +import net.dv8tion.jda.core.requests.WebSocketClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import javax.annotation.Nullable; /** @@ -83,17 +91,34 @@ public long getGuildIdLong() { */ @SuppressWarnings("WeakerAccess") public void connect(VoiceChannel channel) { - // TODO + if (!channel.getGuild().equals(getJda().getGuildById(guild))) + throw new IllegalArgumentException("The provided VoiceChannel is not a part of the Guild that this AudioManager handles." + + "Please provide a VoiceChannel from the proper Guild"); + if (!channel.getGuild().isAvailable()) + throw new GuildUnavailableException("Cannot open an Audio Connection with an unavailable guild. " + + "Please wait until this Guild is available to open a connection."); + final Member self = channel.getGuild().getSelfMember(); + if (!self.hasPermission(channel, Permission.VOICE_CONNECT) && !self.hasPermission(channel, Permission.VOICE_MOVE_OTHERS)) + throw new InsufficientPermissionException(Permission.VOICE_CONNECT); + + setState(State.CONNECTING); + getMainWs().queueAudioConnect(channel); } public void disconnect() { - // TODO + Guild g = getJda().getGuildById(guild); + + if (g == null) return; + + setState(State.DISCONNECTING); + getMainWs().queueAudioDisconnect(g); } public void changeNode(LavalinkSocket newNode) { disconnect(); node = newNode; connect(getJda().getVoiceChannelById(channel)); + // TODO: Handle properly } /** @@ -130,37 +155,17 @@ public LavalinkSocket getNode(boolean selectIfAbsent) { return node; } - void onVoiceJoin() { - - } - - void onVoiceLeave() { - - } - - void onGuildVoiceMove(GuildVoiceMoveEvent event) { - - } - - void onNodeDisconnected() { - - } - /** * @return The channel we are currently connect to */ @SuppressWarnings("WeakerAccess") + @Nullable public VoiceChannel getChannel() { if (channel == null) return null; return getJda().getVoiceChannelById(channel); } - @SuppressWarnings("WeakerAccess") - public JDA getJda() { - return lavalink.getJdaFromSnowflake(String.valueOf(guild)); - } - /** * @return The {@link State} of this {@link Link} */ @@ -169,7 +174,7 @@ public State getState() { return state; } - private void setState(State state) { + private void setState(@Nonnull State state) { if (this.state == State.DESTROYED && state != State.DESTROYED) throw new IllegalStateException("Cannot change state to " + state + " when state is " + State.DESTROYED); @@ -177,6 +182,16 @@ private void setState(State state) { this.state = state; } + @SuppressWarnings("WeakerAccess") + @Nonnull + public JDA getJda() { + return lavalink.getJdaFromSnowflake(String.valueOf(guild)); + } + + private WebSocketClient getMainWs() { + return ((JDAImpl) getJda()).getClient(); + } + @Override public String toString() { return "Link{" + @@ -202,6 +217,11 @@ public enum State { */ CONNECTED, + /** + * Waiting for confirmation from Discord that we have connected + */ + DISCONNECTING, + /** * This {@link Link} has been destroyed and will soon (if not already) be unmapped from {@link Lavalink} */ diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java deleted file mode 100644 index 0b07ad6c7..000000000 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceConnectionManager.java +++ /dev/null @@ -1,109 +0,0 @@ -package lavalink.client.io; - -import net.dv8tion.jda.core.entities.impl.JDAImpl; -import net.dv8tion.jda.core.requests.WebSocketClient; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.concurrent.ConcurrentHashMap; - -class VoiceConnectionManager { - - private static final Logger log = LoggerFactory.getLogger(VoiceConnectionManager.class); - private static final Method JDA_SEND_METHOD; - - private final Lavalink lavalink; - private final ConcurrentHashMap pendingLinks = new ConcurrentHashMap<>(); - - static { - try { - JDA_SEND_METHOD = WebSocketClient.class.getDeclaredMethod("send", String.class, boolean.class); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - JDA_SEND_METHOD.setAccessible(true); - } - - VoiceConnectionManager(Lavalink lavalink) { - this.lavalink = lavalink; - new VoiceConnectionQueueController().start(); - } - - void requestVoiceConnection(Link link) { - pendingLinks.put(link.getGuildIdLong(), link); - - sendOp4((JDAImpl) link.getJda(), - link.getGuildIdLong(), - link.getChannel().getIdLong()); - } - - void disconnectVoiceConnection(Link link) { - pendingLinks.remove(Long.parseLong(link.getGuildId())); - - sendOp4((JDAImpl) link.getJda(), - link.getGuildIdLong(), - null); // Null = disconnect - } - - /** - * Invoked by {@link VoiceServerUpdateInterceptor} - * - * @return true if the VOICE_SERVER_UPDATE is for a channel we expect to join - */ - boolean onServerUpdate(long guildId, long expectedChannel) { - /* - This lambda atomically checks the pendingLinks map for the Link, and if it is in the expected channel. - If it *is* for the expected channel, this lambda will return null, and otherwise the link. - As a side effect, this removes the Link if we proceed. - */ - Link result = pendingLinks.computeIfPresent(guildId, (__, link) -> { - if (link.getChannel() != null - && link.getChannel().getIdLong() == expectedChannel) { - return null; - } else { - return link; - } - }); - - return result == null; - } - - private void sendOp4(@Nonnull JDAImpl jda, - long guildId, - @Nullable Long channel) { - - String message = new JSONObject() - .put("op", 4) - .put("d", new JSONObject() - .put("guild_id", guildId) - .put("channel_id", channel == null ? JSONObject.NULL : channel)) - .toString(); - - try { - JDA_SEND_METHOD.invoke(jda.getClient(), message, false); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - private class VoiceConnectionQueueController extends Thread { - - private VoiceConnectionQueueController() { - setName("VoiceConnectionQueueController"); - setDaemon(true); - setUncaughtExceptionHandler( - (__, e) -> log.error("Caught exception in VoiceConnectionQueueController. NOT GOOD", e) - ); - } - - @Override - public void run() { - //TODO - } - } -} From 255a9c9cbf86cd0529480dc4aea67de446a42aa9 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Wed, 17 Jan 2018 09:38:42 +0100 Subject: [PATCH 14/50] Event handling of voice state, channel delete, and guild leave --- .../java/lavalink/client/io/Lavalink.java | 29 +++++++- .../main/java/lavalink/client/io/Link.java | 8 +++ .../io/VoiceServerUpdateInterceptor.java | 2 +- .../io/VoiceStateUpdateInterceptor.java | 66 +++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index 054ca3916..7be7a9372 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -33,10 +33,14 @@ import net.dv8tion.jda.core.events.ReconnectedEvent; import net.dv8tion.jda.core.events.ResumedEvent; import net.dv8tion.jda.core.events.ShutdownEvent; +import net.dv8tion.jda.core.events.channel.voice.VoiceChannelDeleteEvent; +import net.dv8tion.jda.core.events.guild.GuildLeaveEvent; import net.dv8tion.jda.core.events.guild.voice.GuildVoiceJoinEvent; import net.dv8tion.jda.core.events.guild.voice.GuildVoiceLeaveEvent; import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent; +import net.dv8tion.jda.core.handle.SocketHandler; import net.dv8tion.jda.core.hooks.ListenerAdapter; +import net.dv8tion.jda.core.requests.WebSocketClient; import org.java_websocket.drafts.Draft_6455; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +50,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executors; @@ -168,8 +173,26 @@ void removeDestroyedLink(Link link) { @Override public void onReady(ReadyEvent event) { - ((JDAImpl) event.getJDA()).getClient().getHandlers() - .put("VOICE_SERVER_UPDATE", new VoiceServerUpdateInterceptor(this, (JDAImpl) event.getJDA())); + Map handlers = ((JDAImpl) event.getJDA()).getClient().getHandlers(); + handlers.put("VOICE_SERVER_UPDATE", new VoiceServerUpdateInterceptor(this, (JDAImpl) event.getJDA())); + handlers.put("VOICE_STATE_UPDATE", new VoiceStateUpdateInterceptor(this, (JDAImpl) event.getJDA())); + } + + @Override + public void onGuildLeave(GuildLeaveEvent event) { + Link link = links.get(event.getGuild().getId()); + if (link == null) return; + + ((JDAImpl) event.getJDA()).getClient().removeAudioConnection(link.getGuildIdLong()); + link.destroy(); + } + + @Override + public void onVoiceChannelDelete(VoiceChannelDeleteEvent event) { + Link link = links.get(event.getGuild().getId()); + if (link == null || !event.getChannel().equals(link.getChannel())) return; + + link.disconnect(); } @Override @@ -177,6 +200,8 @@ public void onReconnect(ReconnectedEvent event) { reconnectVoiceConnections(event.getJDA()); } + /* Util */ + private void reconnectVoiceConnections(JDA jda) { if (autoReconnect) { links.forEach((guildId, link) -> { diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index d5ba475bd..22b5aef90 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -111,6 +111,7 @@ public void disconnect() { if (g == null) return; setState(State.DISCONNECTING); + channel = null; getMainWs().queueAudioDisconnect(g); } @@ -192,6 +193,13 @@ private WebSocketClient getMainWs() { return ((JDAImpl) getJda()).getClient(); } + /** + * Setter used by {@link VoiceStateUpdateInterceptor} to change the expected channel + */ + void setChannel(@Nonnull VoiceChannel channel) { + this.channel = channel.getId(); + } + @Override public String toString() { return "Link{" + diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java index 4e9104dc0..60a04083a 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java @@ -42,7 +42,7 @@ public class VoiceServerUpdateInterceptor extends SocketHandler { @Override protected Long handleInternally(JSONObject content) { - log.info(content.toString()); + log.debug(content.toString()); long idLong = content.getLong("guild_id"); if (api.getGuildLock().isLocked(idLong)) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java new file mode 100644 index 000000000..125efe312 --- /dev/null +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java @@ -0,0 +1,66 @@ +package lavalink.client.io; + +import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.GuildVoiceState; +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.VoiceChannel; +import net.dv8tion.jda.core.entities.impl.JDAImpl; +import net.dv8tion.jda.core.handle.VoiceStateUpdateHandler; +import org.json.JSONObject; + +import java.util.Objects; + +public class VoiceStateUpdateInterceptor extends VoiceStateUpdateHandler { + + private final Lavalink lavalink; + + public VoiceStateUpdateInterceptor(Lavalink lavalink, JDAImpl jda) { + super(jda); + this.lavalink = lavalink; + } + + @Override + protected Long handleInternally(JSONObject content) { + final Long guildId = content.has("guild_id") ? content.getLong("guild_id") : null; + if (guildId != null && api.getGuildLock().isLocked(guildId)) + return guildId; + if (guildId == null) + return super.handleInternally(content); + + final long userId = content.getLong("user_id"); + final Long channelId = !content.isNull("channel_id") ? content.getLong("channel_id") : null; + Guild guild = api.getGuildById(guildId); + if (guild == null) return super.handleInternally(content); + + Member member = guild.getMemberById(userId); + if (member == null) return super.handleInternally(content); + + // We only need special handling if our own state is modified + if (!member.equals(guild.getSelfMember())) return super.handleInternally(content); + + + VoiceChannel channel = channelId != null ? guild.getVoiceChannelById(channelId) : null; + GuildVoiceState vState = member.getVoiceState(); + VoiceChannel oldChannel = vState.getChannel(); + Link link = lavalink.getLink(guildId.toString()); + + if (channelId == null) { + link.disconnect(); // Null channel means disconnected + } else if (channel != null) { + link.setChannel(channel); // Change expected channel + } + + if (link.getState() == Link.State.CONNECTED) { + + api.getClient().updateAudioConnection(guildId, channel); + } + + // Handle super + Long result = super.handleInternally(content); + if (!Objects.equals(channel, oldChannel)) { + + } + + return result; + } +} From 2b3c2b80d93886e663e96ccc9dccd5dd70470a4b Mon Sep 17 00:00:00 2001 From: Frederikam Date: Wed, 17 Jan 2018 09:41:55 +0100 Subject: [PATCH 15/50] Handle voice server update properly --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 2 +- .../lavalink/client/io/VoiceServerUpdateInterceptor.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 22b5aef90..7accb3fd7 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -175,7 +175,7 @@ public State getState() { return state; } - private void setState(@Nonnull State state) { + void setState(@Nonnull State state) { if (this.state == State.DESTROYED && state != State.DESTROYED) throw new IllegalStateException("Cannot change state to " + state + " when state is " + State.DESTROYED); diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java index 60a04083a..ca46b83fc 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java @@ -60,8 +60,11 @@ protected Long handleInternally(JSONObject content) { json.put("sessionId", sessionId); json.put("guildId", guild.getId()); json.put("event", content); + + Link link = lavalink.getLink(guild); //noinspection ConstantConditions - lavalink.getLink(guild).getNode(true).send(json.toString()); + link.getNode(true).send(json.toString()); + link.setState(Link.State.CONNECTED); log.info("Sent voice update for guild {}", idLong); From f98a5a259588392b1bf413fd9a94469d4b166a31 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Wed, 17 Jan 2018 09:43:34 +0100 Subject: [PATCH 16/50] Cleanup --- .../client/io/VoiceServerUpdateInterceptor.java | 2 -- .../client/io/VoiceStateUpdateInterceptor.java | 10 ++-------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java index ca46b83fc..7b148f1bf 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java @@ -66,8 +66,6 @@ protected Long handleInternally(JSONObject content) { link.getNode(true).send(json.toString()); link.setState(Link.State.CONNECTED); - log.info("Sent voice update for guild {}", idLong); - return null; } } diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java index 125efe312..1325942fa 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java @@ -14,7 +14,7 @@ public class VoiceStateUpdateInterceptor extends VoiceStateUpdateHandler { private final Lavalink lavalink; - public VoiceStateUpdateInterceptor(Lavalink lavalink, JDAImpl jda) { + VoiceStateUpdateInterceptor(Lavalink lavalink, JDAImpl jda) { super(jda); this.lavalink = lavalink; } @@ -55,12 +55,6 @@ protected Long handleInternally(JSONObject content) { api.getClient().updateAudioConnection(guildId, channel); } - // Handle super - Long result = super.handleInternally(content); - if (!Objects.equals(channel, oldChannel)) { - - } - - return result; + return super.handleInternally(content); } } From 8afdcf1a86e9e3f4c0fbceb999c464bfffc7d602 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Wed, 17 Jan 2018 09:45:45 +0100 Subject: [PATCH 17/50] Added change node handling --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 7accb3fd7..f6ff1933f 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -53,6 +53,7 @@ public class Link { private volatile LavalinkSocket node = null; /* May only be set by setState() */ private volatile State state = State.NOT_CONNECTED; + private volatile boolean reconnectToNewNode = false; Link(Lavalink lavalink, String guildId) { this.lavalink = lavalink; @@ -119,7 +120,7 @@ public void changeNode(LavalinkSocket newNode) { disconnect(); node = newNode; connect(getJda().getVoiceChannelById(channel)); - // TODO: Handle properly + reconnectToNewNode = true; } /** @@ -181,6 +182,11 @@ void setState(@Nonnull State state) { log.debug("Link {} changed state from {} to {}", this, this.state, state); this.state = state; + + if (state == State.DISCONNECTING && reconnectToNewNode) { + reconnectToNewNode = false; + connect(getJda().getVoiceChannelById(channel)); + } } @SuppressWarnings("WeakerAccess") From 1bacb916420b7ab126cad890f70b74238c093fec Mon Sep 17 00:00:00 2001 From: Frederikam Date: Wed, 17 Jan 2018 09:49:09 +0100 Subject: [PATCH 18/50] Destroy method sends destroy op --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index f6ff1933f..c89c7c25b 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -33,6 +33,7 @@ import net.dv8tion.jda.core.exceptions.GuildUnavailableException; import net.dv8tion.jda.core.exceptions.InsufficientPermissionException; import net.dv8tion.jda.core.requests.WebSocketClient; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -133,6 +134,13 @@ public void destroy() { setState(State.DESTROYED); disconnect(); lavalink.removeDestroyedLink(this); + LavalinkSocket socket = getNode(false); + if (socket != null) { + socket.send(new JSONObject() + .put("op", "destroy") + .put("guildId", guild) + .toString()); + } } /** From a35258f6cbd61aa4145673939105c7ec2397419e Mon Sep 17 00:00:00 2001 From: Repulser Date: Wed, 17 Jan 2018 11:37:58 +0200 Subject: [PATCH 19/50] print args --- .../main/java/lavalink/server/io/ConnectionManagerImpl.java | 2 +- .../src/main/java/lavalink/server/io/CoreClientImpl.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index 2c670cdf1..bdb3e8715 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -25,7 +25,7 @@ public void removeAudioConnection(String guildId) { @Override public void queueAudioConnect(String guildId, String channelId) { - log.warn("queueAudioConnect was requested, this shouldn't happen"); + log.warn("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); } @Override diff --git a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java index 54450603b..191e14a8a 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java @@ -43,19 +43,19 @@ public boolean isConnected() { @Override public boolean inGuild(String guildId) { - log.warn("inGuild was requested, this shouldn't happen"); + log.warn("inGuild was requested, this shouldn't happen, guildId:" + guildId); return true; } @Override public boolean voiceChannelExists(String guildId, String channelId) { - log.warn("voiceChannelExists was requested, this shouldn't happen"); + log.warn("voiceChannelExists was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); return true; } @Override public boolean hasPermissionInChannel(String guildId, String channelId, long l) { - log.warn("hasPermissionInChannel was requested, this shouldn't happen"); + log.warn("hasPermissionInChannel was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId, " l:" + l); return true; } From 535fb2a75920e8c6aa307ea96eabf986445cbd38 Mon Sep 17 00:00:00 2001 From: Repulser Date: Wed, 17 Jan 2018 16:10:39 +0200 Subject: [PATCH 20/50] Dep bump --- LavalinkClient/build.gradle | 6 +- LavalinkServer/build.gradle | 12 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 168 +++++++++++------------ 5 files changed, 94 insertions(+), 96 deletions(-) diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index adb4ca612..65a518497 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -5,10 +5,10 @@ ext { } dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.4' + compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'org.json', name: 'json', version: '20170516' - compile group: 'net.dv8tion', name: 'JDA', version: '3.3.1_307' + compile group: 'org.json', name: 'json', version: '20171018' + compile group: 'net.dv8tion', name: 'JDA', version: '3.5.0_328' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.0.0-M4' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.0.0-M4' testCompile group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.0.0-M4' diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index c42031ef4..8ded0b676 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -20,14 +20,12 @@ dependencies { compile group: 'com.github.Repulser', name: 'JDA-Audio', version: 'f9b06ee0e4c1c0cdd9c801c6023fcfed8beb34cb' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' - compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.4' + compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'io.sentry', name: 'sentry-logback', version: '1.5.3' - compile group: 'com.github.oshi', name: 'oshi-core', version: '3.4.3' - compile group: 'org.json', name: 'json', version: '20170516' - compile group: 'com.google.guava', name: 'guava', version: '22.0' + compile group: 'io.sentry', name: 'sentry-logback', version: '1.6.4' + compile group: 'com.github.oshi', name: 'oshi-core', version: '3.4.4' + compile group: 'org.json', name: 'json', version: '20171018' + compile group: 'com.google.guava', name: 'guava', version: '23.0' compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.9.RELEASE' - compile group: 'org.springframework.boot', name: 'spring-boot-autoconfigure', version: '1.5.9.RELEASE' - compile group: 'org.springframework', name: 'spring-web', version: '4.3.9.RELEASE' } diff --git a/build.gradle b/build.gradle index e6fcf345e..c37f11784 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,6 @@ ext { import org.gradle.api.tasks.wrapper.Wrapper.DistributionType task wrapper(type: Wrapper) { - gradleVersion = '4.2.1' + gradleVersion = '4.4.1' distributionType = DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c583957d2..57c7d2d22 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/gradlew.bat b/gradlew.bat index f9553162f..e95643d6a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,84 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 5ab5efd8ddbaef50d335864b5b5917f53a8c635c Mon Sep 17 00:00:00 2001 From: Repulser Date: Wed, 17 Jan 2018 16:11:42 +0200 Subject: [PATCH 21/50] log message in sendWS --- .../src/main/java/lavalink/server/io/CoreClientImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java index 191e14a8a..555951813 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java @@ -32,7 +32,7 @@ public class CoreClientImpl implements CoreClient { @Override public void sendWS(String message) { - log.warn("sendWS was requested, this shouldn't happen"); + log.warn("sendWS was requested, this shouldn't happen, message:" + message); } @Override From 42f80c79310ac131e4fd7f93d309cb78b6b9cc79 Mon Sep 17 00:00:00 2001 From: Repulser Date: Wed, 17 Jan 2018 16:29:18 +0200 Subject: [PATCH 22/50] Fix usage of old method --- LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java b/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java index 8081e65cf..05068f392 100644 --- a/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java +++ b/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java @@ -65,7 +65,7 @@ public static AudioTrack toAudioTrack(String message) throws IOException { public static String toMessage(AudioTrack track) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PLAYER_MANAGER.encodeTrack(new MessageOutput(baos), track); - return new String(Base64.encodeBytesToBytes(baos.toByteArray())); + return Base64.encodeBytes(baos.toByteArray()); } public static int getShardFromSnowflake(String snowflake, int numShards) { From f74b832723f9e881d15f53b1fb37c293936a34b3 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Wed, 17 Jan 2018 16:52:20 +0100 Subject: [PATCH 23/50] Add user limit check to Link#connect() --- .../src/main/java/lavalink/client/io/Link.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index c89c7c25b..f405ef821 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -33,6 +33,7 @@ import net.dv8tion.jda.core.exceptions.GuildUnavailableException; import net.dv8tion.jda.core.exceptions.InsufficientPermissionException; import net.dv8tion.jda.core.requests.WebSocketClient; +import net.dv8tion.jda.core.utils.PermissionUtil; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -103,6 +104,21 @@ public void connect(VoiceChannel channel) { if (!self.hasPermission(channel, Permission.VOICE_CONNECT) && !self.hasPermission(channel, Permission.VOICE_MOVE_OTHERS)) throw new InsufficientPermissionException(Permission.VOICE_CONNECT); + //If we are already connected to this VoiceChannel, then do nothing. + if (channel.equals(channel.getGuild().getSelfMember().getVoiceState().getChannel())) + return; + + final int userLimit = channel.getUserLimit(); // userLimit is 0 if no limit is set! + if (!self.isOwner() && !self.hasPermission(Permission.ADMINISTRATOR)) { + final long perms = PermissionUtil.getExplicitPermission(channel, self); + final long voicePerm = Permission.VOICE_MOVE_OTHERS.getRawValue(); + if (userLimit > 0 // If there is a userlimit + && userLimit <= channel.getMembers().size() // if that userlimit is reached + && (perms & voicePerm) != voicePerm) // If we don't have voice move others permissions + throw new InsufficientPermissionException(Permission.VOICE_MOVE_OTHERS, // then throw exception! + "Unable to connect to VoiceChannel due to userlimit! Requires permission VOICE_MOVE_OTHERS to bypass"); + } + setState(State.CONNECTING); getMainWs().queueAudioConnect(channel); } From c60d979da698f68401d1b2fe8d2a3f39f96df0fd Mon Sep 17 00:00:00 2001 From: Repulser Date: Wed, 17 Jan 2018 18:09:28 +0200 Subject: [PATCH 24/50] Server side magic :) (#54) * Initial commit * Fix * Fixups * destroy op * extra warnings + update JDAA * print args * Dep bump * log message in sendWS * Fix usage of old method --- LavalinkClient/build.gradle | 6 +- .../java/lavalink/client/LavalinkUtil.java | 2 +- LavalinkServer/build.gradle | 14 +- .../server/io/ConnectionManagerImpl.java | 39 ++++ .../lavalink/server/io/CoreClientImpl.java | 148 +-------------- .../lavalink/server/io/SocketContext.java | 6 +- .../java/lavalink/server/io/SocketServer.java | 30 +--- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 168 +++++++++--------- 10 files changed, 151 insertions(+), 266 deletions(-) create mode 100644 LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index 1eb8216a2..65a518497 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -5,10 +5,10 @@ ext { } dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.4' + compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'org.json', name: 'json', version: '20170516' - compile group: 'net.dv8tion', name: 'JDA', version: '3.5.0_327' + compile group: 'org.json', name: 'json', version: '20171018' + compile group: 'net.dv8tion', name: 'JDA', version: '3.5.0_328' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.0.0-M4' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.0.0-M4' testCompile group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.0.0-M4' diff --git a/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java b/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java index 8081e65cf..05068f392 100644 --- a/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java +++ b/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java @@ -65,7 +65,7 @@ public static AudioTrack toAudioTrack(String message) throws IOException { public static String toMessage(AudioTrack track) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PLAYER_MANAGER.encodeTrack(new MessageOutput(baos), track); - return new String(Base64.encodeBytesToBytes(baos.toByteArray())); + return Base64.encodeBytes(baos.toByteArray()); } public static int getShardFromSnowflake(String snowflake, int numShards) { diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 981c7c8a0..8ded0b676 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,17 +17,15 @@ publishToMavenLocal.dependsOn 'bootRepackage' dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: '7a3f9d40a32b74b6e85e0e0c8f9a051f004b5584' + compile group: 'com.github.Repulser', name: 'JDA-Audio', version: 'f9b06ee0e4c1c0cdd9c801c6023fcfed8beb34cb' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' - compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.4' + compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'io.sentry', name: 'sentry-logback', version: '1.5.3' - compile group: 'com.github.oshi', name: 'oshi-core', version: '3.4.3' - compile group: 'org.json', name: 'json', version: '20170516' - compile group: 'com.google.guava', name: 'guava', version: '22.0' + compile group: 'io.sentry', name: 'sentry-logback', version: '1.6.4' + compile group: 'com.github.oshi', name: 'oshi-core', version: '3.4.4' + compile group: 'org.json', name: 'json', version: '20171018' + compile group: 'com.google.guava', name: 'guava', version: '23.0' compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.9.RELEASE' - compile group: 'org.springframework.boot', name: 'spring-boot-autoconfigure', version: '1.5.9.RELEASE' - compile group: 'org.springframework', name: 'spring-web', version: '4.3.9.RELEASE' } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java new file mode 100644 index 000000000..bdb3e8715 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -0,0 +1,39 @@ +package lavalink.server.io; + +import net.dv8tion.jda.manager.ConnectionManager; +import org.java_websocket.WebSocket; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by Repulser + * https://github.com/Repulser + */ +public class ConnectionManagerImpl implements ConnectionManager { + private static final Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class); + private final WebSocket socket; + + + ConnectionManagerImpl(WebSocket socket) { + this.socket = socket; + } + + @Override + public void removeAudioConnection(String guildId) { + } + + @Override + public void queueAudioConnect(String guildId, String channelId) { + log.warn("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); + } + + @Override + public void onDisconnect(String guildId) { + JSONObject obj = new JSONObject() + .put("op", "disconnected") + .put("guildId", guildId); + socket.send(obj.toString()); + } + +} diff --git a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java index a6b85c58b..555951813 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java @@ -22,171 +22,41 @@ package lavalink.server.io; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import lavalink.server.util.ResetableCountDownLatch; import net.dv8tion.jda.CoreClient; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.java_websocket.WebSocket; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.TimeUnit; - public class CoreClientImpl implements CoreClient { private static final Logger log = LoggerFactory.getLogger(CoreClientImpl.class); - private static final int TIMEOUT = 60 * 1000; - - private final WebSocket socket; - private int shardId; - - private final ResetableCountDownLatch validationLatch = new ResetableCountDownLatch(1); - private final ResetableCountDownLatch isConnectedLatch = new ResetableCountDownLatch(1); - private boolean connected = false; - - private LoadingCache guildValidMap = CacheBuilder.newBuilder() - .concurrencyLevel(4) - .expireAfterWrite(5, TimeUnit.SECONDS) - .build( - new CacheLoader() { - @Override - public Boolean load(@SuppressWarnings("NullableProblems") String guild) throws Exception { - return requestValidationSync(guild, null); - } - } - ); - - private LoadingCache, Boolean> channelValidMap = CacheBuilder.newBuilder() - .concurrencyLevel(4) - .expireAfterWrite(5, TimeUnit.SECONDS) - .build( - new CacheLoader, Boolean>() { - @Override - public Boolean load(@SuppressWarnings("NullableProblems") ImmutablePair key) throws Exception { - return requestValidationSync(key.left, key.right); - } - } - ); - - - CoreClientImpl(WebSocket socket, int shardId) { - this.socket = socket; - this.shardId = shardId; - } @Override public void sendWS(String message) { - log.info(message); - JSONObject json = new JSONObject(); - json.put("op", "sendWS"); - json.put("shardId", shardId); - json.put("message", message); - socket.send(json.toString()); + log.warn("sendWS was requested, this shouldn't happen, message:" + message); } @Override public boolean isConnected() { - return requestIsConnectedSync(); + log.warn("isConnected was requested, this shouldn't happen"); + return true; } @Override public boolean inGuild(String guildId) { - log.info("Requested guild check"); - boolean val = guildValidMap.getUnchecked(guildId); - if (!val) { - log.warn("Requested guild check but validation was false!"); - } - return val; + log.warn("inGuild was requested, this shouldn't happen, guildId:" + guildId); + return true; } @Override public boolean voiceChannelExists(String guildId, String channelId) { - log.info("Requested channel check"); - boolean val = channelValidMap.getUnchecked(new ImmutablePair<>(guildId, channelId)); - if (!val) { - log.warn("Requested channel check but validation was false!"); - } - return val; + log.warn("voiceChannelExists was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); + return true; } @Override public boolean hasPermissionInChannel(String guildId, String channelId, long l) { - log.info("Requested permission check"); - boolean val = channelValidMap.getUnchecked(new ImmutablePair<>(guildId, channelId)); - if (!val) { - log.warn("Requested permission check but validation was false!"); - } - return val; - } - - - - private boolean requestValidationSync(String guildId, String channelId) { - JSONObject json = new JSONObject(); - json.put("op", "validationReq"); - json.put("guildId", guildId); - - if (channelId != null) { - json.put("channelId", channelId); - } - - long startTime = System.currentTimeMillis(); - socket.send(json.toString()); - - try { - validationLatch.await(TIMEOUT, TimeUnit.MILLISECONDS); - validationLatch.reset(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - if (System.currentTimeMillis() - startTime >= TIMEOUT) { - log.error("Validation timed out after " + TIMEOUT + " millis"); - return false; - } - - return channelId == null - ? guildValidMap.getIfPresent(guildId) == Boolean.TRUE - : channelValidMap.getIfPresent(new ImmutablePair<>(guildId, channelId)) == Boolean.TRUE; - } - - void provideValidation(String guildId, String channelId, boolean valid) { - guildValidMap.put(guildId, valid); - if (channelId != null) - channelValidMap.put(new ImmutablePair<>(guildId, channelId), valid); - - validationLatch.countDown(); - } - - private boolean requestIsConnectedSync() { - JSONObject json = new JSONObject(); - json.put("op", "isConnectedReq"); - json.put("shardId", shardId); - - long startTime = System.currentTimeMillis(); - socket.send(json.toString()); - - try { - isConnectedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS); - isConnectedLatch.reset(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - if (System.currentTimeMillis() - startTime >= TIMEOUT) { - throw new RuntimeException("Connection checking timed out after " + TIMEOUT + " millis"); - } - - return connected; - } - - void provideIsConnected(boolean connected) { - this.connected = connected; - - isConnectedLatch.countDown(); + log.warn("hasPermissionInChannel was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId, " l:" + l); + return true; } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java index fdbbb5c52..c533958ce 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java @@ -30,6 +30,8 @@ import net.dv8tion.jda.Core; import net.dv8tion.jda.audio.factory.IAudioSendFactory; import net.dv8tion.jda.manager.AudioManager; +import net.dv8tion.jda.manager.ConnectionManager; +import net.dv8tion.jda.manager.ConnectionManagerBuilder; import org.java_websocket.WebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,9 +80,9 @@ Core getCore(int shardId) { return cores.computeIfAbsent(shardId, __ -> { if (nasSupported) - return new Core(userId, new CoreClientImpl(socket, shardId), getAudioSendFactory(shardId)); + return new Core(userId, new CoreClientImpl(), core -> new ConnectionManagerImpl(socket), getAudioSendFactory(shardId)); else - return new Core(userId, new CoreClientImpl(socket, shardId)); + return new Core(userId, new CoreClientImpl(), (ConnectionManagerBuilder) core -> new ConnectionManagerImpl(socket)); } ); } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java index fe2629312..b0ea77621 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java @@ -26,9 +26,7 @@ import com.sedmelluq.discord.lavaplayer.track.TrackMarker; import lavalink.server.player.Player; import lavalink.server.player.TrackEndMarkerHandler; -import lavalink.server.util.DebugConnectionListener; import lavalink.server.util.Util; -import net.dv8tion.jda.manager.AudioManager; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; @@ -113,37 +111,12 @@ public void onMessage(WebSocket webSocket, String s) { switch (json.getString("op")) { /* JDAA ops */ - case "connect": - long guildId = Long.parseLong(json.getString("guildId")); - AudioManager manager = contextMap.get(webSocket).getCore(getShardId(webSocket, json)) - .getAudioManager(json.getString("guildId")); - if (manager.getConnectionListener() == null) { - manager.setConnectionListener(new DebugConnectionListener(guildId)); - } - manager.openAudioConnection(json.getString("channelId")); - break; case "voiceUpdate": contextMap.get(webSocket).getCore(getShardId(webSocket, json)).provideVoiceServerUpdate( json.getString("sessionId"), json.getJSONObject("event") ); break; - case "disconnect": - contextMap.get(webSocket).getCore(getShardId(webSocket, json)).getAudioManager(json.getString("guildId")) - .closeAudioConnection(); - break; - case "validationRes": - ((CoreClientImpl) contextMap.get(webSocket).getCore(getShardId(webSocket, json)).getClient()).provideValidation( - json.getString("guildId"), - json.optString("channelId"), - json.getBoolean("valid") - ); - break; - case "isConnectedRes": - ((CoreClientImpl) contextMap.get(webSocket).getCore(json.getInt("shardId")).getClient()).provideIsConnected( - json.getBoolean("connected") - ); - break; /* Player ops */ case "play": @@ -188,6 +161,9 @@ public void onMessage(WebSocket webSocket, String s) { Player player4 = contextMap.get(webSocket).getPlayer(json.getString("guildId")); player4.setVolume(json.getInt("volume")); break; + case "destroy": + Player player5 = contextMap.get(webSocket).getPlayers().remove(json.getString("guildId")); + if (player5 != null) player5.stop(); default: log.warn("Unexpected operation: " + json.getString("op")); break; diff --git a/build.gradle b/build.gradle index e6fcf345e..c37f11784 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,6 @@ ext { import org.gradle.api.tasks.wrapper.Wrapper.DistributionType task wrapper(type: Wrapper) { - gradleVersion = '4.2.1' + gradleVersion = '4.4.1' distributionType = DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c583957d2..57c7d2d22 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/gradlew.bat b/gradlew.bat index f9553162f..e95643d6a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,84 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From bf214b06ac21964c34aac34ee543ff92622f32f1 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Wed, 17 Jan 2018 17:13:09 +0100 Subject: [PATCH 25/50] Fix runtime exception for destroying link --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 2 +- .../java/lavalink/client/io/VoiceStateUpdateInterceptor.java | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index f405ef821..8d8d1e2a4 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -147,8 +147,8 @@ public void changeNode(LavalinkSocket newNode) { */ @SuppressWarnings("unused") public void destroy() { - setState(State.DESTROYED); disconnect(); + setState(State.DESTROYED); lavalink.removeDestroyedLink(this); LavalinkSocket socket = getNode(false); if (socket != null) { diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java index 1325942fa..b5195dc64 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java @@ -38,10 +38,7 @@ protected Long handleInternally(JSONObject content) { // We only need special handling if our own state is modified if (!member.equals(guild.getSelfMember())) return super.handleInternally(content); - VoiceChannel channel = channelId != null ? guild.getVoiceChannelById(channelId) : null; - GuildVoiceState vState = member.getVoiceState(); - VoiceChannel oldChannel = vState.getChannel(); Link link = lavalink.getLink(guildId.toString()); if (channelId == null) { @@ -51,7 +48,6 @@ protected Long handleInternally(JSONObject content) { } if (link.getState() == Link.State.CONNECTED) { - api.getClient().updateAudioConnection(guildId, channel); } From e5fd982708e6dc9e1638f495a3b56afa7b5f9d68 Mon Sep 17 00:00:00 2001 From: Repulser Date: Thu, 18 Jan 2018 17:18:18 +0200 Subject: [PATCH 26/50] Fixes and enchancements --- .../java/lavalink/client/LavalinkUtil.java | 1 - .../java/lavalink/client/io/Lavalink.java | 56 ------------------- .../main/java/lavalink/client/io/Link.java | 3 +- .../io/VoiceStateUpdateInterceptor.java | 2 - LavalinkServer/build.gradle | 2 +- .../server/io/ConnectionManagerImpl.java | 15 +---- .../lavalink/server/io/CoreClientImpl.java | 14 ++--- .../lavalink/server/io/SocketContext.java | 5 +- .../java/lavalink/server/io/SocketServer.java | 5 +- .../server/player/AudioLoaderRestHandler.java | 6 +- 10 files changed, 20 insertions(+), 89 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java b/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java index 05068f392..c1beb0353 100644 --- a/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java +++ b/LavalinkClient/src/main/java/lavalink/client/LavalinkUtil.java @@ -45,7 +45,6 @@ public class LavalinkUtil { static { PLAYER_MANAGER = new DefaultAudioPlayerManager(); - PLAYER_MANAGER.enableGcMonitoring(); /* These are only to encode/decode messages */ PLAYER_MANAGER.registerSourceManager(new YoutubeAudioSourceManager()); diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index 7be7a9372..ce7eb22cd 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -23,24 +23,14 @@ package lavalink.client.io; import lavalink.client.LavalinkUtil; -import lavalink.client.player.LavalinkPlayer; import net.dv8tion.jda.core.JDA; import net.dv8tion.jda.core.entities.Guild; -import net.dv8tion.jda.core.entities.VoiceChannel; import net.dv8tion.jda.core.entities.impl.JDAImpl; -import net.dv8tion.jda.core.events.DisconnectEvent; import net.dv8tion.jda.core.events.ReadyEvent; -import net.dv8tion.jda.core.events.ReconnectedEvent; -import net.dv8tion.jda.core.events.ResumedEvent; -import net.dv8tion.jda.core.events.ShutdownEvent; import net.dv8tion.jda.core.events.channel.voice.VoiceChannelDeleteEvent; import net.dv8tion.jda.core.events.guild.GuildLeaveEvent; -import net.dv8tion.jda.core.events.guild.voice.GuildVoiceJoinEvent; -import net.dv8tion.jda.core.events.guild.voice.GuildVoiceLeaveEvent; -import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent; import net.dv8tion.jda.core.handle.SocketHandler; import net.dv8tion.jda.core.hooks.ListenerAdapter; -import net.dv8tion.jda.core.requests.WebSocketClient; import org.java_websocket.drafts.Draft_6455; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,7 +52,6 @@ public class Lavalink extends ListenerAdapter { private static final Logger log = LoggerFactory.getLogger(Lavalink.class); - private boolean autoReconnect = true; private final int numShards; private final Function jdaProvider; private final ConcurrentHashMap links = new ConcurrentHashMap<>(); @@ -85,16 +74,6 @@ public Lavalink(String userId, int numShards, Function jdaProvider reconnectService.scheduleWithFixedDelay(new ReconnectTask(this), 0, 500, TimeUnit.MILLISECONDS); } - @SuppressWarnings("unused") - public void setAutoReconnect(boolean autoReconnect) { - this.autoReconnect = autoReconnect; - } - - @SuppressWarnings("unused") - public boolean getAutoReconnect() { - return autoReconnect; - } - public void addNode(URI serverUri, String password) { HashMap headers = new HashMap<>(); headers.put("Authorization", password); @@ -195,39 +174,4 @@ public void onVoiceChannelDelete(VoiceChannelDeleteEvent event) { link.disconnect(); } - @Override - public void onReconnect(ReconnectedEvent event) { - reconnectVoiceConnections(event.getJDA()); - } - - /* Util */ - - private void reconnectVoiceConnections(JDA jda) { - if (autoReconnect) { - links.forEach((guildId, link) -> { - try { - //Note: We also ensure that the link belongs to the JDA object - if (link.getChannel() != null - && jda.getGuildById(guildId) != null) { - link.connect(link.getChannel()); - } - } catch (Exception e) { - log.error("Caught exception while trying to reconnect link " + link, e); - } - }); - } - } - - private void disconnectVoiceConnections(JDA jda) { - links.forEach((guildId, link) -> { - try { - if (jda.getGuildById(guildId) != null) { - link.disconnect(); - } - } catch (Exception e) { - log.error("Caught exception while trying to disconnect link " + link, e); - } - }); - } - } diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index c89c7c25b..9a4de26ee 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -29,7 +29,6 @@ import net.dv8tion.jda.core.entities.Member; import net.dv8tion.jda.core.entities.VoiceChannel; import net.dv8tion.jda.core.entities.impl.JDAImpl; -import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent; import net.dv8tion.jda.core.exceptions.GuildUnavailableException; import net.dv8tion.jda.core.exceptions.InsufficientPermissionException; import net.dv8tion.jda.core.requests.WebSocketClient; @@ -138,7 +137,7 @@ public void destroy() { if (socket != null) { socket.send(new JSONObject() .put("op", "destroy") - .put("guildId", guild) + .put("guildId", Long.toString(guild)) .toString()); } } diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java index 1325942fa..afebb34b9 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java @@ -8,8 +8,6 @@ import net.dv8tion.jda.core.handle.VoiceStateUpdateHandler; import org.json.JSONObject; -import java.util.Objects; - public class VoiceStateUpdateInterceptor extends VoiceStateUpdateHandler { private final Lavalink lavalink; diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 8ded0b676..add09c85f 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,7 +17,7 @@ publishToMavenLocal.dependsOn 'bootRepackage' dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'com.github.Repulser', name: 'JDA-Audio', version: 'f9b06ee0e4c1c0cdd9c801c6023fcfed8beb34cb' + compile group: 'com.github.Repulser', name: 'JDA-Audio', version: '339cc176f881ecf60eaf4d99ad90d36bf9ac5ea2' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index bdb3e8715..d452c16b2 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -1,8 +1,6 @@ package lavalink.server.io; import net.dv8tion.jda.manager.ConnectionManager; -import org.java_websocket.WebSocket; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,12 +10,6 @@ */ public class ConnectionManagerImpl implements ConnectionManager { private static final Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class); - private final WebSocket socket; - - - ConnectionManagerImpl(WebSocket socket) { - this.socket = socket; - } @Override public void removeAudioConnection(String guildId) { @@ -25,15 +17,12 @@ public void removeAudioConnection(String guildId) { @Override public void queueAudioConnect(String guildId, String channelId) { - log.warn("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); + throw new RuntimeException("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); } @Override public void onDisconnect(String guildId) { - JSONObject obj = new JSONObject() - .put("op", "disconnected") - .put("guildId", guildId); - socket.send(obj.toString()); + throw new RuntimeException("onDisconnect was requested, this shouldn't happen, guildId:" + guildId); } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java index 555951813..75c13949d 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java @@ -32,31 +32,27 @@ public class CoreClientImpl implements CoreClient { @Override public void sendWS(String message) { - log.warn("sendWS was requested, this shouldn't happen, message:" + message); + throw new RuntimeException("sendWS was requested, this shouldn't happen, message:" + message); } @Override public boolean isConnected() { - log.warn("isConnected was requested, this shouldn't happen"); - return true; + throw new RuntimeException("isConnected was requested, this shouldn't happen"); } @Override public boolean inGuild(String guildId) { - log.warn("inGuild was requested, this shouldn't happen, guildId:" + guildId); - return true; + throw new RuntimeException("inGuild was requested, this shouldn't happen, guildId:" + guildId); } @Override public boolean voiceChannelExists(String guildId, String channelId) { - log.warn("voiceChannelExists was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); - return true; + throw new RuntimeException("voiceChannelExists was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); } @Override public boolean hasPermissionInChannel(String guildId, String channelId, long l) { - log.warn("hasPermissionInChannel was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId, " l:" + l); - return true; + throw new RuntimeException("hasPermissionInChannel was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId + " l:" + l); } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java index c533958ce..17e0bd364 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java @@ -30,7 +30,6 @@ import net.dv8tion.jda.Core; import net.dv8tion.jda.audio.factory.IAudioSendFactory; import net.dv8tion.jda.manager.AudioManager; -import net.dv8tion.jda.manager.ConnectionManager; import net.dv8tion.jda.manager.ConnectionManagerBuilder; import org.java_websocket.WebSocket; import org.slf4j.Logger; @@ -80,9 +79,9 @@ Core getCore(int shardId) { return cores.computeIfAbsent(shardId, __ -> { if (nasSupported) - return new Core(userId, new CoreClientImpl(), core -> new ConnectionManagerImpl(socket), getAudioSendFactory(shardId)); + return new Core(userId, new CoreClientImpl(), core -> new ConnectionManagerImpl(), getAudioSendFactory(shardId)); else - return new Core(userId, new CoreClientImpl(), (ConnectionManagerBuilder) core -> new ConnectionManagerImpl(socket)); + return new Core(userId, new CoreClientImpl(), (ConnectionManagerBuilder) core -> new ConnectionManagerImpl()); } ); } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java index b0ea77621..de273f683 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java @@ -27,6 +27,7 @@ import lavalink.server.player.Player; import lavalink.server.player.TrackEndMarkerHandler; import lavalink.server.util.Util; +import net.dv8tion.jda.Core; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; @@ -112,10 +113,12 @@ public void onMessage(WebSocket webSocket, String s) { switch (json.getString("op")) { /* JDAA ops */ case "voiceUpdate": - contextMap.get(webSocket).getCore(getShardId(webSocket, json)).provideVoiceServerUpdate( + Core core = contextMap.get(webSocket).getCore(getShardId(webSocket, json)); + core.provideVoiceServerUpdate( json.getString("sessionId"), json.getJSONObject("event") ); + core.getAudioManager(json.getJSONObject("event").getString("guild_id")).setAutoReconnect(false); break; /* Player ops */ diff --git a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java index 31b797813..838fb3bb4 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java +++ b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java @@ -31,7 +31,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; From ae32005ec9e3dcb417ef94bc6c3f2dd551d0b5c3 Mon Sep 17 00:00:00 2001 From: Repulser Date: Thu, 18 Jan 2018 21:16:14 +0200 Subject: [PATCH 27/50] Bring this back --- .../java/lavalink/client/io/Lavalink.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index ce7eb22cd..aaa117758 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -27,6 +27,7 @@ import net.dv8tion.jda.core.entities.Guild; import net.dv8tion.jda.core.entities.impl.JDAImpl; import net.dv8tion.jda.core.events.ReadyEvent; +import net.dv8tion.jda.core.events.ReconnectedEvent; import net.dv8tion.jda.core.events.channel.voice.VoiceChannelDeleteEvent; import net.dv8tion.jda.core.events.guild.GuildLeaveEvent; import net.dv8tion.jda.core.handle.SocketHandler; @@ -52,6 +53,7 @@ public class Lavalink extends ListenerAdapter { private static final Logger log = LoggerFactory.getLogger(Lavalink.class); + private boolean autoReconnect = true; private final int numShards; private final Function jdaProvider; private final ConcurrentHashMap links = new ConcurrentHashMap<>(); @@ -74,6 +76,17 @@ public Lavalink(String userId, int numShards, Function jdaProvider reconnectService.scheduleWithFixedDelay(new ReconnectTask(this), 0, 500, TimeUnit.MILLISECONDS); } + + @SuppressWarnings("unused") + public void setAutoReconnect(boolean autoReconnect) { + this.autoReconnect = autoReconnect; + } + + @SuppressWarnings("unused") + public boolean getAutoReconnect() { + return autoReconnect; + } + public void addNode(URI serverUri, String password) { HashMap headers = new HashMap<>(); headers.put("Authorization", password); @@ -174,4 +187,26 @@ public void onVoiceChannelDelete(VoiceChannelDeleteEvent event) { link.disconnect(); } + @Override + public void onReconnect(ReconnectedEvent event) { + reconnectVoiceConnections(event.getJDA()); + } + + + private void reconnectVoiceConnections(JDA jda) { + if (autoReconnect) { + links.forEach((guildId, link) -> { + try { + //Note: We also ensure that the link belongs to the JDA object + if (link.getChannel() != null + && jda.getGuildById(guildId) != null) { + link.connect(link.getChannel()); + } + } catch (Exception e) { + log.error("Caught exception while trying to reconnect link " + link, e); + } + }); + } + } + } From d83a788b7d618fc96dc1b83814c44bf3208ea206 Mon Sep 17 00:00:00 2001 From: Repulser Date: Thu, 18 Jan 2018 21:28:06 +0200 Subject: [PATCH 28/50] Re-design --- .../lavalink/server/io/ConnectionManagerImpl.java | 4 ++-- .../java/lavalink/server/io/CoreClientImpl.java | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index dda8a3543..8b10fb079 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -14,12 +14,12 @@ public void removeAudioConnection(String guildId) { @Override public void queueAudioConnect(String guildId, String channelId) { - throw new RuntimeException("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); + new RuntimeException("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId).printStackTrace(); } @Override public void onDisconnect(String guildId) { - throw new RuntimeException("onDisconnect was requested, this shouldn't happen, guildId:" + guildId); + new RuntimeException("onDisconnect was requested, this shouldn't happen, guildId:" + guildId).printStackTrace(); } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java index 957d27f83..a1e6e44dd 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java @@ -28,27 +28,31 @@ public class CoreClientImpl implements CoreClient { @Override public void sendWS(String message) { - throw new RuntimeException("sendWS was requested, this shouldn't happen, message:" + message); + new RuntimeException("sendWS was requested, this shouldn't happen, message:" + message).printStackTrace(); } @Override public boolean isConnected() { - throw new RuntimeException("isConnected was requested, this shouldn't happen"); + new RuntimeException("isConnected was requested, this shouldn't happen").printStackTrace(); + return true; } @Override public boolean inGuild(String guildId) { - throw new RuntimeException("inGuild was requested, this shouldn't happen, guildId:" + guildId); + new RuntimeException("inGuild was requested, this shouldn't happen, guildId:" + guildId).printStackTrace(); + return true; } @Override public boolean voiceChannelExists(String guildId, String channelId) { - throw new RuntimeException("voiceChannelExists was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId); + new RuntimeException("voiceChannelExists was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId).printStackTrace(); + return true; } @Override public boolean hasPermissionInChannel(String guildId, String channelId, long l) { - throw new RuntimeException("hasPermissionInChannel was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId + " l:" + l); + new RuntimeException("hasPermissionInChannel was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId + " l:" + l).printStackTrace(); + return true; } } From 31988479e15345a552183255a0fd472a69a431e6 Mon Sep 17 00:00:00 2001 From: Repulser Date: Thu, 18 Jan 2018 21:50:57 +0200 Subject: [PATCH 29/50] Redesign part 2 --- .../lavalink/server/io/ConnectionManagerImpl.java | 7 +++++-- .../java/lavalink/server/io/CoreClientImpl.java | 14 +++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index 8b10fb079..b18c4fa18 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -1,12 +1,15 @@ package lavalink.server.io; import net.dv8tion.jda.manager.ConnectionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Created by Repulser * https://github.com/Repulser */ public class ConnectionManagerImpl implements ConnectionManager { + private static final Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class); @Override public void removeAudioConnection(String guildId) { @@ -14,12 +17,12 @@ public void removeAudioConnection(String guildId) { @Override public void queueAudioConnect(String guildId, String channelId) { - new RuntimeException("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId).printStackTrace(); + log.warn("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId, new RuntimeException()); } @Override public void onDisconnect(String guildId) { - new RuntimeException("onDisconnect was requested, this shouldn't happen, guildId:" + guildId).printStackTrace(); + log.warn("onDisconnect was requested, this shouldn't happen, guildId:" + guildId, new RuntimeException()); } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java index a1e6e44dd..92dc5cdac 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java @@ -23,35 +23,39 @@ package lavalink.server.io; import net.dv8tion.jda.CoreClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class CoreClientImpl implements CoreClient { + private static final Logger log = LoggerFactory.getLogger(CoreClientImpl.class); + @Override public void sendWS(String message) { - new RuntimeException("sendWS was requested, this shouldn't happen, message:" + message).printStackTrace(); + log.warn("sendWS was requested, this shouldn't happen, message:" + message, new RuntimeException()); } @Override public boolean isConnected() { - new RuntimeException("isConnected was requested, this shouldn't happen").printStackTrace(); + log.warn("isConnected was requested, this shouldn't happen", new RuntimeException()); return true; } @Override public boolean inGuild(String guildId) { - new RuntimeException("inGuild was requested, this shouldn't happen, guildId:" + guildId).printStackTrace(); + log.warn("inGuild was requested, this shouldn't happen, guildId:" + guildId, new RuntimeException()); return true; } @Override public boolean voiceChannelExists(String guildId, String channelId) { - new RuntimeException("voiceChannelExists was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId).printStackTrace(); + log.warn("voiceChannelExists was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId, new RuntimeException()); return true; } @Override public boolean hasPermissionInChannel(String guildId, String channelId, long l) { - new RuntimeException("hasPermissionInChannel was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId + " l:" + l).printStackTrace(); + log.warn("hasPermissionInChannel was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId + " l:" + l, new RuntimeException()); return true; } From d928dcf38d36cd4e3f84a619b656409309d25b82 Mon Sep 17 00:00:00 2001 From: Repulser Date: Fri, 19 Jan 2018 14:28:38 +0200 Subject: [PATCH 30/50] Destroy link on voice channel delete --- LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index aaa117758..3f7ab7e7b 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -184,7 +184,7 @@ public void onVoiceChannelDelete(VoiceChannelDeleteEvent event) { Link link = links.get(event.getGuild().getId()); if (link == null || !event.getChannel().equals(link.getChannel())) return; - link.disconnect(); + link.destroy(); } @Override From 6c710530a82385438f54d8b5946995363bed409a Mon Sep 17 00:00:00 2001 From: Repulser Date: Fri, 19 Jan 2018 14:59:30 +0200 Subject: [PATCH 31/50] better way --- LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java | 2 +- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 4 +++- .../java/lavalink/client/io/VoiceStateUpdateInterceptor.java | 3 +++ .../src/main/java/lavalink/server/io/CoreClientImpl.java | 1 - 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index 3f7ab7e7b..aaa117758 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -184,7 +184,7 @@ public void onVoiceChannelDelete(VoiceChannelDeleteEvent event) { Link link = links.get(event.getGuild().getId()); if (link == null || !event.getChannel().equals(link.getChannel())) return; - link.destroy(); + link.disconnect(); } @Override diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index b3aa8a128..5e129b0ce 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -146,7 +146,9 @@ public void changeNode(LavalinkSocket newNode) { */ @SuppressWarnings("unused") public void destroy() { - disconnect(); + if (state != State.DISCONNECTING && state != State.NOT_CONNECTED) { + disconnect(); + } setState(State.DESTROYED); lavalink.removeDestroyedLink(this); LavalinkSocket socket = getNode(false); diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java index 2bfb6fd0f..ecf93a30d 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java @@ -40,6 +40,9 @@ protected Long handleInternally(JSONObject content) { if (channelId == null) { link.disconnect(); // Null channel means disconnected + if (link.getState() != Link.State.DESTROYED) { + link.setState(Link.State.NOT_CONNECTED); + } } else if (channel != null) { link.setChannel(channel); // Change expected channel } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java index 92dc5cdac..1cb045e46 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/CoreClientImpl.java @@ -32,7 +32,6 @@ public class CoreClientImpl implements CoreClient { @Override public void sendWS(String message) { - log.warn("sendWS was requested, this shouldn't happen, message:" + message, new RuntimeException()); } @Override From 3a9bfa11f3d57f85ba788a98e89625e8e14099ec Mon Sep 17 00:00:00 2001 From: Repulser Date: Fri, 19 Jan 2018 20:14:12 +0200 Subject: [PATCH 32/50] Remove disconnect call --- .../java/lavalink/client/io/VoiceStateUpdateInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java index ecf93a30d..bc4c36538 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceStateUpdateInterceptor.java @@ -39,7 +39,7 @@ protected Long handleInternally(JSONObject content) { Link link = lavalink.getLink(guildId.toString()); if (channelId == null) { - link.disconnect(); // Null channel means disconnected + // Null channel means disconnected if (link.getState() != Link.State.DESTROYED) { link.setState(Link.State.NOT_CONNECTED); } From 2e1a5c0827a6e270399c1f0aa9b11ed2e371c007 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Fri, 19 Jan 2018 22:57:35 +0100 Subject: [PATCH 33/50] remove disconnected op from implementation docs --- IMPLEMENTATION.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 63f739dd8..99435c9a9 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -84,15 +84,6 @@ See [LavalinkSocket.java](https://github.com/Frederikam/Lavalink/blob/dev/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java) for client implementation -Received when the voice connection is disconnected (and unable to resume). -The client must queue a new voice connection if it desires to reconnect. -```json -{ - "op": "disconnected", - "guildId": "...", -} -``` - Position information about a player. Includes unix timestamp. ```json { From ad434fecab6d9a98b5d859fc32f1f4cc42eb1f67 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sat, 20 Jan 2018 17:25:20 +0100 Subject: [PATCH 34/50] Don't set channel to null before disconnecting --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 5e129b0ce..dbeb31ba0 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -128,7 +128,6 @@ public void disconnect() { if (g == null) return; setState(State.DISCONNECTING); - channel = null; getMainWs().queueAudioDisconnect(g); } @@ -208,6 +207,10 @@ void setState(@Nonnull State state) { log.debug("Link {} changed state from {} to {}", this, this.state, state); this.state = state; + if (state == State.NOT_CONNECTED || state == State.DESTROYED) { + channel = null; + } + if (state == State.DISCONNECTING && reconnectToNewNode) { reconnectToNewNode = false; connect(getJda().getVoiceChannelById(channel)); From 2a1c33a0661bed74836f81ae9411ac26e7bb1252 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sat, 20 Jan 2018 17:27:49 +0100 Subject: [PATCH 35/50] Improve disconnect handling --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index dbeb31ba0..40de53cd6 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -207,13 +207,11 @@ void setState(@Nonnull State state) { log.debug("Link {} changed state from {} to {}", this, this.state, state); this.state = state; - if (state == State.NOT_CONNECTED || state == State.DESTROYED) { - channel = null; - } - - if (state == State.DISCONNECTING && reconnectToNewNode) { + if (state == State.NOT_CONNECTED && reconnectToNewNode) { reconnectToNewNode = false; connect(getJda().getVoiceChannelById(channel)); + } else if (state == State.NOT_CONNECTED || state == State.DESTROYED) { + channel = null; } } From edf5369f83101eba89ab80e10847c03e7b3073dd Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sat, 20 Jan 2018 17:32:17 +0100 Subject: [PATCH 36/50] Fix reconnectVoiceConnections --- .../src/main/java/lavalink/client/io/Lavalink.java | 2 +- .../src/main/java/lavalink/client/io/Link.java | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index aaa117758..7a8256044 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -200,7 +200,7 @@ private void reconnectVoiceConnections(JDA jda) { //Note: We also ensure that the link belongs to the JDA object if (link.getChannel() != null && jda.getGuildById(guildId) != null) { - link.connect(link.getChannel()); + link.connect(link.getLastChannel()); } } catch (Exception e) { log.error("Caught exception while trying to reconnect link " + link, e); diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 40de53cd6..62187c035 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -187,6 +187,17 @@ public LavalinkSocket getNode(boolean selectIfAbsent) { @SuppressWarnings("WeakerAccess") @Nullable public VoiceChannel getChannel() { + if (channel == null || state == State.DESTROYED || state == State.NOT_CONNECTED) return null; + + return getJda().getVoiceChannelById(channel); + } + + /** + * @return The channel we are currently connected to, or which we were connected to + */ + @SuppressWarnings("WeakerAccess") + @Nullable + VoiceChannel getLastChannel() { if (channel == null) return null; return getJda().getVoiceChannelById(channel); @@ -210,8 +221,6 @@ void setState(@Nonnull State state) { if (state == State.NOT_CONNECTED && reconnectToNewNode) { reconnectToNewNode = false; connect(getJda().getVoiceChannelById(channel)); - } else if (state == State.NOT_CONNECTED || state == State.DESTROYED) { - channel = null; } } From 772ff1eb18bbf997930572452865ef8598cc3dea Mon Sep 17 00:00:00 2001 From: Repulser Date: Sat, 20 Jan 2018 18:35:44 +0200 Subject: [PATCH 37/50] Remove unused ops --- .../lavalink/client/io/LavalinkSocket.java | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java b/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java index 4b49864da..42c82404b 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java @@ -88,54 +88,6 @@ public void onMessage(String message) { } switch (json.getString("op")) { - case "sendWS": - JDAImpl jda = (JDAImpl) lavalink.getJda(json.getInt("shardId")); - jda.getClient().send(json.getString("message")); - break; - case "validationReq": - int sId = LavalinkUtil.getShardFromSnowflake(json.getString("guildId"), lavalink.getNumShards()); - JDA jda2 = lavalink.getJda(sId); - - String guildId = json.getString("guildId"); - String channelId = json.optString("channelId"); - if (channelId.equals("")) channelId = null; - - - JSONObject res = new JSONObject(); - res.put("op", "validationRes"); - res.put("guildId", guildId); - VoiceChannel vc = null; - if (channelId != null) - vc = jda2.getVoiceChannelById(channelId); - - Guild guild = jda2.getGuildById(guildId); - - if (guild == null && channelId == null) { - res.put("valid", false); - send(res.toString()); - } else if (guild == null) { - res.put("valid", false); - res.put("channelId", channelId); - send(res.toString()); - } else if (channelId != null) { - res.put("valid", vc != null - && PermissionUtil.checkPermission(vc, guild.getSelfMember(), - Permission.VOICE_CONNECT, Permission.VOICE_SPEAK)); - res.put("channelId", channelId); - send(res.toString()); - } else { - res.put("valid", true); - send(res.toString()); - } - break; - case "isConnectedReq": - JDAImpl jda3 = (JDAImpl) lavalink.getJda(json.getInt("shardId")); - JSONObject res2 = new JSONObject(); - res2.put("op", "isConnectedRes"); - res2.put("shardId", json.getInt("shardId")); - res2.put("connected", jda3.getClient().isConnected()); - send(res2.toString()); - break; case "playerUpdate": lavalink.getLink(json.getString("guildId")) .getPlayer() From 4fcebba35f66e9425f6a9878daffa7c42a09f8cc Mon Sep 17 00:00:00 2001 From: Repulser Date: Sat, 20 Jan 2018 18:43:33 +0200 Subject: [PATCH 38/50] remove audio connection + last chanel --- .../src/main/java/lavalink/client/io/Lavalink.java | 9 ++++----- .../src/main/java/lavalink/client/io/Link.java | 6 +++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index 7a8256044..3c88ad125 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -175,16 +175,15 @@ public void onGuildLeave(GuildLeaveEvent event) { Link link = links.get(event.getGuild().getId()); if (link == null) return; - ((JDAImpl) event.getJDA()).getClient().removeAudioConnection(link.getGuildIdLong()); - link.destroy(); + link.removeConnection(); } @Override public void onVoiceChannelDelete(VoiceChannelDeleteEvent event) { Link link = links.get(event.getGuild().getId()); - if (link == null || !event.getChannel().equals(link.getChannel())) return; + if (link == null || !event.getChannel().equals(link.getLastChannel())) return; - link.disconnect(); + link.removeConnection(); } @Override @@ -198,7 +197,7 @@ private void reconnectVoiceConnections(JDA jda) { links.forEach((guildId, link) -> { try { //Note: We also ensure that the link belongs to the JDA object - if (link.getChannel() != null + if (link.getLastChannel() != null && jda.getGuildById(guildId) != null) { link.connect(link.getLastChannel()); } diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 62187c035..aed7d63d2 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -131,6 +131,10 @@ public void disconnect() { getMainWs().queueAudioDisconnect(g); } + void removeConnection() { + getMainWs().removeAudioConnection(guild); + } + public void changeNode(LavalinkSocket newNode) { disconnect(); node = newNode; @@ -184,7 +188,7 @@ public LavalinkSocket getNode(boolean selectIfAbsent) { /** * @return The channel we are currently connect to */ - @SuppressWarnings("WeakerAccess") + @SuppressWarnings({"WeakerAccess", "unused"}) @Nullable public VoiceChannel getChannel() { if (channel == null || state == State.DESTROYED || state == State.NOT_CONNECTED) return null; From f9c8d673974281a880abf8a5f3c29af7168fe454 Mon Sep 17 00:00:00 2001 From: Repulser Date: Sun, 21 Jan 2018 17:31:15 +0200 Subject: [PATCH 39/50] Loadbalancer fix (#57) * Fix loadbalancer * player * Disconnect required * Try this way --- .../java/lavalink/client/io/Lavalink.java | 2 +- .../main/java/lavalink/client/io/Link.java | 23 +++++++++---------- .../io/VoiceServerUpdateInterceptor.java | 1 + .../java/lavalink/server/io/SocketServer.java | 1 + 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index 3c88ad125..ebad38398 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -199,7 +199,7 @@ private void reconnectVoiceConnections(JDA jda) { //Note: We also ensure that the link belongs to the JDA object if (link.getLastChannel() != null && jda.getGuildById(guildId) != null) { - link.connect(link.getLastChannel()); + link.connect(link.getLastChannel(), false); } } catch (Exception e) { log.error("Caught exception while trying to reconnect link " + link, e); diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index aed7d63d2..4a68d636d 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -46,15 +46,14 @@ public class Link { private static final Logger log = LoggerFactory.getLogger(Link.class); - private final Lavalink lavalink; private final long guild; private LavalinkPlayer player; private volatile String channel = null; + JSONObject lastVoiceServerUpdate = null; private volatile LavalinkSocket node = null; /* May only be set by setState() */ private volatile State state = State.NOT_CONNECTED; - private volatile boolean reconnectToNewNode = false; Link(Lavalink lavalink, String guildId) { this.lavalink = lavalink; @@ -92,7 +91,7 @@ public long getGuildIdLong() { * @param channel Channel to connect to */ @SuppressWarnings("WeakerAccess") - public void connect(VoiceChannel channel) { + void connect(VoiceChannel channel, boolean checkChannel) { if (!channel.getGuild().equals(getJda().getGuildById(guild))) throw new IllegalArgumentException("The provided VoiceChannel is not a part of the Guild that this AudioManager handles." + "Please provide a VoiceChannel from the proper Guild"); @@ -104,7 +103,7 @@ public void connect(VoiceChannel channel) { throw new InsufficientPermissionException(Permission.VOICE_CONNECT); //If we are already connected to this VoiceChannel, then do nothing. - if (channel.equals(channel.getGuild().getSelfMember().getVoiceState().getChannel())) + if (checkChannel && channel.equals(channel.getGuild().getSelfMember().getVoiceState().getChannel())) return; final int userLimit = channel.getUserLimit(); // userLimit is 0 if no limit is set! @@ -122,6 +121,10 @@ public void connect(VoiceChannel channel) { getMainWs().queueAudioConnect(channel); } + public void connect(VoiceChannel voiceChannel) { + connect(voiceChannel, true); + } + public void disconnect() { Guild g = getJda().getGuildById(guild); @@ -136,10 +139,11 @@ void removeConnection() { } public void changeNode(LavalinkSocket newNode) { - disconnect(); node = newNode; - connect(getJda().getVoiceChannelById(channel)); - reconnectToNewNode = true; + if (lastVoiceServerUpdate != null) { + node.send(lastVoiceServerUpdate.toString()); + player.onNodeChange(); + } } /** @@ -221,11 +225,6 @@ void setState(@Nonnull State state) { log.debug("Link {} changed state from {} to {}", this, this.state, state); this.state = state; - - if (state == State.NOT_CONNECTED && reconnectToNewNode) { - reconnectToNewNode = false; - connect(getJda().getVoiceChannelById(channel)); - } } @SuppressWarnings("WeakerAccess") diff --git a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java index 7b148f1bf..eefdc139d 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/VoiceServerUpdateInterceptor.java @@ -62,6 +62,7 @@ protected Long handleInternally(JSONObject content) { json.put("event", content); Link link = lavalink.getLink(guild); + link.lastVoiceServerUpdate = json; //noinspection ConstantConditions link.getNode(true).send(json.toString()); link.setState(Link.State.CONNECTED); diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java index de273f683..5f2911668 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java @@ -167,6 +167,7 @@ public void onMessage(WebSocket webSocket, String s) { case "destroy": Player player5 = contextMap.get(webSocket).getPlayers().remove(json.getString("guildId")); if (player5 != null) player5.stop(); + break; default: log.warn("Unexpected operation: " + json.getString("op")); break; From 3279bc316c90f290e2a4349b7ab05c1d9bb78c70 Mon Sep 17 00:00:00 2001 From: Repulser Date: Mon, 22 Jan 2018 17:12:27 +0200 Subject: [PATCH 40/50] Update JDAA from PR in order to remove unused disconnect hook (#59) --- LavalinkServer/build.gradle | 2 +- .../main/java/lavalink/server/io/ConnectionManagerImpl.java | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index add09c85f..3e04ec172 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,7 +17,7 @@ publishToMavenLocal.dependsOn 'bootRepackage' dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'com.github.Repulser', name: 'JDA-Audio', version: '339cc176f881ecf60eaf4d99ad90d36bf9ac5ea2' + compile group: 'com.github.Repulser', name: 'JDA-Audio', version: 'b0e027c2b6ca0f6f6f2505231ded1ba19981e267' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index b18c4fa18..22c101672 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -20,9 +20,4 @@ public void queueAudioConnect(String guildId, String channelId) { log.warn("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId, new RuntimeException()); } - @Override - public void onDisconnect(String guildId) { - log.warn("onDisconnect was requested, this shouldn't happen, guildId:" + guildId, new RuntimeException()); - } - } From b00fec560ff96e6596e9f3b1ff7481e980d22354 Mon Sep 17 00:00:00 2001 From: Napster Date: Wed, 24 Jan 2018 20:35:19 +0100 Subject: [PATCH 41/50] Add a client side Prometheus metrics collector (#58) Added a name property for nodes --- LavalinkClient/build.gradle | 1 + .../java/lavalink/client/io/Lavalink.java | 19 +++- .../lavalink/client/io/LavalinkSocket.java | 23 ++-- .../java/lavalink/client/io/RemoteStats.java | 4 +- .../client/io/metrics/LavalinkCollector.java | 105 ++++++++++++++++++ 5 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 LavalinkClient/src/main/java/lavalink/client/io/metrics/LavalinkCollector.java diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index 65a518497..49b6c051c 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -9,6 +9,7 @@ dependencies { compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' compile group: 'org.json', name: 'json', version: '20171018' compile group: 'net.dv8tion', name: 'JDA', version: '3.5.0_328' + compileOnly group: 'io.prometheus', name: 'simpleclient', version: '0.1.0' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.0.0-M4' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.0.0-M4' testCompile group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.0.0-M4' diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index ebad38398..11a0bf34e 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -47,6 +47,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; public class Lavalink extends ListenerAdapter { @@ -87,13 +88,27 @@ public boolean getAutoReconnect() { return autoReconnect; } - public void addNode(URI serverUri, String password) { + private static final AtomicInteger nodeCounter = new AtomicInteger(0); + + public void addNode(@Nonnull URI serverUri, @Nonnull String password) { + addNode("Lavalink_Node_#" + nodeCounter.getAndIncrement(), serverUri, password); + } + + /** + * @param name + * A name to identify this node. May show up in metrics and other places. + * @param serverUri + * uri of the node to be added + * @param password + * password of the node to be added + */ + public void addNode(@Nonnull String name, @Nonnull URI serverUri, @Nonnull String password) { HashMap headers = new HashMap<>(); headers.put("Authorization", password); headers.put("Num-Shards", Integer.toString(numShards)); headers.put("User-Id", userId); - nodes.add(new LavalinkSocket(this, serverUri, new Draft_6455(), headers)); + nodes.add(new LavalinkSocket(name, this, serverUri, new Draft_6455(), headers)); } @SuppressWarnings("unused") diff --git a/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java b/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java index 42c82404b..c0b5c06ab 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java @@ -29,18 +29,13 @@ import lavalink.client.player.event.TrackEndEvent; import lavalink.client.player.event.TrackExceptionEvent; import lavalink.client.player.event.TrackStuckEvent; -import net.dv8tion.jda.core.JDA; -import net.dv8tion.jda.core.Permission; -import net.dv8tion.jda.core.entities.Guild; -import net.dv8tion.jda.core.entities.VoiceChannel; -import net.dv8tion.jda.core.entities.impl.JDAImpl; -import net.dv8tion.jda.core.utils.PermissionUtil; import org.java_websocket.drafts.Draft; import org.java_websocket.handshake.ServerHandshake; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.io.IOException; import java.net.ConnectException; import java.net.URI; @@ -53,15 +48,20 @@ public class LavalinkSocket extends ReusableWebSocket { private static final Logger log = LoggerFactory.getLogger(LavalinkSocket.class); private static final int TIMEOUT_MS = 5000; + @Nonnull + private final String name; + @Nonnull private final Lavalink lavalink; RemoteStats stats; long lastReconnectAttempt = 0; private int reconnectsAttempted = 0; + @Nonnull private final URI remoteUri; private boolean available = false; - LavalinkSocket(Lavalink lavalink, URI serverUri, Draft protocolDraft, Map headers) { + LavalinkSocket(@Nonnull String name, @Nonnull Lavalink lavalink, @Nonnull URI serverUri, Draft protocolDraft, Map headers) { super(serverUri, protocolDraft, headers, TIMEOUT_MS); + this.name = name; this.lavalink = lavalink; this.remoteUri = serverUri; try { @@ -182,6 +182,7 @@ public void send(String text) throws NotYetConnectedException { } } + @Nonnull @SuppressWarnings("unused") public URI getRemoteUri() { return remoteUri; @@ -205,10 +206,16 @@ public boolean isAvailable() { return available && isOpen() && !isClosing(); } + @Nonnull + public String getName() { + return name; + } + @Override public String toString() { return "LavalinkSocket{" + - "remoteUri=" + remoteUri + + "name=" + name + + ",remoteUri=" + remoteUri + '}'; } } diff --git a/LavalinkClient/src/main/java/lavalink/client/io/RemoteStats.java b/LavalinkClient/src/main/java/lavalink/client/io/RemoteStats.java index c08b30f9c..95ef986a8 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/RemoteStats.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/RemoteStats.java @@ -30,7 +30,7 @@ public class RemoteStats { private final JSONObject json; private final int players; private final int playingPlayers; - private final long uptime; + private final long uptime; //in millis // In bytes private final long memFree; @@ -82,7 +82,7 @@ public int getPlayingPlayers() { return playingPlayers; } - public long getUptime() { + public long getUptime() { //in millis return uptime; } diff --git a/LavalinkClient/src/main/java/lavalink/client/io/metrics/LavalinkCollector.java b/LavalinkClient/src/main/java/lavalink/client/io/metrics/LavalinkCollector.java new file mode 100644 index 000000000..7cfb5de19 --- /dev/null +++ b/LavalinkClient/src/main/java/lavalink/client/io/metrics/LavalinkCollector.java @@ -0,0 +1,105 @@ +package lavalink.client.io.metrics; + +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; +import lavalink.client.io.Lavalink; +import lavalink.client.io.LavalinkSocket; +import lavalink.client.io.RemoteStats; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by napster on 22.01.18. + *

+ * A prometheus collector for gathering and exposing lavalink metrics client side. + */ +@SuppressWarnings("unused") +public class LavalinkCollector extends Collector { + + private final Lavalink lavalink; + + public LavalinkCollector(@Nonnull Lavalink lavalinkInstance) { + this.lavalink = lavalinkInstance; + } + + @Override + public List collect() { + + List mfs = new ArrayList<>(); + List labelNames = Collections.singletonList("node"); + + + GaugeMetricFamily players = new GaugeMetricFamily("lavalink_players_current", + "Amount of players", labelNames); + mfs.add(players); + GaugeMetricFamily playingPlayers = new GaugeMetricFamily("lavalink_playing_players_current", + "Amount of playing players", labelNames); + mfs.add(playingPlayers); + GaugeMetricFamily uptimeSeconds = new GaugeMetricFamily("lavalink_uptime_seconds", + "Uptime of the node", labelNames); + mfs.add(uptimeSeconds); + + + GaugeMetricFamily memFree = new GaugeMetricFamily("lavalink_mem_free_bytes", + "Amount of free memory", labelNames); + mfs.add(memFree); + GaugeMetricFamily memUsed = new GaugeMetricFamily("lavalink_mem_used_bytes", + "Amount of used memory", labelNames); + mfs.add(memUsed); + GaugeMetricFamily memAllocated = new GaugeMetricFamily("lavalink_mem_allocated_bytes", + "Amount of allocated memory", labelNames); + mfs.add(memAllocated); + GaugeMetricFamily memReservable = new GaugeMetricFamily("lavalink_mem_reservable_bytes", + "Amount of reservable memory", labelNames); + mfs.add(memReservable); + + GaugeMetricFamily cpuCores = new GaugeMetricFamily("lavalink_cpu_cores", + "Amount of cpu cores", labelNames); + mfs.add(cpuCores); + GaugeMetricFamily systemLoad = new GaugeMetricFamily("lavalink_load_system", + "Total load of the system", labelNames); + mfs.add(systemLoad); + GaugeMetricFamily lavalinkLoad = new GaugeMetricFamily("lavalink_load_lavalink", + "Load caused by Lavalink", labelNames); + mfs.add(lavalinkLoad); + + + GaugeMetricFamily averageFramesSentPerMinute = new GaugeMetricFamily("lavalink_average_frames_sent_per_minute", + "Average frames sent per minute", labelNames); + mfs.add(averageFramesSentPerMinute); + GaugeMetricFamily averageFramesNulledPerMinute = new GaugeMetricFamily("lavalink_average_frames_nulled_per_minute", + "Average frames nulled per minute", labelNames); + mfs.add(averageFramesNulledPerMinute); + GaugeMetricFamily averageFramesDeficitPerMinute = new GaugeMetricFamily("lavalink_average_frames_deficit_per_minute", + "Average frames deficit per minute", labelNames); + mfs.add(averageFramesDeficitPerMinute); + + + for (LavalinkSocket node : lavalink.getNodes()) { + List labels = Collections.singletonList(node.getName()); + RemoteStats stats = node.getStats(); + + players.addMetric(labels, stats.getPlayers()); + playingPlayers.addMetric(labels, stats.getPlayingPlayers()); + uptimeSeconds.addMetric(labels, stats.getUptime() / 1000); + + memFree.addMetric(labels, stats.getMemFree()); + memUsed.addMetric(labels, stats.getMemUsed()); + memAllocated.addMetric(labels, stats.getMemAllocated()); + memReservable.addMetric(labels, stats.getMemReservable()); + + cpuCores.addMetric(labels, stats.getCpuCores()); + systemLoad.addMetric(labels, stats.getSystemLoad()); + lavalinkLoad.addMetric(labels, stats.getLavalinkLoad()); + + averageFramesSentPerMinute.addMetric(labels, stats.getAvgFramesSentPerMinute()); + averageFramesNulledPerMinute.addMetric(labels, stats.getAvgFramesNulledPerMinute()); + averageFramesDeficitPerMinute.addMetric(labels, stats.getAvgFramesDeficitPerMinute()); + } + + return mfs; + } +} From f268977de0e8e517be033aac427ea13a06db1c9d Mon Sep 17 00:00:00 2001 From: Napster Date: Sun, 28 Jan 2018 14:32:03 +0100 Subject: [PATCH 42/50] Fix missing sentry release based on commit hash --- LavalinkServer/build.gradle | 1 + .../src/main/java/lavalink/server/Launcher.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 3e04ec172..1de96175b 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -1,6 +1,7 @@ plugins { id 'application' id 'org.springframework.boot' version '1.5.9.RELEASE' + id 'com.gorylenko.gradle-git-properties' version '1.4.20' } description = 'Play audio to discord voice channels' diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.java b/LavalinkServer/src/main/java/lavalink/server/Launcher.java index 9602753de..8694e37b9 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.java +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.java @@ -79,10 +79,12 @@ public Launcher(Config config, SocketServer socketServer) { private void initSentry() { String sentryDsn = config.getSentryDsn(); if (sentryDsn == null || sentryDsn.isEmpty()) { + log.info("No sentry dsn found, turning off sentry."); turnOffSentry(); return; } SentryClient sentryClient = Sentry.init(sentryDsn); + log.info("Set up sentry."); // set the git commit hash this was build on as the release Properties gitProps = new Properties(); @@ -92,11 +94,13 @@ private void initSentry() { log.error("Failed to load git repo information", e); } - String commitHash = gitProps.getProperty("git.commit.id.full"); - if (commitHash == null || commitHash.isEmpty()) { - return; + String commitHash = gitProps.getProperty("git.commit.id"); + if (commitHash != null && !commitHash.isEmpty()) { + log.info("Setting sentry release to commit hash {}", commitHash); + sentryClient.setRelease(commitHash); + } else { + log.warn("No git commit hash found to set up sentry release"); } - sentryClient.setRelease(commitHash); } private void turnOffSentry() { From a026992c82dd59fe047bb6a6f1bd7d930a16ffc3 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Sun, 28 Jan 2018 16:05:27 +0100 Subject: [PATCH 43/50] Update JDAA to use upstream instead of fork --- LavalinkServer/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 1de96175b..e9c629108 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -18,7 +18,7 @@ publishToMavenLocal.dependsOn 'bootRepackage' dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.45' - compile group: 'com.github.Repulser', name: 'JDA-Audio', version: 'b0e027c2b6ca0f6f6f2505231ded1ba19981e267' + compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: '91438c36d7107cf838c2f2eb147b08f989d929db' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' From 09188a335a847f9da3973e0bc826f5087642d84e Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sun, 28 Jan 2018 16:18:26 +0100 Subject: [PATCH 44/50] Make sentry DSN nullable --- LavalinkServer/src/main/java/lavalink/server/Config.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/Config.java b/LavalinkServer/src/main/java/lavalink/server/Config.java index d6fef6bd1..d0c53ea44 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Config.java +++ b/LavalinkServer/src/main/java/lavalink/server/Config.java @@ -47,16 +47,19 @@ public void setPassword(String password) { this.password = password; } + @Nullable private String sentryDsn; + @Nullable public String getSentryDsn() { return sentryDsn; } - public void setSentryDsn(String sentryDsn) { + public void setSentryDsn(@Nullable String sentryDsn) { this.sentryDsn = sentryDsn; } + @SuppressWarnings("WeakerAccess") @Nullable public Integer bufferDurationMs; From 5fd915ecfe48281352758aa31695bea7fefa9c33 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sun, 28 Jan 2018 16:18:48 +0100 Subject: [PATCH 45/50] Add defaults to the WS server for backwards compatibility --- LavalinkServer/src/main/java/lavalink/server/Launcher.java | 4 ++-- .../main/java/lavalink/server/io/ConnectionManagerImpl.java | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.java b/LavalinkServer/src/main/java/lavalink/server/Launcher.java index 8694e37b9..6dfa659b6 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.java +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.java @@ -139,8 +139,8 @@ public static void main(String[] args) { } @Bean - static SocketServer socketServer(@Value("${lavalink.server.ws.port}") Integer port, - @Value("${lavalink.server.ws.host}") String host, + static SocketServer socketServer(@Value("${lavalink.server.ws.port:8080}") Integer port, + @Value("${lavalink.server.ws.host:0.0.0.0}") String host, @Value("${lavalink.server.password}") String password) { SocketServer ss = new SocketServer(new InetSocketAddress(host, port), password); ss.start(); diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index 22c101672..78d788185 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -15,6 +15,11 @@ public class ConnectionManagerImpl implements ConnectionManager { public void removeAudioConnection(String guildId) { } + @Override + public void onDisconnect(String s) { + + } + @Override public void queueAudioConnect(String guildId, String channelId) { log.warn("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId, new RuntimeException()); From fa56a27b09f55fcfe9175fcd4b9d8a0204e1a671 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sun, 28 Jan 2018 16:23:55 +0100 Subject: [PATCH 46/50] Thanks IDEA --- .../main/java/lavalink/server/io/ConnectionManagerImpl.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java index 78d788185..22c101672 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/ConnectionManagerImpl.java @@ -15,11 +15,6 @@ public class ConnectionManagerImpl implements ConnectionManager { public void removeAudioConnection(String guildId) { } - @Override - public void onDisconnect(String s) { - - } - @Override public void queueAudioConnect(String guildId, String channelId) { log.warn("queueAudioConnect was requested, this shouldn't happen, guildId:" + guildId + " channelId:" + channelId, new RuntimeException()); From f8a85f33cb4e31253ce378ecade629c3550a1a30 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Sun, 28 Jan 2018 20:48:43 +0100 Subject: [PATCH 47/50] Fix concurrent exception with player event listeners --- .../src/main/java/lavalink/client/player/LavalinkPlayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java b/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java index 61eb7ba96..24e0f4abe 100644 --- a/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java +++ b/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java @@ -33,8 +33,8 @@ import org.json.JSONObject; import java.io.IOException; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; public class LavalinkPlayer implements IPlayer { @@ -45,7 +45,7 @@ public class LavalinkPlayer implements IPlayer { private long position = -1; private final Link link; - private List listeners = new ArrayList<>(); + private List listeners = new CopyOnWriteArrayList<>(); public LavalinkPlayer(Link link) { this.link = link; From 3c743cd0e9f57cd56b59007205018de1432d95ea Mon Sep 17 00:00:00 2001 From: Frederikam Date: Mon, 29 Jan 2018 18:18:40 +0100 Subject: [PATCH 48/50] Bump versions --- LavalinkClient/build.gradle | 2 +- LavalinkServer/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index 49b6c051c..f4df29579 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -1,5 +1,5 @@ description = 'JDA based client for the Lavalink-Server' -version System.getenv('dev') == 'true' ? '-SNAPSHOT' : '1.3' +version System.getenv('dev') == 'true' ? '-SNAPSHOT' : '2.0' ext { moduleName = 'Lavalink-Client' } diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index e9c629108..1b17b7326 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -6,7 +6,7 @@ plugins { description = 'Play audio to discord voice channels' mainClassName = "lavalink.server.Launcher" -version '1.3' +version '2.0' ext { moduleName = 'Lavalink-Server' } From 6a2e6b80d9512d12b64aeeb8878826cae318b421 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Mon, 29 Jan 2018 18:24:06 +0100 Subject: [PATCH 49/50] Update readme --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2441a958..1c9380576 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Standalone audio sending node based on Lavaplayer and JDA-Audio. Allows for sending audio without it ever reaching any of your shards. -Being used in production by FredBoat and Dyno. +Being used in production by FredBoat, Dyno, Rythm, LewdBot, and more. ## Features * Powered by Lavaplayer @@ -16,11 +16,16 @@ Being used in production by FredBoat and Dyno. * Basic authentication ## Client libraries: +### Supports 2.x: * [JDA client](https://github.com/Frederikam/Lavalink/tree/master/LavalinkClient) (JDA, Java) +* Or [create your own](https://github.com/Frederikam/Lavalink/blob/master/IMPLEMENTATION.md) + +### Supports 1.x: * [eris-lavalink](https://github.com/briantanner/eris-lavalink) (Eris, JavaScript) * [lava-d.js](https://github.com/untocodes/lava-d.js) (discord.js, JavaScript) * [lavalink.js](https://github.com/briantanner/lavalink.js) (discord.js, JavaScript) -* Or [create your own](https://github.com/Frederikam/Lavalink/blob/master/IMPLEMENTATION.md) + +Outdated as of January 29 2018. ## Server configuration Download from [the CI server](https://ci.fredboat.com/viewLog.html?buildId=lastSuccessful&buildTypeId=Lavalink_Build&tab=artifacts&guest=1) From b8dd3c8a7e186755c1ab343d19a552baecf138e7 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Mon, 29 Jan 2018 19:56:56 +0100 Subject: [PATCH 50/50] Document v2 changes --- IMPLEMENTATION.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 99435c9a9..e2689e059 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -10,6 +10,31 @@ How to write your own client. The Java client will serve as an example implement * Hybi 10 * Hixie 76 * Hixie 75 + +## Changes v1.3 -> v2.0 +With the release of v2.0 many unnecessary ops were removed: + +* `connect` +* `disconnect` +* `validationRes` +* `isConnectedRes` +* `validationReq` +* `isConnectedReq` +* `sendWS` + +With Lavalink 1.x the server had the responsibility of handling Discord VOICE_SERVER_UPDATEs as well as its own internal ratelimiting. +This remote handling makes things unnecessarily complicated and adds a lot og points where things could go wrong. +One problem we noticed is that since JDAA is unaware of ratelimits on the bot's gateway connection, it would keep adding +to the ratelimit queue to the gateway. With this update this is now the responsibility of the Lavalink client or the +Discord client. + +A voice connection is now initiated by forwarding a `voiceUpdate` (VOICE_SERVER_UPDATE) to the server. When you want to +disconnect or move to a different voice channel you must send Discord a new VOICE_STATE_UPDATE. If you want to move your +connection to a new Lavalink server you can simply send the VOICE_SERVER_UPDATE to the new node, and the other node +will be disconnected by Discord. + +Depending on your Discord library, it may be possible to take advantage of the library's OP 4 handling. For instance, +the JDA client takes advantage of JDA's websocket write thread to send OP 4s for connects, disconnects and reconnects. ## Protocol ### Opening a connection