Skip to content

Commit

Permalink
Add Support for v4 and Plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
DRSchlaubi committed Jul 15, 2023
1 parent e6e6d85 commit dd538a7
Show file tree
Hide file tree
Showing 44 changed files with 1,593 additions and 86 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Created by https://www.toptal.com/developers/gitignore/api/intellij+iml,gradle,kotlin
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+iml,gradle,kotlin

Expand Down Expand Up @@ -110,7 +109,6 @@ hs_err_pid*

### Gradle ###
.gradle
build/

# Ignore Gradle GUI config
gradle-app.setting
Expand All @@ -125,7 +123,7 @@ gradle-app.setting
# gradle/wrapper/gradle-wrapper.properties

### Gradle Patch ###
**/build/
**/build/*

# End of https://www.toptal.com/developers/gitignore/api/intellij+iml,gradle,kotlin

Expand All @@ -134,3 +132,4 @@ docs/
!docs/index.html

.idea/artifacts
!**/build/generated/
13 changes: 0 additions & 13 deletions .idea/codeStyles/Project.xml

This file was deleted.

5 changes: 0 additions & 5 deletions .idea/codeStyles/codeStyleConfig.xml

This file was deleted.

2 changes: 1 addition & 1 deletion CNAME
Original file line number Diff line number Diff line change
@@ -1 +1 @@
l.mik.wtf
lavalink.kord.dev
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ player.on<TrackStartEvent> {
# Documentation

For more info please use the [example](https://github.com/DRSchlaubi/Lavakord/blob/main/example)
or [Dokka docs](https://l.mik.wtf/)
or [Dokka docs](https://lavalink.kord.dev/)

# Multiplatform

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

group = "dev.schlaubi.lavakord"
version = "4.1.0"
version = "5.0.0"

allprojects {
repositories {
Expand Down
3 changes: 3 additions & 0 deletions buildSrc/src/main/kotlin/lavalink-module.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ kotlin {
optIn("kotlin.RequiresOptIn")
optIn("kotlin.time.ExperimentalTime")
optIn("kotlinx.serialization.ExperimentalSerializationApi")
optIn("kotlin.contracts.ExperimentalContracts")
optIn("dev.schlaubi.lavakord.PluginApi")
optIn("dev.schlaubi.lavakord.UnsafeRestApi")
}
}
}
Expand Down
1 change: 0 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ kotlin {
sourceSets {
all {
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
languageSettings.optIn("dev.schlaubi.lavakord.UnsafeRestApi")
}
commonMain {
dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ package dev.schlaubi.lavakord
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
public annotation class UnsafeRestApi

/**
* Marks a declaration as part of the plugin api.
*/
@RequiresOptIn("This API is only intended to be used by plugins")
@MustBeDocumented
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
public annotation class PluginApi
13 changes: 13 additions & 0 deletions core/src/commonMain/kotlin/dev/schlaubi/lavakord/Options.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import kotlin.time.Duration.Companion.seconds
*
* @property loadBalancer configuration for the load balancer
* @property link configuration for links between lavalink and guilds
* @property plugins configuration for plugins
*/
public interface LavaKordOptions {

Expand Down Expand Up @@ -61,6 +62,7 @@ public interface LavaKordOptions {
*
* @property loadBalancer [LavaKordOptions.LoadBalancingConfig]
* @property link [LavaKordOptions.LinkConfig]
* @property plugins [LavaKordOptions.PluginsConfig]
*/
public data class MutableLavaKordOptions(
override val loadBalancer: LoadBalancingConfig = LoadBalancingConfig(),
Expand Down Expand Up @@ -90,6 +92,17 @@ public data class MutableLavaKordOptions(
return link.apply(block)
}

/**
* Applies [block] to [plugins].
*/
public inline fun plugins(block: PluginsConfig.() -> Unit): PluginsConfig {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}

return plugins.apply(block)
}

/**
* Makes this configuration immutable.
*/
Expand Down
16 changes: 12 additions & 4 deletions core/src/commonMain/kotlin/dev/schlaubi/lavakord/Plugin.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package dev.schlaubi.lavakord

import dev.arbjerg.lavalink.protocol.v4.Info
import dev.arbjerg.lavalink.protocol.v4.Plugin as RestPlugin
import dev.schlaubi.lavakord.audio.Event
import kotlinx.serialization.json.JsonElement
import dev.arbjerg.lavalink.protocol.v4.Plugin as RestPlugin

/**
* Interface for a Lavalink plugin.
Expand All @@ -21,12 +20,21 @@ public interface Plugin {
public val version: String

/**
* Op codes of events supported by this plugin.
* Op codes of events supported by [decodeToEvent].
*/
public val opCodes: List<String>
get() = emptyList()

/**
* Event types handled by [decodeToEvent].
*/
public val eventTypes: List<String>
get() = emptyList()

/**
* Converts a [JsonElement] to an event specific plugin.
*/
public fun JsonElement.decodeToEvent(): Event
public fun JsonElement.decodeToEvent(): Event {
throw UnsupportedOperationException("Plugin was registered for op code but does not provide a deserializer")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public abstract class AbstractLavakord internal constructor(
NodeImpl(serverUri, finalName, password, this)
nodesMap[finalName] = node
launch {
node.checkPlugins()
node.check()
node.connect()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import mu.KotlinLogging
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
Expand Down Expand Up @@ -62,8 +66,11 @@ internal class NodeImpl(
override val events: SharedFlow<Event>
get() = eventPublisher.asSharedFlow()

internal suspend fun checkPlugins() {
val (_, _, _, _, _, _, _, plugins) = getInfo()
internal suspend fun check() {
val (version, _, _, _, _, _, _, plugins) = getInfo()
if(version.major != 4) {
error("Unsupported Lavalink version (${version.major} on node $name")
}
val pluginMap = plugins.plugins.associate { (name, version) -> name to version }
val installedPlugins = lavakord.options.plugins.plugins
val installedPluginNames = lavakord.options.plugins
Expand Down Expand Up @@ -131,9 +138,26 @@ internal class NodeImpl(
}
}

private suspend fun onEvent(event: Message) {
LOG.trace { "Received event: $event" }
when (event) {
private suspend fun onEvent(eventRaw: JsonElement) {
LOG.trace { "Received event: $eventRaw" }
@Suppress("ReplaceNotNullAssertionWithElvisReturn")
val op = eventRaw.jsonObject["op"]!!.jsonPrimitive.content
val eventType = eventRaw.jsonObject["type"]?.jsonPrimitive?.content
val providingPlugin = lavakord.options.plugins.plugins.firstOrNull {
if (op == "event") {
eventType in it.eventTypes
} else {
op in it.opCodes
}
}
if (providingPlugin != null) {
val event = with(providingPlugin) {
eventRaw.decodeToEvent()
}
eventPublisher.tryEmit(event)
return
}
when (val event = lavakord.json.decodeFromJsonElement<Message>(eventRaw)) {
is Message.PlayerUpdateEvent -> (lavakord.getLink(event.guildId).player as WebsocketPlayer).provideState(
event.state
)
Expand Down Expand Up @@ -166,15 +190,6 @@ internal class NodeImpl(
}
}

internal companion object {
private fun generateResumeKey(): String {
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
return (1..25)
.map { allowedChars.random() }
.joinToString("")
}
}

private fun HttpRequestBuilder.addUrl() {
val resources = lavakord.gatewayClient.plugin(Resources)
url {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package dev.schlaubi.lavakord.audio.player

import dev.arbjerg.lavalink.protocol.v4.LoadResult
import dev.arbjerg.lavalink.protocol.v4.Track
import dev.schlaubi.lavakord.PluginApi
import dev.schlaubi.lavakord.audio.Event
import dev.schlaubi.lavakord.audio.EventSource
import dev.schlaubi.lavakord.audio.Node
import dev.schlaubi.lavakord.audio.RestNode
import dev.schlaubi.lavakord.checkImplementation
import dev.schlaubi.lavakord.rest.loadItem
import kotlin.time.Duration
import kotlin.time.DurationUnit
Expand Down Expand Up @@ -43,7 +48,7 @@ public interface Player : EventSource<Event> {
/**
* Directly plays a single track.
*
* **Important:** This only works if [loadItem] would return with [TrackResponse.LoadType.TRACK_LOADED], for search
* **Important:** This only works if [loadItem] would return with [LoadResult.TrackLoaded], for search
* and playlists use [loadItem]
*/
public suspend fun searchAndPlayTrack(identifier: String, playOptionsBuilder: PlayOptions.() -> Unit)
Expand Down Expand Up @@ -84,3 +89,23 @@ public interface Player : EventSource<Event> {
*/
public val filters: Filters
}

/**
* Retrieves the [RestNode] behind a [Player].
*/
@PluginApi
public val Player.node: Node
get() {
checkImplementation()
return node
}

/**
* Returns the [guildId] for a [Player].
*/
@PluginApi
public val Player.guildId: ULong
get() {
checkImplementation()
return guildId
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package dev.schlaubi.lavakord.internal

/**
* Annotation to generate utility functions for search queries.
*/
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
@Repeatable
internal annotation class GenerateQueryHelper(
val serviceName: String,
val serviceWebsite: String,
val generateSearchAndPlayFunction: Boolean,
val packageName: String,
val prefix: String,
val parameters: Array<Parameter> = [Parameter("query")],
val builderOptions: Array<Parameter> = [],
val builderFunction: String = "",
val operationName: String = "search"
) {
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
annotation class Parameter(
val name: String,
val queryName: String = "",
val kDoc: String = "",
val type: Type = Type.STRING,
val enumTypes: Array<EnumType> = []
) {
annotation class EnumType(
val name: String,
val value: String,
val kDoc: String
)

enum class Type {
STRING,
INT,
ENUM
}
}
}

/**
* Interface for all generated query builders.
*/
public interface QueryBuilder

internal fun taggedQuery(vararg parameters: Pair<String, Any?>): String = buildString {
val iterator = parameters.iterator()
while (iterator.hasNext()) {
val (name, value) = iterator.next()
if (name.isEmpty()) {
append(value)
} else if (value != null) {
append("$name:${value.toString().replace(" ", "%20")}")
}
if (iterator.hasNext() && value != null) {
append("%20")
}
}
}
Loading

0 comments on commit dd538a7

Please sign in to comment.