diff --git a/.gitignore b/.gitignore index d738fba21..67c6d0d25 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ build/* .project .classpath .settings +/Testbot/build/ +/Testbot/out/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ad23d2c10..ad313e56f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,28 @@ Each release usually includes various fixes and improvements. The most noteworthy of these, as well as any features and breaking changes, are listed here. +## v3.3.2 +* Replaced Magma with Koe. +* Finally implemented `stopTime` for `play` op. +* Added `playerUpdateInterval` config option. +* Added `environment` to Sentry config. +* Fixed #332 +* Updated IP rotator. +* Update lavaplayer to `1.3.59` from devoxin's fork. +* Added a Testbot for development. + +Contributors: +[@Frederikam](https://github.com/Frederikam), +[@Thewsomeguy](https://github.com/Thewsomeguy), +[@Neuheit](https://github.com/Neuheit), +[@Sangoon_Is_Noob](https://github.com/Sangoon_Is_Noob), +[@TheEssem](https://github.com/Essem), and +[@Devoxin](https://github.com/Devoxin) + ## v3.3.1.4 * Update lavaplayer to `1.3.54.3` from devoxin's fork. + ## v3.3.1.3 * Update lavaplayer to `1.3.53` from devoxin's fork. diff --git a/LavalinkServer/application.yml.example b/LavalinkServer/application.yml.example index 27247bed5..868d1e836 100644 --- a/LavalinkServer/application.yml.example +++ b/LavalinkServer/application.yml.example @@ -15,6 +15,7 @@ lavalink: local: false bufferDurationMs: 400 youtubePlaylistLoadLimit: 6 # Number of pages at 100 each + playerUpdateInterval: 5 # How frequently to send player updates to clients, in seconds youtubeSearchEnabled: true soundcloudSearchEnabled: true gc-warnings: true @@ -32,6 +33,7 @@ metrics: sentry: dsn: "" + environment: "" # tags: # some_key: some_value # another_key: another_value diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index bba5395ff..5138aefc0 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -34,16 +34,16 @@ bootRun { } dependencies { - compile group: 'space.npstr.Magma', name: 'magma', version: magmaVersion + compile (group: 'moe.kyokobot.koe', name: 'core', version: koeVersion) { + // This version of SLF4J does not recognise Logback 1.2.3 + exclude group: "org.slf4j", module: "slf4j-api" + } + compile group: 'moe.kyokobot.koe', name: 'ext-udpqueue', version: koeVersion //compile group: 'com.sedmelluq', name: 'lavaplayer', version: lavaplayerVersion - compile group: 'com.github.devoxin', name: 'lavaplayer', version: '1.3.54.3' + compile group: 'com.github.devoxin', name: 'lavaplayer', version: '1.3.59' compile group: 'com.sedmelluq', name: 'lavaplayer-ext-youtube-rotator', version: lavaplayerIpRotatorVersion - compile group: 'com.sedmelluq', name: 'jda-nas', version: jdaNasVersion compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlinVersion - compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: jappVersion - //required by japp - compile group: 'org.apache.commons', name: 'commons-lang3', version: commonsLangVersion compile group: 'org.springframework', name: 'spring-websocket', version: springWebSocketVersion compile group: 'ch.qos.logback', name: 'logback-classic', version: logbackVersion compile group: 'io.sentry', name: 'sentry-logback', version: sentryLogbackVersion diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt index 648b281fe..b1a83d26e 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.kt +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.kt @@ -125,5 +125,6 @@ object Launcher { } ) sa.run(*args) + log.info("You can safely ignore the big red warning about illegal reflection. See https://github.com/Frederikam/Lavalink/issues/295") } } diff --git a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt index 6f5fe73ab..953fb1a2b 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt @@ -14,7 +14,7 @@ import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceM import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager -import com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotator +import com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotatorSetup import com.sedmelluq.lava.extensions.youtuberotator.planner.AbstractRoutePlanner import com.sedmelluq.lava.extensions.youtuberotator.planner.BalancingIpRoutePlanner import com.sedmelluq.lava.extensions.youtuberotator.planner.NanoIpRoutePlanner @@ -50,9 +50,9 @@ class AudioPlayerConfiguration { if (routePlanner != null) { val retryLimit = serverConfig.ratelimit?.retryLimit ?: -1 when { - retryLimit < 0 -> YoutubeIpRotator.setup(youtube, routePlanner) - retryLimit == 0 -> YoutubeIpRotator.setup(youtube, routePlanner, Int.MAX_VALUE) - else -> YoutubeIpRotator.setup(youtube, routePlanner, retryLimit) + retryLimit < 0 -> YoutubeIpRotatorSetup(routePlanner).forSource(youtube).setup() + retryLimit == 0 -> YoutubeIpRotatorSetup(routePlanner).forSource(youtube).withRetryLimit(Int.MAX_VALUE).setup() + else -> YoutubeIpRotatorSetup(routePlanner).forSource(youtube).withRetryLimit(retryLimit).setup() } } diff --git a/LavalinkServer/src/main/java/lavalink/server/config/AudioSendFactoryConfiguration.java b/LavalinkServer/src/main/java/lavalink/server/config/AudioSendFactoryConfiguration.java deleted file mode 100644 index 31358ec89..000000000 --- a/LavalinkServer/src/main/java/lavalink/server/config/AudioSendFactoryConfiguration.java +++ /dev/null @@ -1,56 +0,0 @@ -package lavalink.server.config; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -/** - * Created by napster on 05.03.18. - */ -@Component -public class AudioSendFactoryConfiguration { - - private static final Logger log = LoggerFactory.getLogger(AudioSendFactoryConfiguration.class); - - private boolean nasSupported = false; - private final int audioSendFactoryCount = Runtime.getRuntime().availableProcessors() * 2; - - public AudioSendFactoryConfiguration(ServerConfig serverConfig) { - String os = System.getProperty("os.name"); - - log.info("OS: " + System.getProperty("os.name") + ", Arch: " + System.getProperty("os.arch")); - - if ((os.contains("Windows") || os.contains("Linux")) - && !System.getProperty("os.arch").equalsIgnoreCase("arm") - && !System.getProperty("os.arch").equalsIgnoreCase("arm-linux") - ) { - nasSupported = true; - log.info("JDA-NAS supported system detected. Enabled native audio sending."); - - Integer customBuffer = serverConfig.getBufferDurationMs(); - if (customBuffer != null) { - log.info("Setting buffer to {}ms", customBuffer); - } else { - log.info("Using default buffer"); - } - - Integer customPlaylistLimit = serverConfig.getYoutubePlaylistLoadLimit(); - if (customPlaylistLimit != null) { - log.info("Setting playlist load limit to {}", customPlaylistLimit); - } else { - log.info("Using default playlist load limit"); - } - } else { - log.warn("This system and architecture appears to not support native audio sending! " - + "GC pauses may cause your bot to stutter during playback."); - } - } - - public boolean isNasSupported() { - return nasSupported; - } - - public int getAudioSendFactoryCount() { - return audioSendFactoryCount; - } -} diff --git a/LavalinkServer/src/main/java/lavalink/server/config/KoeConfiguration.kt b/LavalinkServer/src/main/java/lavalink/server/config/KoeConfiguration.kt new file mode 100644 index 000000000..d787a3ed7 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/config/KoeConfiguration.kt @@ -0,0 +1,39 @@ +package lavalink.server.config + +import moe.kyokobot.koe.KoeOptions +import moe.kyokobot.koe.codec.udpqueue.UdpQueueFramePollerFactory +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class KoeConfiguration(val serverConfig: ServerConfig) { + + private val log: Logger = LoggerFactory.getLogger(KoeConfiguration::class.java) + + @Bean + fun koeOptions(): KoeOptions = KoeOptions.builder().apply { + log.info("OS: " + System.getProperty("os.name") + ", Arch: " + System.getProperty("os.arch")) + val os = System.getProperty("os.name") + val arch = System.getProperty("os.arch") + + // Maybe add Windows natives back? + val nasSupported = os.contains("linux", ignoreCase = true) + && arch.equals("amd64", ignoreCase = true) + + if (nasSupported) { + log.info("Enabling JDA-NAS") + var bufferSize = serverConfig.bufferDurationMs ?: UdpQueueFramePollerFactory.DEFAULT_BUFFER_DURATION + if (bufferSize <= 0) { + log.warn("Buffer size of {}ms is illegal. Defaulting to {}", + bufferSize, UdpQueueFramePollerFactory.DEFAULT_BUFFER_DURATION) + bufferSize = UdpQueueFramePollerFactory.DEFAULT_BUFFER_DURATION + } + setFramePollerFactory(UdpQueueFramePollerFactory(bufferSize, Runtime.getRuntime().availableProcessors())) + } else { + log.warn("This system and architecture appears to not support native audio sending! " + + "GC pauses may cause your bot to stutter during playback.") + } + }.create() +} \ No newline at end of file diff --git a/LavalinkServer/src/main/java/lavalink/server/config/SentryConfigProperties.java b/LavalinkServer/src/main/java/lavalink/server/config/SentryConfigProperties.java deleted file mode 100644 index eca63fa28..000000000 --- a/LavalinkServer/src/main/java/lavalink/server/config/SentryConfigProperties.java +++ /dev/null @@ -1,34 +0,0 @@ -package lavalink.server.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -/** - * Created by napster on 20.05.18. - */ -@Component -@ConfigurationProperties(prefix = "sentry") -public class SentryConfigProperties { - - private String dsn = ""; - private Map tags = new HashMap<>(); - - public String getDsn() { - return dsn; - } - - public void setDsn(String dsn) { - this.dsn = dsn; - } - - public Map getTags() { - return tags; - } - - public void setTags(Map tags) { - this.tags = tags; - } -} diff --git a/LavalinkServer/src/main/java/lavalink/server/config/SentryConfigProperties.kt b/LavalinkServer/src/main/java/lavalink/server/config/SentryConfigProperties.kt new file mode 100644 index 000000000..2aec8e6ec --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/config/SentryConfigProperties.kt @@ -0,0 +1,16 @@ +package lavalink.server.config + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.stereotype.Component +import java.util.* + +/** + * Created by napster on 20.05.18. + */ +@Component +@ConfigurationProperties(prefix = "sentry") +class SentryConfigProperties { + var dsn = "" + var environment = "" + var tags: Map = HashMap() +} \ No newline at end of file diff --git a/LavalinkServer/src/main/java/lavalink/server/config/SentryConfiguration.java b/LavalinkServer/src/main/java/lavalink/server/config/SentryConfiguration.java index f0fa9b34d..b4e420d94 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/SentryConfiguration.java +++ b/LavalinkServer/src/main/java/lavalink/server/config/SentryConfiguration.java @@ -36,7 +36,7 @@ public SentryConfiguration(ServerConfig serverConfig, SentryConfigProperties sen } if (dsn != null && !dsn.isEmpty()) { - turnOn(dsn, sentryConfig.getTags()); + turnOn(dsn, sentryConfig.getTags(), sentryConfig.getEnvironment()); if (warnDeprecatedDsnConfig) { log.warn("Please update the location of the sentry dsn in lavalinks config file / your environment " + "vars, it is now located under 'sentry.dsn' instead of 'lavalink.server.sentryDsn'."); @@ -47,9 +47,10 @@ public SentryConfiguration(ServerConfig serverConfig, SentryConfigProperties sen } - public void turnOn(String dsn, Map tags) { + public void turnOn(String dsn, Map tags, String environment) { log.info("Turning on sentry"); SentryClient sentryClient = Sentry.init(dsn); + if (!environment.isBlank()) sentryClient.setEnvironment(environment); if (tags != null) { tags.forEach(sentryClient::addTag); diff --git a/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt b/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt index 7df6e1926..28f27ffe9 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt @@ -33,6 +33,7 @@ class ServerConfig { var sentryDsn = "" var bufferDurationMs: Int? = null var youtubePlaylistLoadLimit: Int? = null + var playerUpdateInterval: Int? = 5 var isGcWarnings = true var isYoutubeSearchEnabled = true var isSoundcloudSearchEnabled = true diff --git a/LavalinkServer/src/main/java/lavalink/server/io/RoutePlannerRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/io/RoutePlannerRestHandler.kt index b86ac1998..aa3791082 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/RoutePlannerRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/RoutePlannerRestHandler.kt @@ -39,10 +39,11 @@ class RoutePlannerRestHandler(private val routePlanner: AbstractRoutePlanner?) { * Removes a single address from the addresses which are currently marked as failing */ @PostMapping("/routeplanner/free/address") - fun freeSingleAddress(request: HttpServletRequest, @RequestBody requestBody: JSONObject): ResponseEntity { + fun freeSingleAddress(request: HttpServletRequest, @RequestBody requestBody: String): ResponseEntity { routePlanner ?: throw RoutePlannerDisabledException() try { - val address = InetAddress.getByName(requestBody.getString("address")) + val jsonObject = JSONObject(requestBody) + val address = InetAddress.getByName(jsonObject.getString("address")) routePlanner.freeAddress(address) return ResponseEntity.noContent().build() } catch (exception: UnknownHostException) { @@ -65,7 +66,7 @@ class RoutePlannerRestHandler(private val routePlanner: AbstractRoutePlanner?) { */ private fun getDetailBlock(planner: AbstractRoutePlanner): IRoutePlannerStatus { val ipBlock = planner.ipBlock - val ipBlockStatus = IpBlockStatus(ipBlock.type.simpleName, ipBlock.size.toString() ?: "0") + val ipBlockStatus = IpBlockStatus(ipBlock.type.simpleName, ipBlock.size.toString()) val failingAddresses = planner.failingAddresses val failingAddressesStatus = failingAddresses.entries.map { diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.kt b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.kt index a8cd27326..749ac526d 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.kt @@ -23,42 +23,44 @@ package lavalink.server.io import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager -import lavalink.server.player.Player -import space.npstr.magma.api.MagmaMember import io.undertow.websockets.core.WebSocketCallback import io.undertow.websockets.core.WebSocketChannel import io.undertow.websockets.core.WebSockets import io.undertow.websockets.jsr.UndertowSession +import lavalink.server.player.Player +import lavalink.server.config.ServerConfig +import moe.kyokobot.koe.KoeClient +import moe.kyokobot.koe.KoeEventAdapter +import moe.kyokobot.koe.VoiceConnection +import moe.kyokobot.koe.VoiceServerInfo import org.json.JSONObject import org.slf4j.LoggerFactory import org.springframework.web.socket.WebSocketSession import org.springframework.web.socket.adapter.standard.StandardWebSocketSession -import space.npstr.magma.MagmaFactory -import space.npstr.magma.api.MagmaApi -import space.npstr.magma.api.event.MagmaEvent -import space.npstr.magma.api.event.WebSocketClosed import java.util.* -import java.util.concurrent.* import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit -import java.util.function.Supplier class SocketContext internal constructor( val audioPlayerManager: AudioPlayerManager, - var session: WebSocketSession, + val serverConfig: ServerConfig, + private var session: WebSocketSession, private val socketServer: SocketServer, - val userId: String + val userId: String, + private val koe: KoeClient ) { companion object { private val log = LoggerFactory.getLogger(SocketContext::class.java) } - internal val magma: MagmaApi = MagmaFactory.of { socketServer.getAudioSendFactory(it) } //guildId <-> Player val players = ConcurrentHashMap() + @Volatile var sessionPaused = false private val resumeEventQueue = ConcurrentLinkedQueue() @@ -67,7 +69,7 @@ class SocketContext internal constructor( var resumeKey: String? = null var resumeTimeout = 60L // Seconds private var sessionTimeoutFuture: ScheduledFuture? = null - private val executor: ScheduledExecutorService + private val executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() val playerUpdateService: ScheduledExecutorService val playingPlayers: List @@ -79,9 +81,6 @@ class SocketContext internal constructor( init { - magma.eventStream.subscribe { this.handleMagmaEvent(it) } - - executor = Executors.newSingleThreadScheduledExecutor() executor.scheduleAtFixedRate(StatsTask(this, socketServer), 0, 1, TimeUnit.MINUTES) playerUpdateService = Executors.newScheduledThreadPool(2) { r -> @@ -93,25 +92,31 @@ class SocketContext internal constructor( } internal fun getPlayer(guildId: String) = players.computeIfAbsent(guildId) { - Player(this, guildId, audioPlayerManager) + Player(this, guildId, audioPlayerManager, serverConfig) } internal fun getPlayers(): Map { return players } - private fun handleMagmaEvent(magmaEvent: MagmaEvent) { - if (magmaEvent is WebSocketClosed) { - val out = JSONObject() - out.put("op", "event") - out.put("type", "WebSocketClosedEvent") - out.put("guildId", magmaEvent.member.guildId) - out.put("reason", magmaEvent.reason) - out.put("code", magmaEvent.closeCode) - out.put("byRemote", magmaEvent.isByRemote) - - send(out) + /** + * Gets or creates a voice connection + */ + fun getVoiceConnection(guild: Long): VoiceConnection { + var conn = koe.getConnection(guild) + if (conn == null) { + conn = koe.createConnection(guild) + conn.registerListener(EventHandler(guild.toString())) } + return conn + } + + /** + * Disposes of a voice connection + */ + fun destroy(guild: Long) { + players.remove(guild.toString())?.stop() + koe.destroyConnection(guild) } fun pause() { @@ -162,23 +167,28 @@ class SocketContext internal constructor( send(resumeEventQueue.remove()) } - players.values.forEach { it -> SocketServer.sendPlayerUpdate(this, it) } + players.values.forEach { SocketServer.sendPlayerUpdate(this, it) } } internal fun shutdown() { log.info("Shutting down " + playingPlayers.size + " playing players.") executor.shutdown() playerUpdateService.shutdown() - players.keys.forEach { guildId -> - val member = MagmaMember.builder() - .userId(userId) - .guildId(guildId) - .build() - magma.removeSendHandler(member) - magma.closeConnection(member) - } - players.values.forEach(Player::stop) - magma.shutdown() + koe.close() + } + + private inner class EventHandler(private val guildId: String) : KoeEventAdapter() { + override fun gatewayClosed(code: Int, reason: String?, byRemote: Boolean) { + val out = JSONObject() + out.put("op", "event") + out.put("type", "WebSocketClosedEvent") + out.put("guildId", guildId) + out.put("reason", reason ?: "") + out.put("code", code) + out.put("byRemote", byRemote) + + send(out) + } } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt index d16cfd51f..9c5bddfce 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt @@ -22,14 +22,11 @@ package lavalink.server.io -import com.github.shredder121.asyncaudio.jda.AsyncPacketProviderFactory -import com.sedmelluq.discord.lavaplayer.jdaudp.NativeAudioSendFactory import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager -import lavalink.server.config.AudioSendFactoryConfiguration import lavalink.server.config.ServerConfig import lavalink.server.player.Player -import lavalink.server.util.Util -import net.dv8tion.jda.api.audio.factory.IAudioSendFactory +import moe.kyokobot.koe.Koe +import moe.kyokobot.koe.KoeOptions import org.json.JSONObject import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -37,25 +34,23 @@ import org.springframework.web.socket.CloseStatus import org.springframework.web.socket.TextMessage import org.springframework.web.socket.WebSocketSession import org.springframework.web.socket.handler.TextWebSocketHandler -import space.npstr.magma.api.Member import java.util.* import java.util.concurrent.ConcurrentHashMap -import java.util.function.Supplier @Service class SocketServer( private val serverConfig: ServerConfig, private val audioPlayerManager: AudioPlayerManager, - private val audioSendFactoryConfiguration: AudioSendFactoryConfiguration + koeOptions: KoeOptions ) : TextWebSocketHandler() { // userId <-> shardCount private val shardCounts = ConcurrentHashMap() val contextMap = HashMap() - private val sendFactories = ConcurrentHashMap() @Suppress("LeakingThis") private val handlers = WebSocketHandlers(contextMap) private val resumableSessions = mutableMapOf() + private val koe = Koe.koe(koeOptions) companion object { private val log = LoggerFactory.getLogger(SocketServer::class.java) @@ -92,7 +87,14 @@ class SocketServer( shardCounts[userId] = shardCount - contextMap[session.id] = SocketContext(audioPlayerManager, session, this, userId) + contextMap[session.id] = SocketContext( + audioPlayerManager, + serverConfig, + session, + this, + userId, + koe.newClient(userId.toLong()) + ) log.info("Connection successfully established from " + session.remoteAddress!!) } @@ -141,40 +143,25 @@ class SocketServer( return } + val context = contextMap[session.id] + ?: throw IllegalStateException("No context for session ID ${session.id}. Broken websocket?") + when (json.getString("op")) { // @formatter:off - "voiceUpdate" -> handlers.voiceUpdate(session, json) - "play" -> handlers.play(session, json) - "stop" -> handlers.stop(session, json) - "pause" -> handlers.pause(session, json) - "seek" -> handlers.seek(session, json) - "volume" -> handlers.volume(session, json) - "destroy" -> handlers.destroy(session, json) - "configureResuming" -> handlers.configureResuming(session, json) - "equalizer" -> handlers.equalizer(session, json) + "voiceUpdate" -> handlers.voiceUpdate(context, json) + "play" -> handlers.play(context, json) + "stop" -> handlers.stop(context, json) + "pause" -> handlers.pause(context, json) + "seek" -> handlers.seek(context, json) + "volume" -> handlers.volume(context, json) + "destroy" -> handlers.destroy(context, json) + "configureResuming" -> handlers.configureResuming(context, json) + "equalizer" -> handlers.equalizer(context, json) else -> log.warn("Unexpected operation: " + json.getString("op")) // @formatter:on } } - fun getAudioSendFactory(member: Member): IAudioSendFactory { - val shardCount = shardCounts.getOrDefault(member.userId, 1) - val shardId = Util.getShardFromSnowflake(member.guildId, shardCount) - - return sendFactories.computeIfAbsent(shardId % audioSendFactoryConfiguration.audioSendFactoryCount - ) { - val customBuffer = serverConfig.bufferDurationMs - val nativeAudioSendFactory: NativeAudioSendFactory - nativeAudioSendFactory = if (customBuffer != null) { - NativeAudioSendFactory(customBuffer) - } else { - NativeAudioSendFactory() - } - - AsyncPacketProviderFactory.adapt(nativeAudioSendFactory) - } - } - internal fun onSessionResumeTimeout(context: SocketContext) { resumableSessions.remove(context.resumeKey) context.shutdown() diff --git a/LavalinkServer/src/main/java/lavalink/server/io/WebSocketHandlers.kt b/LavalinkServer/src/main/java/lavalink/server/io/WebSocketHandlers.kt index 31222ef65..9080c566c 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/WebSocketHandlers.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/WebSocketHandlers.kt @@ -1,12 +1,12 @@ package lavalink.server.io +import com.sedmelluq.discord.lavaplayer.track.TrackMarker +import lavalink.server.player.TrackEndMarkerHandler import lavalink.server.util.Util +import moe.kyokobot.koe.VoiceServerInfo import org.json.JSONObject import org.slf4j.Logger import org.slf4j.LoggerFactory -import org.springframework.web.socket.WebSocketSession -import space.npstr.magma.api.MagmaMember -import space.npstr.magma.api.MagmaServerUpdate class WebSocketHandlers(private val contextMap: Map) { @@ -14,35 +14,22 @@ class WebSocketHandlers(private val contextMap: Map) { private val log: Logger = LoggerFactory.getLogger(WebSocketHandlers::class.java) } - fun voiceUpdate(session: WebSocketSession, json: JSONObject) { + fun voiceUpdate(context: SocketContext, json: JSONObject) { val sessionId = json.getString("sessionId") - val guildId = json.getString("guildId") + val guildId = json.getLong("guildId") val event = json.getJSONObject("event") - val endpoint = event.optString("endpoint") - val token = event.getString("token") + val endpoint: String? = event.optString("endpoint") + val token: String = event.getString("token") //discord sometimes send a partial server update missing the endpoint, which can be ignored. - if (endpoint == null || endpoint.isEmpty()) { - return - } + endpoint ?: return - val sktContext = contextMap[session.id]!! - val member = MagmaMember.builder() - .userId(sktContext.userId) - .guildId(guildId) - .build() - val serverUpdate = MagmaServerUpdate.builder() - .sessionId(sessionId) - .endpoint(endpoint) - .token(token) - .build() - sktContext.magma.provideVoiceServerUpdate(member, serverUpdate) + context.getVoiceConnection(guildId).connect(VoiceServerInfo(sessionId, endpoint, token)) } - fun play(session: WebSocketSession, json: JSONObject) { - val ctx = contextMap[session.id]!! - val player = ctx.getPlayer(json.getString("guildId")) + fun play(context: SocketContext, json: JSONObject) { + val player = context.getPlayer(json.getString("guildId")) val noReplace = json.optBoolean("noReplace", false) if (noReplace && player.playingTrack != null) { @@ -50,7 +37,7 @@ class WebSocketHandlers(private val contextMap: Map) { return } - val track = Util.toAudioTrack(ctx.audioPlayerManager, json.getString("track")) + val track = Util.toAudioTrack(context.audioPlayerManager, json.getString("track")) if (json.has("startTime")) { track.position = json.getLong("startTime") @@ -61,45 +48,45 @@ class WebSocketHandlers(private val contextMap: Map) { player.setVolume(json.getInt("volume")) } - player.play(track) - - val context = contextMap[session.id]!! + if (json.has("endTime")) { + val stopTime = json.getLong("endTime") + if (stopTime > 0) { + val handler = TrackEndMarkerHandler(player) + val marker = TrackMarker(stopTime, handler) + track.setMarker(marker) + } + } - val m = MagmaMember.builder() - .userId(context.userId) - .guildId(json.getString("guildId")) - .build() - context.magma.setSendHandler(m, context.getPlayer(json.getString("guildId"))) + player.play(track) - SocketServer.sendPlayerUpdate(ctx, player) + val conn = context.getVoiceConnection(player.guildId.toLong()) + context.getPlayer(json.getString("guildId")).provideTo(conn) } - fun stop(session: WebSocketSession, json: JSONObject) { - val player = contextMap[session.id]!!.getPlayer(json.getString("guildId")) + fun stop(context: SocketContext, json: JSONObject) { + val player = context.getPlayer(json.getString("guildId")) player.stop() } - fun pause(session: WebSocketSession, json: JSONObject) { - val context = contextMap[session.id]!! + fun pause(context: SocketContext, json: JSONObject) { val player = context.getPlayer(json.getString("guildId")) player.setPause(json.getBoolean("pause")) SocketServer.sendPlayerUpdate(context, player) } - fun seek(session: WebSocketSession, json: JSONObject) { - val context = contextMap[session.id]!! + fun seek(context: SocketContext, json: JSONObject) { val player = context.getPlayer(json.getString("guildId")) player.seekTo(json.getLong("position")) SocketServer.sendPlayerUpdate(context, player) } - fun volume(session: WebSocketSession, json: JSONObject) { - val player = contextMap[session.id]!!.getPlayer(json.getString("guildId")) + fun volume(context: SocketContext, json: JSONObject) { + val player = context.getPlayer(json.getString("guildId")) player.setVolume(json.getInt("volume")) } - fun equalizer(session: WebSocketSession, json: JSONObject) { - val player = contextMap[session.id]!!.getPlayer(json.getString("guildId")) + fun equalizer(context: SocketContext, json: JSONObject) { + val player = context.getPlayer(json.getString("guildId")) val bands = json.getJSONArray("bands") for (i in 0 until bands.length()) { @@ -108,21 +95,12 @@ class WebSocketHandlers(private val contextMap: Map) { } } - fun destroy(session: WebSocketSession, json: JSONObject) { - val socketContext = contextMap[session.id]!! - val player = socketContext.players.remove(json.getString("guildId")) - player?.stop() - val mem = MagmaMember.builder() - .userId(socketContext.userId) - .guildId(json.getString("guildId")) - .build() - socketContext.magma.removeSendHandler(mem) - socketContext.magma.closeConnection(mem) + fun destroy(context: SocketContext, json: JSONObject) { + context.destroy(json.getLong("guildId")) } - fun configureResuming(session: WebSocketSession, json: JSONObject) { - val socketContext = contextMap[session.id]!! - socketContext.resumeKey = json.optString("key", null) - if (json.has("timeout")) socketContext.resumeTimeout = json.getLong("timeout") + fun configureResuming(context: SocketContext, json: JSONObject) { + context.resumeKey = json.optString("key", null) + if (json.has("timeout")) context.resumeTimeout = json.getLong("timeout") } -} \ No newline at end of file +} diff --git a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java index ee24cc7c6..de120f96c 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java +++ b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java @@ -106,6 +106,7 @@ private JSONObject encodeLoadResult(LoadResult result) { exception.put("severity", result.exception.severity.toString()); json.put("exception", exception); + log.error("Track loading failed", result.exception); } return json; @@ -113,10 +114,10 @@ private JSONObject encodeLoadResult(LoadResult result) { @GetMapping(value = "/loadtracks", produces = "application/json") @ResponseBody - public CompletionStage> getLoadTracks(HttpServletRequest request, - @RequestParam String identifier) { - - log(request); + public CompletionStage> getLoadTracks( + HttpServletRequest request, + @RequestParam String identifier) { + log.info("Got request to load for identifier \"{}\"", identifier); return new AudioLoader(audioPlayerManager).load(identifier) .thenApply(this::encodeLoadResult) diff --git a/LavalinkServer/src/main/java/lavalink/server/player/Player.java b/LavalinkServer/src/main/java/lavalink/server/player/Player.java index 5ff084ef2..e2ae73973 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/Player.java +++ b/LavalinkServer/src/main/java/lavalink/server/player/Player.java @@ -30,34 +30,38 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; +import io.netty.buffer.ByteBuf; import lavalink.server.io.SocketContext; import lavalink.server.io.SocketServer; -import net.dv8tion.jda.api.audio.AudioSendHandler; +import lavalink.server.config.ServerConfig; +import moe.kyokobot.koe.VoiceConnection; +import moe.kyokobot.koe.media.OpusAudioFrameProvider; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; -import java.nio.ByteBuffer; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -public class Player extends AudioEventAdapter implements AudioSendHandler { +public class Player extends AudioEventAdapter { private static final Logger log = LoggerFactory.getLogger(Player.class); - private SocketContext socketContext; + private final SocketContext socketContext; private final String guildId; + private final ServerConfig serverConfig; private final AudioPlayer player; - private AudioLossCounter audioLossCounter = new AudioLossCounter(); + private final AudioLossCounter audioLossCounter = new AudioLossCounter(); private AudioFrame lastFrame = null; - private ScheduledFuture myFuture = null; - private EqualizerFactory equalizerFactory = new EqualizerFactory(); + private ScheduledFuture myFuture = null; + private final EqualizerFactory equalizerFactory = new EqualizerFactory(); private boolean isEqualizerApplied = false; - public Player(SocketContext socketContext, String guildId, AudioPlayerManager audioPlayerManager) { + public Player(SocketContext socketContext, String guildId, AudioPlayerManager audioPlayerManager, ServerConfig serverConfig) { this.socketContext = socketContext; this.guildId = guildId; + this.serverConfig = serverConfig; this.player = audioPlayerManager.createPlayer(); this.player.addListener(this); this.player.addListener(new EventEmitter(audioPlayerManager, this)); @@ -66,6 +70,7 @@ public Player(SocketContext socketContext, String guildId, AudioPlayerManager au public void play(AudioTrack track) { player.playTrack(track); + SocketServer.Companion.sendPlayerUpdate(socketContext, this); } public void stop() { @@ -142,33 +147,14 @@ public boolean isPaused() { return player.isPaused(); } - @Override - public boolean canProvide() { - lastFrame = player.provide(); - - if(lastFrame == null) { - audioLossCounter.onLoss(); - return false; - } else { - audioLossCounter.onSuccess(); - return true; - } - } - - @Override - public ByteBuffer provide20MsAudio() { - return ByteBuffer.wrap(lastFrame.getData()); - } - - @Override - public boolean isOpus() { - return true; - } - public AudioLossCounter getAudioLossCounter() { return audioLossCounter; } + private int getInterval() { + return serverConfig.getPlayerUpdateInterval(); + } + public boolean isPlaying() { return player.getPlayingTrack() != null && !player.isPaused(); } @@ -185,7 +171,35 @@ public void onTrackStart(AudioPlayer player, AudioTrack track) { if (socketContext.getSessionPaused()) return; SocketServer.Companion.sendPlayerUpdate(socketContext, this); - }, 0, 5, TimeUnit.SECONDS); + }, 0, this.getInterval(), TimeUnit.SECONDS); + } + } + + public void provideTo(VoiceConnection connection) { + connection.setAudioSender(new Provider(connection)); + } + + private class Provider extends OpusAudioFrameProvider { + public Provider(VoiceConnection connection) { + super(connection); + } + + @Override + public boolean canProvide() { + lastFrame = player.provide(); + + if(lastFrame == null) { + audioLossCounter.onLoss(); + return false; + } else { + return true; + } + } + + @Override + public void retrieveOpusFrame(ByteBuf buf) { + audioLossCounter.onSuccess(); + buf.writeBytes(lastFrame.getData()); } } diff --git a/README.md b/README.md index 799b41b3e..5b1d76ec7 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Allows for sending audio without it ever reaching any of your shards. Being used in production by FredBoat, Dyno, LewdBot, and more. +A [very simple example bot](Testbot) is available. + [![JDA guild](https://discordapp.com/api/guilds/125227483518861312/embed.png?style=banner2)](https://discord.gg/jtAWrzU) ## Features @@ -68,7 +70,6 @@ users about the compatibility of their clients to the Lavalink server. ## Client libraries: -### Supports 3.x and older: * [Lavalink-Client](https://github.com/FredBoat/Lavalink-Client) (JDA or generic, Java) * [LavaClient](https://github.com/SamOphis/LavaClient) (Java) * [Lavalink.py](https://github.com/Devoxin/Lavalink.py) (discord.py, Python) @@ -93,10 +94,6 @@ users about the compatibility of their clients to the Lavalink server. * [Lavalink-rs](https://gitlab.com/nitsuga5124/lavalink-rs/) (Async Libraries, Rust) * Or [create your own](https://github.com/Frederikam/Lavalink/blob/master/IMPLEMENTATION.md) -### Supports 2.x: -* [eris-lavalink](https://github.com/briantanner/eris-lavalink) (Eris, JavaScript) -* Or [create your own](https://github.com/Frederikam/Lavalink/blob/master/IMPLEMENTATION.md) - ## Server configuration Download binaries from [the CI server](https://ci.fredboat.com/viewLog.html?buildId=lastSuccessful&buildTypeId=Lavalink_Build&tab=artifacts&guest=1) or [the GitHub releases](https://github.com/Frederikam/Lavalink/releases). @@ -107,6 +104,3 @@ Run with `java -jar Lavalink.jar` Docker images are available on the [Docker hub](https://hub.docker.com/r/fredboat/lavalink/). [![Docker Pulls](https://img.shields.io/docker/pulls/fredboat/lavalink.svg)](https://hub.docker.com/r/fredboat/lavalink/) [![Docker layers](https://images.microbadger.com/badges/image/fredboat/lavalink:dev.svg)](https://microbadger.com/images/fredboat/lavalink:dev "Get your own image badge on microbadger.com") - -# Acknowledgements -This project contains modified code from https://github.com/sedmelluq/jda-nas v1.0.5 diff --git a/Testbot/build.gradle b/Testbot/build.gradle new file mode 100644 index 000000000..8c9c46d4b --- /dev/null +++ b/Testbot/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'kotlin' +apply plugin: 'application' + +group 'lavalink' +version '1.0' +mainClassName = "lavalink.testbot.TestbotKt" + +repositories { + mavenCentral() + jcenter() + maven { url 'https://jitpack.io' } +} + +dependencies { + compile ('net.dv8tion:JDA:4.1.1_135') { + exclude module: 'opus-java' + } + compile "com.github.FredBoat:Lavalink-Client:f84b333518" + compile "ch.qos.logback:logback-classic:$logbackVersion" + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} \ No newline at end of file diff --git a/Testbot/readme.md b/Testbot/readme.md new file mode 100644 index 000000000..96212c143 --- /dev/null +++ b/Testbot/readme.md @@ -0,0 +1,35 @@ +# Lavalink Testbot +This is a minimalistic example of a bot using Lavalink. This example is based upon Lavalink-Client for Java. + +https://github.com/FredBoat/Lavalink-Client + +This uses Lavaplayer to load tracks rather than initially loading tracks via Lavalink. Non-JVM (Java) bots will need +to query the `/loadtracks` endpoint. + + +## Running the test bot +This guide assumes the following: +* You have a Lavalink node already running +* You have a Discord bot and its token +* You have a shell in the same directory as this readme +* You have Java 11 or newer. Check with `java -version` + +Run this command. Gradle will download as needed, and will build and run the bot: +```bash +../gradlew run --args "YOUR_BOT_TOKEN ws://localhost:2333 youshallnotpass" +``` + +Replace token, host, and password as needed. + +Replace `../gradlew` with `../gradlew.bat` if on Windows. + + +## Using the test bot +Only one command is currently supported, and only the first or selected track of a playlist or search will be played. Example usages: +``` +;;play https://www.youtube.com/watch?v=cRh1-_pRDzo +;;play https://soundcloud.com/home-2001/resonance +;;play https://www.youtube.com/watch?v=t2D5HlKLh34&list=PLKUyqLlH6brkzzJgD6Gdriga4mdtCAMBJ +;;play ytsearch: John Coltrane Giant Steps +;;play https://www.youtube.com/watch?v=dQw4w9WgXcQ +``` \ No newline at end of file diff --git a/Testbot/src/main/kotlin/lavalink/testbot/testbot.kt b/Testbot/src/main/kotlin/lavalink/testbot/testbot.kt new file mode 100644 index 000000000..0826b261d --- /dev/null +++ b/Testbot/src/main/kotlin/lavalink/testbot/testbot.kt @@ -0,0 +1,110 @@ +package lavalink.testbot + +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler +import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager +import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist +import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import lavalink.client.io.jda.JdaLavalink +import net.dv8tion.jda.api.JDA +import net.dv8tion.jda.api.JDABuilder +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.events.ReadyEvent +import net.dv8tion.jda.api.events.StatusChangeEvent +import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter +import net.dv8tion.jda.api.requests.GatewayIntent +import net.dv8tion.jda.api.utils.cache.CacheFlag.* +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.lang.IllegalArgumentException +import java.net.URI + +private val log: Logger = LoggerFactory.getLogger("Testbot") +private lateinit var jda: JDA +private val lavalink = JdaLavalink(1) { _ -> jda } +private val playerManager = DefaultAudioPlayerManager() +lateinit var host: String +lateinit var password: String + +fun main(args: Array) { + if (args.size < 3) { + throw IllegalArgumentException("Expected 3 arguments. Please refer to the readme.") + } + + val token = args[0] + host = args[1] + password = args[2] + AudioSourceManagers.registerRemoteSources(playerManager) + + jda = JDABuilder.createDefault(token, + GatewayIntent.GUILD_MESSAGES, + GatewayIntent.GUILD_VOICE_STATES) + .disableCache(CLIENT_STATUS, ACTIVITY, CLIENT_STATUS, EMOTE, MEMBER_OVERRIDES) + .addEventListeners(Listener, lavalink) + .setVoiceDispatchInterceptor(lavalink.voiceInterceptor) + .build() +} + +object Listener : ListenerAdapter() { + override fun onStatusChange(event: StatusChangeEvent) { + log.info("{} -> {}", event.oldStatus, event.newStatus) + } + + override fun onReady(event: ReadyEvent) { + lavalink.setUserId(jda.selfUser.id) + lavalink.addNode(URI(host), password) + } + + override fun onGuildMessageReceived(event: GuildMessageReceivedEvent) { + val member = event.member ?: return + if (member.user.isBot) return + + try { + play(event.channel, member, event.message.contentRaw) + } catch (e: Exception) { + event.channel.sendMessage(e.message ?: e.toString()).queue() + } + } +} + +fun play(channel: TextChannel, member: Member, message: String) { + if (message.startsWith(";;play ")) { + val vc = member.voiceState?.channel + if (vc == null) { + channel.sendMessage("You must be in a voice channel").queue() + return + } + + val link = lavalink.getLink(channel.guild) + link.connect(vc) + + val track = message.drop(";;play ".length).trim() + playerManager.loadItem(track, object : AudioLoadResultHandler { + override fun loadFailed(e: FriendlyException) { + channel.sendMessage(e.message ?: e.toString()).queue() + } + + override fun trackLoaded(track: AudioTrack) { + channel.sendMessage("Playing `${track.info.title}`").queue() + link.player.playTrack(track) + } + + override fun noMatches() { + channel.sendMessage("No matches").queue() + } + + override fun playlistLoaded(playlist: AudioPlaylist) { + val loaded = playlist.selectedTrack ?: playlist.tracks.firstOrNull() + if (loaded == null) { + channel.sendMessage("Empty playlist").queue() + return + } + channel.sendMessage("Playing `${loaded.info.title}` from list `${playlist.name}`").queue() + link.player.playTrack(loaded) + } + }) + } +} \ No newline at end of file diff --git a/Testbot/src/main/resources/logback.xml b/Testbot/src/main/resources/logback.xml new file mode 100644 index 000000000..4bd2e3760 --- /dev/null +++ b/Testbot/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 107b1316d..351b697c9 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ buildscript { testLoggerVersion = '1.6.0' } repositories { + mavenLocal() maven { url "https://plugins.gradle.org/m2/" } maven { url 'http://repo.spring.io/plugins-release' } maven { url 'https://jitpack.io' } @@ -53,10 +54,10 @@ subprojects { ext { //@formatter:off lavaplayerVersion = '1.3.50' - lavaplayerIpRotatorVersion = '0.1.7' - magmaVersion = '0.12.5' + lavaplayerIpRotatorVersion = '0.2.3' jdaNasVersion = '1.1.0' jappVersion = '1.3.2-MINN' + koeVersion = '1.0.1' springBootVersion = "${springBootVersion}" springWebSocketVersion = '5.1.9.RELEASE' @@ -75,9 +76,3 @@ subprojects { ext { moduleName = 'Lavalink-Parent' } - -task wrapper(type: Wrapper) { - gradleVersion = '4.10.2' - //noinspection UnnecessaryQualifiedReference - distributionType = Wrapper.DistributionType.ALL -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 29953ea14..62d4c0535 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d76b502e2..622ab64a3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cccdd3d51..fbd7c5158 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6a..a9f778a7a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,8 +29,11 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @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= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -65,6 +84,7 @@ set CMD_LINE_ARGS=%* 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% diff --git a/settings.gradle b/settings.gradle index 824cc4b54..a7064b685 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,5 @@ rootProject.name = 'Lavalink-Parent' include ':Lavalink-Server' +include ':Testbot' project(':Lavalink-Server').projectDir = "$rootDir/LavalinkServer" as File