Git Product home page Git Product logo

kord's Introduction

Kord

Discord Download Github CI status (branch)

Kord is a coroutine-based, modularized implementation of the Discord API, written 100% in Kotlin.

If you have any feedback, we'd love to hear it, hit us up on discord or write up an issue if you have any suggestions!

Why use Kord

Kord was created as an answer to the frustrations of writing Discord bots with other JVM libraries, which either use thread-blocking code or verbose and scope restrictive reactive systems. We believe an API written from the ground up in Kotlin with coroutines can give you the best of both worlds: The conciseness of imperative code with the concurrency of reactive code.

Aside from coroutines, we also wanted to give the user full access to lower level APIs. Sometimes you have to do some unconventional things, and we want to allow you to do those in a safe and supported way.

Status of Kord

Right now, Kord should provide a full mapping of the non-voice API on Kotlin/JVM and Kotlin/JS and an experimental mapping of the Voice API on Kotlin/JVM

Documentation

Modules

Module Docs Artifact JVM JS Native²
common common kord-common¹
rest rest kord-rest¹
gateway gateway kord-gateway¹
core core kord-core¹
voice voice kord-voice ❌³
core-voice core-voice kord-core-voice

¹ These artifacts only supports Gradle Version 5.3 or higher, for older Gradle versions and Maven please append -jvm
² For Native Support please see #69
³ For Voice JS please see #69

Installation

Replace {version} with the latest version number on maven central.

For Snapshots replace {version} with {branch}-SNAPSHOT

e.g: feature-amazing-thing-SNAPSHOT for the branch feature/amazing-thing

For Snapshots for the branch main replace {version} with {nextPlannedVersion}-SNAPSHOT (see nextPlannedVersion in gradle.properties)

Download

Gradle (Kotlin)

repositories {
    mavenCentral()
    // Kord Snapshots Repository (Optional):
    maven("https://oss.sonatype.org/content/repositories/snapshots")
}

dependencies {
    implementation("dev.kord:kord-core:{version}")
}

Gradle (Groovy)

repositories {
    mavenCentral()
    // Kord Snapshots Repository (Optional):
    maven {
        url "https://oss.sonatype.org/content/repositories/snapshots"
    }
}

dependencies {
    implementation("dev.kord:kord-core:{version}")
}

Maven

Kord Snapshots Repository (Optional):
<repository>
    <id>snapshots-repo</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    <releases>
        <enabled>false</enabled>
    </releases>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>

<dependency>
    <groupId>dev.kord</groupId>
    <artifactId>kord-core-jvm</artifactId>
    <version>{version}</version>
</dependency>

FAQ

Will you support kotlin multi-platform

Currently we're supporting both Kotlin/JVM and Kotlin/JS for the majority of our API, for more information check Modules and #69

When will you document your code?

Yes.

This project is supported by JetBrains

JetBrains Logo (Main) logo

kord's People

Contributors

bartarys avatar baskerbyte avatar benwoodworth avatar bosukas avatar bytealex avatar drschlaubi avatar en-you avatar galarzaa90 avatar hopebaron avatar kimcore avatar ks129 avatar lost-illusi0n avatar lukellmann avatar matytyma avatar moire9 avatar mrbarrientosg avatar mrpowergamerbr avatar noahh99 avatar noakpalander avatar nocomment1105 avatar noituri avatar nycodeghg avatar potatopresident avatar sebastianaigner avatar sschr15 avatar tmpod avatar toxicmushroom avatar viztea avatar will421 avatar winteryfox avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kord's Issues

Restructure Packages

Description

Navigating between files in Kord is time-consuming without an IDE. We must look into restructuring those files to ease the access to related files.

Lack of String representation of core entities

Core entities must be able to be represented in a string.

If you want to send an entity in a string you will have to do this manually using formats provided by discord.
e.g:

// MessageCreateEvent 
val emoji = getGuild()?.getEmoji(...)
val stringToReply = "This is an <:$name:$id>"

We might override the toString() functions of the entity classes so that the user can achieve something like

// MessageCreateEvent 
val emoji = getGuild()?.getEmoji(...)
val stringToReply = "This is an $emoji"

Communicate Unstable API

With Discord's ever changing API we will sometimes have to add or change properties in our own lower level API.
Since this technically results in us breaking the API, it would mean that staying up to do date with Discord means a lot of minor version bumps.

Most of the changes don't propagate themselves in a breaking way through our API though. The effects are mostly limited to the serializable classes and entity data classes, which shouldn't really be used by consumers of our API in the first place.

I propose introducing @KordUnstableApi to properly inform both Kord developers and users which APIs can and are allowed to change in minor versions.

Replace companion object invoke factory functions with top-level factory functions

I suggest replacing the current pattern of creating factory constructors:

class Example(args) {

    companion object {
        operator fun invoke(args) : Example = TODO()
    }
}

With the following:

fun Example(args) : Example = TODO()

class Example(args)

There isn't any directly meaningful benefit in terms of extensibility or performance, but there is the issue of auto-completion: Intellij auto-completes the current structure as Class.invoke(args) instead of Class(args) and places these calls at a lower priority (as with all companion functions). Switching to top-level functions puts them at equal priority and without the unneeded invoke.

Other Jetbrains-owned libraries (like kotlinx.serialization) follow this pattern as well, and it does cut down on a level of nesting.
Additionally, there are some issues with bytecode generation when calling invoke() operator functions as ().

Provide full entities on events

Kord's core entities are designed to optimize caching; instead of a Guild keeping a reference to its Channels, it'll keep a reference to the channel IDs, and query the EntitySupplier to fetch them. This approach makes it easy for us to keep the cache up to date; every entity is only once in the cache for modifications, insertions and removals.

This does come with some issues however: Discord's gateway events often do provide a 'full' entity (e.g. Guild creates will contain full channels instead of core's channel IDs, as well as full members, roles, etc). For users with cache this means extra queries for data that was originally present. For users running without cache this means that data is gone completely and they have to resort to retrieval via REST, which is a major inefficiency.

An attempt to mitigate this can be seen in Message objects, which have a reference to a full user object for their authors instead of delegating retrieval to the cache. Since authors are such an important field in the entity, we decided not to separate it from the message itself, even though this does 'corrupt' the cache.

To mitigate this, we should start by providing 'full' entities in events whenever possible. These new entities should be incorporated into the current hierarchy (Behavior -> cache optimized Entity -> Full Entity) as much as possible. While this does mean a fair amount of extra classes, I believe this to be a worthwhile change for both cache and non-cache use cases, which should translate in some performance gains.

Multiprocess support

Summary

Create Remote implementations of Gateway, RequestHandler and DataCache that can comminucate rate limits between multiple Kord processes.

Goals

Make it viable for one bot token to be used over multiple Kord processes by:

  • syncing gateway send ratelimits
  • syncing gateway indentify ratelimits
  • syncing rest ratelimits
  • syncing cache data

Non-Goals

This issue does not intend to alter existing code (unless necessary), the implementation of this feature should be its own module.

Motivation

Kord technically supports multiprocess applications, but there is no support for the above goals. While this is a setup that won't be applicable for most users, it is something we should support if we wish to call Kord feature complete.

Description

Kord allows a single process to only run a subset of the suggested/required shards, but there is no support for the syncing of resources between these separate processes. This feature request intends to implement the needed parts to make this scenario relatively hassle-free.

The approach suggested is to create a RemoteGatewayClient, RemoteRequestHandlerClient, RemoteDataCacheClient and RemoteRateLimiterClient and their respective RemoteXServer variants, where clients will, based on a stream protocol, communicate the needed information with each other to remain in sync.

For everything but the DataCache, this would simply involve requesting and updating ratelimits with the server, requesting when the next action can be executed, notifying the consumption of tokens and information related like bucket rate limits. The DataCache however would require the ability to store and fetch complex data structures.

Conceptually, we're looking for something like rsocket to implement the protocol layer, although the requirement on netty, rxjava and other jvm-only libraries would mean that we'd have to find another implementation for our eventual move to multiplatform. We could simple write our own protocols on top of ktor, but the work needed for this might be out of scope. This issue will be expanded upon once we have found a suitable protocol layer.

Issue originally made by @BartArys

Make data a property in Entity

Seeing as all of our core entities has a data property we shall consider adding it to the interface.

This will serve the caching process in #1

Command DSL

Summary

Provide a usable and extensible command DSL that facilitates the creation of bots.

Goals

Support a command DSL with the following features:

  • type safe (user-made) Arguments
  • grouping of commands
  • conditional execution of commands (preconditions, 'turning commands on and off')
  • command aliases
  • command autocorrect/suggest
  • command search

Non-Goals

  • This feature shouldn't include an implementation of dependency injection,
    there are better and more mature libraries than what we could come up for this feature.

Motivation

Every bot quickly reaches a point in which it becomes beneficial to separate the functionality of different invocations into a structure that cuts down on boilerplate. As such, we should introduce such a framework for Kord users.

Description

Command structure

Conceptually, discord command libraries follow a similar pattern in how commands are structured: [prefix][command name] [arguments...].

  • The prefix is usually short as to not bother the user too much and allows the bot the easily check if a certain message is intended to be a command invocation.
  • The command name usually consists of a single word, which allows for easy and fast look-up, increasing performance.
  • Arguments are usually fixed length (save for the last argument, which is often allowed to be of variable length), this allows for fast matching and failing since there is only one possible combination of solutions.

Arguments

Arguments are a way for commands to apply restrictions on what arguments are acceptable for invocation, this saves the user from parsing them in the execution of the command (and having to return an error message). They allow for custom mapping from text to a desired type and contain some self-documenting features for help-like commands.

interface Argument<T, in CONTEXT> {
    val name: String
    val example: String

    suspend fun parse(words: List<String>, fromIndex: Int, context: CONTEXT): Result<T>
}
  • T: the type of value the argument generates (e.g. an IntArgument would generate Int and thus be an Argument<T, CONTEXT>).

  • CONTEXT: the context for which this argument was invoked, for Kord arguments the context will be a MessageCreateEvent.

  • name: the name of an argument, used for self-documenting features

  • example: an example of a valid value, used for self-documenting features

  • parse: a function that converts a set of Strings into a possible Result, a Result can either be a Success (the requirements for the argument were met) or a Failure (the requirements couldn't be met and an error-like message is given).

sealed class Result<T> {
    class Success<T>(val item: T, val wordsTaken: Int) : Result<T>()
    class Failure<T>(val reason: String, val atWord: Int) : Result<T>()
}
  • item: The generated value from the Argument.

  • wordsTaken: The amount of words taken to generate the result, this number will be added to the fromIndex and then be passed to the next command. (e.g. an IntArgument will convert the first String word to an Int, on success it'll report a Success(number, 1)).

  • reason: The reason for failure.

  • atWord: At which word the argument failed, this is in relation to the fromIndex. (e.g. an IntArgument will report a Failure("expected an integer number", 0)).

Extensions

While the types of input are often very limited (mostly: text, number, mention), The rules of those values often aren't.
Arguments and Results should be easily extensible so that rules can be easily applied on top of existing Arguments.

for example:

fun <T: Any> Result<T>.optional(): Result.Success<T?> = when(this) {
    is Result.Success -> this as Result.Success<T?>
    else -> Result.Success(null, 0)
}

fun <T : Any, CONTEXT> Argument<T, CONTEXT>.optional(): Argument<T?, CONTEXT> = object :
        Argument<T?, CONTEXT> by this as Argument<T?, CONTEXT> {

    override suspend fun parse(words: List<String>, fromIndex: Int, context: CONTEXT): Result<T?> {
        return this@optional.parse(words, fromIndex, context).optional()
    }

}

The steps of command creation

To allow for a configurable and extensible design, we should discover all steps in the process of making commands, and allow users to hook into those steps;

  • CommandBuilder modification
  • CommandBuilder building
CommandBuilder modification

Module builders are implicitly created as a 'blank slate' the first time they are requested and are returned for every subsequent get,
this ensure that there is no collision between two modules with the same name (they will be the same module) and allows easy modification of modules you do not own.

interface ModuleModifier {

    suspend fun apply(container: ModuleContainer)

} 

class ModuleContainer() {

    operator fun get(name: String): ModuleBuilder<*, *, *> 

    operator fun String.unaryMinus()
    fun remove(module: String) 

    inline fun apply(name: String, consumer: (ModuleBuilder<*, *, *>) -> Unit)

    fun forEach(consumer: suspend (ModuleBuilder<*, *, *>) -> Unit)
}
CommandBuilder building

The builder needs to be build into a Command. There's no real room for configuration here since the behaviour of a command is fixed.

The steps of command invocation

To allow for a configurable and extensible design, we should discover all steps in the process of invoking commands, and allow users to hook into those steps;

  • before parsing
  • after parsing/before invoke
  • on invoke
  • after invoke
before parsing

By introducing control flow before any work is being done, we can efficiently reject events with minimal overhead. (used for ignoring certain users, bots, etc). Filters that operate on this level will fail silently.

after parsing/before invoke

A second kind of control flow will operate right before a command is invoked. Filters on this level will have access to the parsed arguments (depending on where the filter was created) and should mostly be used to verify internal state that's required over multiple commands. Filters that operate on this level won't fail silently.

on invoke

Most useful for logging, this event won't be allowed to influence the control flow of the command.

after invoke

After a command is finished, there might be some cleanup left to do, the most common application for these kinds of events would be to delete the caller's message.

Making extendable commands

Creating ways to interact with commands is one step of the process, the other will be to create a way to introduce data to be processed in the steps of command creation. Since inserting properties on an instance level is impossible in Kotlin, we're required to go about this slightly differently.

Metadata

Similarly to how JSON libraries represent properties as key->value maps, Kord could introduce a key->value map to introduce data about the command, for later processing or runtime usage:

interface Metadata {
    interface Key<T>

    operator fun<T> get(key: Key<T>) : T?
}

during the building and modifying of commands:

interface MutableMetadata : MetaData {
    operator fun<T> set(key: Metadata.Key<T>, value: T)
}

A simple example of inserting a flag in a command:

object EnsureGuild: Metadata.Key<Boolean>

fun CommandConfiguration.ensureGuild(enable: Boolean = true) {
    metaData[EnsureGuild] = enable
}

//mocked code example
fun myCommand() = command("test") {
    ensureGuild()
    execute {
        //....
    }
}

Then during the processing of commands, an extra precondition can be added that will reject events if they weren't executed in a guild.

Kord could modify all its behaviour in commands to be build upon metadata. The execution of commands could be written as follows:

object Execute : Metadata.Key<Pair<ArgumentCollection<*>, suspend CommandEvent<*>.() -> Unit>>

fun <T> CommandConfiguration.invoke(collection: ArgumentCollection<T>, block: suspend CommandEvent<T>.() -> Unit) {
    val pair = collection to block
    metaData[Execute] =  pair as Pair<ArgumentCollection<*>, suspend CommandEvent<*>.() -> Unit>
}

//mocked code example
fun command() = command("test") {

    invoke(arguments(IntArgument, IntArgument)) {
        //....
    }

}

Issue originally made by @BartArys

Missing builder DSL for permission overwrites

Editing entities with permission overwrites requires users to manually create an Overwrite object, which is a lower level API object. These calls should be replaced with the following DSLs:

inline fun memberOverwrite(id: Snowflake, builder: PermissionOverwriteBuilder.() -> Unit) : Overwrite
    = PermissionOverwriteBuilder(type = "member", id = id).apply(builder).toOverwrite()

inline fun roleOverwrite(id: Snowflake, builder: PermissionOverwriteBuilder.() -> Unit): Overwrite
    = PermissionOverwriteBuilder(type = "role", id = id).apply(builder).toOverwrite()

These builder functions should also be present in all [X]Builder classes that contain a MutableList<Overwrite>.
They should be added as addMemberOverwrite and addRoleOverwrite.

Add core support for getting invites

Kord core doesn't currently support getting invites per code, although rest does provide the endpoint.

There are some issues with this though, as we currently have an invite entity that doesn't fit the data.

For one, the invite isn't always spawned from a guild, so that has to be nullable.

Another issue is that we can't get the guild from rest or cache, as the bot will most likely not be part of the guild,
so we'll have to provide a separate class to represent the partial guild data.

Issue originally made by @BartArys

Embed color is not preserved during edit

val message = channel.createEmbed {
  color = ...
  description = ...
}

message.edit {
  embed {
    description = ...
  }
}

Expectation is that only the description changes. However, the color will also revert to black. Adding color = ... to edit { embed } fixes this

Add contracts to Kord with Kotlin 1.4

This issue serves as a note for Kotlin's 1.4 widening of contract support and will be evaluated on its release.

With contract support for final members introduced in 1.4. Previously, contracts were only allowed for extension functions, which would only serve about half of the places where it would be useful.

A sure improvement would be the introduction of contract { callsInPlace(block, EXACTLY_ONCE) } in our DSL, which would make the following possible:

val eventuallyInitialized: String
channel.createMessage {
    eventuallyInitialized = "value"
    //....
}
println(eventuallyInitialized)

Issue originally made by @BartArys

Differentiate between missing and null in lower level APIs

Why

Discord's API makes a distinction between fields that can be serialized with a value, with null or be missing from the serialized entity. We had originally theorized that reducing both null and missing to Kotlin's null (and thus making the type T?) would suffice.

However, certain use cases were overlooked. The most notable one is trying to figure out changes between two versions of an entity. Some Discord events provide an update as an overwrite of the entity (like Member Update). Here, the distinction between null and missing is trivial since we have a full old and new entity, so any difference is guaranteed to be a change. (value -> null means a removal of that property)
Other events (like Message Update) provide an entity as a 'delta', only providing the fields that changed. Here, it's much harder to find out if a field that went from a value to null meant that value became null or in fact hasn't changed at all.

How

Since Kotlin's built-in nullability is unable to cover this use-case, we'll have to go look elsewhere for an implementation. We're looking for a way to encode three states into one type: value, null and missing.

java.util.Optional

A possible solution could be java.util.Optional.
Providing Optional<T>? covers the value|missing|null case and can be specialized in Optional<T> for value|missing, value|null can still be represented as T?.

Asides from us trying to avoid any dependency on JVM classes, this also doesn't intuitively explain the difference between Optional.empty and Optional?. Other issues are:

  • Impossible to have Optional<T?>, an optional cannot contain null
  • Clunky to interact with: to unwrap Optional<T>? you'd use optional?.orElse(null)
  • Not serializable by default: this can be remedied by providing the context with a custom KSerializer, but it remains a potential source of issues.

Our own optional

Another solution is providing our own implementation of Optional where each state is provided as sealed class implementation:
Optional.Null, Optional.Value and Optional.Missing

In a similar fashion Optional<T?> would cover the case of value|missing|null and can be specialized in Optional<T> for value|missing, value|null can still be represented as T?. However, it does seem more intuitive to figure out the meaning of the different states.

In addition, this does not suffer from the previous problems:

  • Optional<T?> is possible, In addition we can forbid Optional.Value<T: Any?> and force Optional.Null<T> to be nullable (Optional.Null<T> : Optional<T?>)
  • Easier to interact with, getting the value of an optional should be as simple as value: T? = optional.value and can be null safe if smart-casted to Optional.Value.
  • Able to provide our own built-in serializer.
  • Multiplatform compatible

To me it seems like we'll be able to gain most from going with the second option, the implementation should be low-maintenance anyway as I doubt the format would change much.

Generics and performance

Because of type erasure on the JVM, wrapping otherwise primitive types inside of Optional would result in them being boxed instead. However, Int? (what we are currently using) will also be represented as a boxed type, the introduction of Optional won't lead to a performance regression. In fact, this might be a source of performance gains.

Java libs usually deal with this by creating special primitive variants (see OptionalInt), the drawback is that these types cannot share a common hierarchy with their generic counterparts, so each primitive variant introduced leads to an exponential growth in code needing to handle these types.

Fortunately for us, there are only 3 primitive data types in the Discord API that are nullable:

  • int(Int)
  • snowflake(Long)
  • boolean(Boolean)

Introducing OptionalInt, OptionalLong and OptionalBoolean could thus be a potential source of performance.

Note that the places where these types would be applicable are smaller than one would initially assume; the primitive type has to be value|missing, we lose any potential performance gain if the type also becomes nullable (this would result in boxing). That means that primitive Optionals can only have two states: Optional.Missing and Optional.Value.

Why only lower level APIs ?

The concept of value|null|missing is foreign to Kotlin and does not play well with its built-in nullable types, the motivation to not include it in the core Entity types is the same behind not including optionals in the first place; I'm not convinced users care about a value being missing vs being null in most scenarios, just if the the value is present to interact with. Having everything up until and including xData classes use optionals should be sufficient for the use cases we forgot about initially. From there on out we (and users) can introduce their own structures that allow comparisons between two or more 'states' of the same entity.

Requesting guild members with presences throw exceptions

Code:

gateway.send(RequestGuildMembers(guildId = listOf(guild), presences = true))

Stacktrace:

kotlinx.serialization.MissingFieldException: Field 'afk' is required, but it was missing
	at com.gitlab.kordlib.gateway.Presence.<init>(Command.kt) ~[?:?]
	at com.gitlab.kordlib.gateway.Presence$$serializer.deserialize(Command.kt) ~[?:?]
	at com.gitlab.kordlib.gateway.Presence$$serializer.deserialize(Command.kt:112) ~[?:?]
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:52) ~[?:?]
	at kotlinx.serialization.json.internal.StreamingJsonInput.decodeSerializableValue(StreamingJsonInput.kt:33) ~[?:?]
	at kotlinx.serialization.builtins.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:63) ~[?:?]
	at kotlinx.serialization.internal.ListLikeSerializer.readElement(CollectionSerializers.kt:87) ~[?:?]
	at kotlinx.serialization.internal.AbstractCollectionSerializer.readElement$default(CollectionSerializers.kt:54) ~[?:?]
	at kotlinx.serialization.internal.AbstractCollectionSerializer.patch(CollectionSerializers.kt:36) ~[?:?]
	at kotlinx.serialization.internal.AbstractCollectionSerializer.deserialize(CollectionSerializers.kt:45) ~[?:?]
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:52) ~[?:?]
	at kotlinx.serialization.json.internal.StreamingJsonInput.decodeSerializableValue(StreamingJsonInput.kt:33) ~[?:?]
	at kotlinx.serialization.Decoder$DefaultImpls.decodeNullableSerializableValue(Decoding.kt:231) ~[?:?]
	at kotlinx.serialization.json.JsonInput$DefaultImpls.decodeNullableSerializableValue(JsonInput.kt) ~[?:?]
	at kotlinx.serialization.json.internal.StreamingJsonInput.decodeNullableSerializableValue(StreamingJsonInput.kt:16) ~[?:?]
	at kotlinx.serialization.builtins.AbstractDecoder.decodeNullableSerializableElement(AbstractDecoder.kt:65) ~[?:?]
	at com.gitlab.kordlib.gateway.GuildMembersChunkData$$serializer.deserialize(Command.kt) ~[?:?]
	at com.gitlab.kordlib.gateway.GuildMembersChunkData$$serializer.deserialize(Command.kt:92) ~[?:?]
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:52) ~[?:?]
	at kotlinx.serialization.json.internal.StreamingJsonInput.decodeSerializableValue(StreamingJsonInput.kt:33) ~[?:?]
	at kotlinx.serialization.builtins.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:63) ~[?:?]
	at com.gitlab.kordlib.gateway.Event$Companion.getByDispatchEvent(Event.kt:104) ~[?:?]
	at com.gitlab.kordlib.gateway.Event$Companion.deserialize(Event.kt:57) ~[?:?]
	at com.gitlab.kordlib.gateway.Event$Companion.deserialize(Event.kt:29) ~[?:?]
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:52) ~[?:?]
	at kotlinx.serialization.json.internal.StreamingJsonInput.decodeSerializableValue(StreamingJsonInput.kt:33) ~[?:?]
	at kotlinx.serialization.DecodingKt.decode(Decoding.kt:521) ~[?:?]
	at kotlinx.serialization.json.Json.parse(Json.kt:131) ~[?:?]
	at com.gitlab.kordlib.gateway.DefaultGateway.read(DefaultGateway.kt:193) ~[?:?]
	at com.gitlab.kordlib.gateway.DefaultGateway$readSocket$$inlined$collect$1.emit(Collect.kt:134) ~[?:?]
	at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) ~[?:?]
	at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt) ~[?:?]
	at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:73) ~[?:?]
	at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:55) ~[?:?]
	at com.gitlab.kordlib.gateway.DefaultGateway$asFlow$1.invokeSuspend(DefaultGateway.kt:223) ~[?:?]
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[?:?]
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56) ~[?:?]
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) ~[?:?]
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738) ~[?:?]
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) ~[?:?]
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) ~[?:?]

Exception after receiving DM (kord-core)

The client after receiving message event throws NPE at
com.gitlab.kordlib.core.gateway.handler.MessageEventHandler.handle(MessageEventHandler.kt:57)

From what I checked, guildId.value!! might be the cause as guildID is not returned in DMs.

Track open Discord PR's to implement

This issue exists to track PRs/issues on the discord-api-docs repository that document API changes/additions/removals.
Implementing these isn't a priority until they get merged to the main branch. Feel free to comment on this issue or notify one of the devs on discord If you find any open PRs/issues that are not tracked in this list.

"DiscordInteraction" doesn't work in Direct Messages

On a direct message, the member and guild_id are null.

Guild:

{"channel_id":"513405772911345664","data":{"id":"812846584084955136","name":"online"},"guild_id":"268353819409252352","id":"814909719054647316","member":{"deaf":false,"is_pending":false,"joined_at":"2017-01-10T12:22:16.323000+00:00","mute":false,"nick":null,"pending":false,"permissions":"4294967295","premium_since":null,"roles":["297051132793061378","334711955736625185","401353261266763778","650869752524439563","693210200496144424"],"user":{"avatar":"8bd2b747f135c65fd2da873c34ba485c","discriminator":"4185","id":"123170274651668480","public_flags":131712,"username":"MrPowerGamerBR"}},"token":"-","type":2,"version":1}

Direct Message:

{"channel_id":"637662290170216458","data":{"id":"812857383717699626","name":"online"},"id":"814909846746431539","token":"-","type":2,"user":{"avatar":"8bd2b747f135c65fd2da873c34ba485c","discriminator":"4185","id":"123170274651668480","public_flags":131712,"username":"MrPowerGamerBR"},"version":1}

This causes errors when trying to parse a command received via direct message.

Fetching voice state of a member that has been moved throws an exception

Code

member.getVoiceStateOrNull()

Stacktrace

[08:31:25 WARN]: Exception in thread "Server thread" java.lang.IllegalStateException: Expected only one element
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.FlowKt__ReduceKt$singleOrNull$$inlined$collect$1.emit(Collect.kt:133)
[08:31:25 WARN]: 	at com.gitlab.kordlib.cache.map.internal.ActionQuery$asFlow$$inlined$fold$lambda$1$1.emit(Collect.kt:138)
[08:31:25 WARN]: 	at com.gitlab.kordlib.cache.map.internal.Action$ValueAction$onMap$$inlined$filter$1$2.emit(Collect.kt:139)
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt)
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:73)
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:55)
[08:31:25 WARN]: 	at com.gitlab.kordlib.cache.map.MapLikeCollection$Companion$from$1$values$1.invokeSuspend(MapLikeCollection.kt:89)
[08:31:25 WARN]: 	at com.gitlab.kordlib.cache.map.MapLikeCollection$Companion$from$1$values$1.invoke(MapLikeCollection.kt)
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:60)
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:210)
[08:31:25 WARN]: 	at com.gitlab.kordlib.cache.map.internal.Action$ValueAction$onMap$$inlined$filter$1.collect(SafeCollector.kt:127)
[08:31:25 WARN]: 	at com.gitlab.kordlib.cache.map.internal.ActionQuery$asFlow$$inlined$fold$lambda$1.collect(SafeCollector.kt:127)
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.FlowKt__ReduceKt.singleOrNull(Reduce.kt:153)
[08:31:25 WARN]: 	at kotlinx.coroutines.flow.FlowKt.singleOrNull(Unknown Source)
[08:31:25 WARN]: 	at com.gitlab.kordlib.cache.api.Query$DefaultImpls.singleOrNull(Query.kt:40)
[08:31:25 WARN]: 	at com.gitlab.kordlib.cache.map.internal.ActionQuery.singleOrNull(ActionQuery.kt:15)
[08:31:25 WARN]: 	at com.gitlab.kordlib.core.behavior.MemberBehavior$DefaultImpls.getVoiceStateOrNull(MemberBehavior.kt:160)
[08:31:25 WARN]: 	at com.gitlab.kordlib.core.entity.Member.getVoiceStateOrNull(Member.kt:23)

Make Snowflake not inline

This issue will serve mostly as a note, changes for Snowflakes aren't planned for any immediate version but are something that should be discussed eventually.

Why were Snowflakes inline

Inline classes allow us to refine the API without suffering runtime performance. Discord's Snowflakes can be optimized to Long but they are so present in our API and meaningfully different from a standard Long that they deserve a special type. Wrapping a Long in an inline Snowflake gives us the desired effect for free. At the time, it seemed like a decision with no meaningful drawbacks.

Why shouldn't they be inline anymore

Inline classes are still an experimental feature, they don't perfectly integrate with some parts of the API. One of those is serialization. Our lower level API is a direct representation of Discord's API and as such is serializable, being able to use Snowflake for ids across our entire API would go a long way in increasing the user experience.

Another issue is type casting. Kordx.commands uses class casting which isn't fully supported either, locking us out of providing Snowflake arguments.

We're certain that the Kotlin team will eventually fix these issues, but progress has been slower than expected. We're currently at < 1.0 so we're allowed to make a drastic change as de-inlining Snowflake but we have to do so keeping in mind that we will eventually inline Snowflakes again and will only be able to do so when these issues are solved again (or risk breaking some user code for issues similar to the one's we've experience up until now).

What is the performance cost of non-inline Snowflakes

Potentially rather small, actually. I haven't run any tests personally, but Snowflakes are only created on the demand of user code, we cache Snowflakes as Longs because of the current issues with inline classes.

If however, we de-inline Snowflake we would be free to use them in our lower level APIs. That would make them present in nearly every gateway event coming through, increasing memory consumption for some of the bigger bots using the API.


Addendum: Is the process of removing the inline modifier from a class called 'outlining'?

Issue originally made by @BartArys

Ignore PRESENCES_REPLACE event

PRESENCES_REPLACE is an event that fires to update the client's friend list. Since bots can't have friends (😔) this event is completely useless, which is proven by the fact that the payload is an empty array.

Why bots receive this event is one of humanity's greatest mysteries. Nonetheless, we should ignore this event moving forward.

Trying to mention the everyone role results in a malformed @@everyone mention in the client

channel.createMessage(guild.everyoneRole.mention)

results in:
@@everyone

This behaviour on is correct according the official docs, but it seems like the client stumbles over the special everyone type. Using the literal @everyone works fine though.

Proposed solution:
Introduce a check on RoleBehavior#mention that checks for guildId.value == id.value and returns the literal @everyone on true instead.

Add library support to resolve cdn image to given resolution

As documented in the docs, it is possible to request different resolution of the Images sent by discord by appending a get parameter ?size=desired_size where desired_size can be between 16 and 4096 (under power of two like 128, 256, 512, etc.).

This issue is created to address this enchantment in the API.

Proposed solution:
Introduce a new enum class similar to Format under Image, which specify the image resolution.

"Tiered" channel edit builders

Currently, only the bottom channel types have a .edit {} function, but there are sets of parameters that are shared by multiple channel types and some of those channel types are already grouped together - for example, all guild channels have a position.

It would be nice if these higher types also had edit builders so that you could change these channel attributes without having to do a bunch of casting.


image

Ktor error on REST requests: kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for AwaitContinuation

https://canary.discordapp.com/channels/556525343595298817/587324906702766226/752957191341867009

Exception in thread "DefaultDispatcher-worker-2" kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for AwaitContinuation(DispatchedContinuation[Dispatchers.Default, Continuation at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:101)@1b0b8cf0]){HttpResponseData=(statusCode=200 OK)}@51a22686. Please read KDoc to 'handleFatalException' method and report this incident to maintainers
at kotlinx.coroutines.DispatchedTask.handleFatalException$kotlinx_coroutines_core(DispatchedTask.kt:93)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:64)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.lang.ClassCastException: class kotlin.coroutines.jvm.internal.CompletedContinuation cannot be cast to class kotlinx.coroutines.DispatchedContinuation (kotlin.coroutines.jvm.internal.CompletedContinuation and kotlinx.coroutines.DispatchedContinuation are in unnamed module of loader 'app')

Kotlin/kotlinx.coroutines#2215 (comment)

The root cause of this issue seems to be a bug in kotlinx.coroutines.
1.4.0 did not regress comparing with 1.4.0-RC, the only change required to fix this is to rollback Ktor version back to 1.3.2-1.4.0-rc from 1.4.0.
Last Ktor release has changed coroutines pattern usage and AwaitAll bug started to reproduce on a regular basis

Make Kord builder no longer suspend

Kord's builder DSL suspends to figure out the recommended amount of shards from Discord's REST API.
When it was originally introduced, it was motivated as a way to automatically scale shards without input from the user.
The truth is however, that most bot authors won't be running Kord bots on 1000+ guilds. Most will run a select few dedicated guilds instead, and those running on more won't be using a single kord instance for all their bots anyway.

As such, we should transform the builder to no longer suspend by default and assume a default shard config (1 shard, index 0) that can modified by the user, and allow them to call the suspending function manually. This makes building Kord nicer for third APIs and has the added bonus of skipping an HTTP request, speeding up the startup.

Disable direct API tests for non-master merges

Certain sanity tests are ran against the actual discord API to minimize the chance of bugs. While we still consider this a good strategy moving forward, we should minimize those tests to being ran only before a merge to master. This comes with a downside of not immediately being able to tell a bug on our part, but makes it many times easier for contributors to provide changes.

Issue originally posted by @BartArys

Improve communication of Kord 'ready' states

Bot authors may want to wait for Kord to have done its initial setup, this'll mostly involve waiting until the Gateway has send all initial cacheable data. They may incorrectly come to the conclusion that the ReadyEvent fits their needs.
Kord's ReadyEvent is a representation of Discord's Gateway READY event and indicates that the gateway is ready to accept commands.

To clear up this confusion, and introduce the missing functionality, I suggest the following changes:

  • Rename ReadyEvent to GatewayReadyEvent to better communicate the intention of this event.
  • Introduce a KordReadyEvent to signal the completion of initial gateway traffic.

Furthermore, we may want to make this KordReadyEvent 'pluggable', to allow certain actions like fetching members via the gateway to cache them before this event gets fired.

Nullability issue when deserializing embeds on message create

Hi there,

I am using Kord and when receiving messages with embeds in them, the deserializer seems to require a field to be non-null, which seems to be nullable on Discord's end.

It seems as though an incorrect type in the documentation of Discord's API is to blame.
When deserializing the Provider of an embed, Discord does send a null value (in this case for the url), even though the documentation states, that the API will either send this field or not. It is not annotated to be nullable: https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure

I have added the log of my bot below:

FINEST 2021-01-02T16:09:22.787+01:00 [dev.kord.gateway.DefaultGateway read]: Gateway <<< {"t":"MESSAGE_CREATE","s":3,"op":0,"d":{"type":0,"tts":false,"timestamp":"2021-01-02T15:09:22.288000+00:00","referenced_message":null,"pinned":false,"nonce":"794945439483822080","mentions":[],"mention_roles":[],"mention_everyone":false,"member":{"roles":["602449218765848576","595757090904211457","598614486810886145","559421263303540756","579389392867557400"],"premium_since":null,"pending":false,"nick":"GiantTree 🌳�","mute":false,"joined_at":"2019-03-24T16:37:57.753000+00:00","is_pending":false,"hoisted_role":"559421263303540756","deaf":false},"id":"794945440084394004","flags":0,"embeds":[{"url":"https://www.reddit.com/r/pcmasterrace/comments/koep08/well_it_hapenned_to_me/","type":"link","title":"r/pcmasterrace - well it hapenned to me","thumbnail":{"width":1280,"url":"https://external-preview.redd.it/4Wx7Qk-RDRF92qFRVRroSbZJBd5fyc-NhYBaoeTxexI.png?format=pjpg&auto=webp&s=d8a739cfd37c84d714b8c44c49f441345ffd7719","proxy_url":"https://images-ext-2.discordapp.net/external/wo4g700-48jJTdXK2FXa61-w46oO7T_ZbcPCs0fP_Cw/%3Fformat%3Dpjpg%26auto%3Dwebp%26s%3Dd8a739cfd37c84d714b8c44c49f441345ffd7719/https/external-preview.redd.it/4Wx7Qk-RDRF92qFRVRroSbZJBd5fyc-NhYBaoeTxexI.png","height":720},"provider":{"url":null,"name":"reddit"},"description":"34,735 votes and 409 comments so far on Reddit","color":16777215}],"edited_timestamp":null,"content":"https://www.reddit.com/r/pcmasterrace/comments/koep08/well_it_hapenned_to_me/","channel_id":"565872054059008009","author":{"username":"GiantTree �","public_flags":128,"id":"103595873841188864","discriminator":"1337","avatar":"6bd97d13e277373bb0c900b02dde064a"},"attachments":[],"guild_id":"559415621956141076"}}
SEVERE 2021-01-02T16:09:22.798+01:00 [io.ktor.util.LoggingKt error]: descriptor for kotlin.String was not nullable but null mark was encountered
kotlinx.serialization.SerializationException: descriptor for kotlin.String was not nullable but null mark was encountered
	at dev.kord.common.entity.optional.Optional$OptionalSerializer.deserialize(Optional.kt:160)
	at dev.kord.common.entity.optional.Optional$OptionalSerializer.deserialize(Optional.kt:151)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:33)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:41)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:63)
	at dev.kord.common.entity.DiscordEmbed$Provider$$serializer.deserialize(DiscordMessage.kt)
	at dev.kord.common.entity.DiscordEmbed$Provider$$serializer.deserialize(DiscordMessage.kt:480)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:33)
	at dev.kord.common.entity.optional.Optional$OptionalSerializer.deserialize(Optional.kt:173)
	at dev.kord.common.entity.optional.Optional$OptionalSerializer.deserialize(Optional.kt:151)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:33)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:41)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:63)
	at dev.kord.common.entity.DiscordEmbed$$serializer.deserialize(DiscordMessage.kt)
	at dev.kord.common.entity.DiscordEmbed$$serializer.deserialize(DiscordMessage.kt:392)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:33)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:41)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:63)
	at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:479)
	at kotlinx.serialization.internal.ListLikeSerializer.readElement(CollectionSerializers.kt:80)
	at kotlinx.serialization.internal.AbstractCollectionSerializer.readElement$default(CollectionSerializers.kt:51)
	at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:36)
	at kotlinx.serialization.internal.AbstractCollectionSerializer.deserialize(CollectionSerializers.kt:43)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:33)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:41)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:63)
	at dev.kord.common.entity.DiscordMessage$$serializer.deserialize(DiscordMessage.kt)
	at dev.kord.common.entity.DiscordMessage$$serializer.deserialize(DiscordMessage.kt:63)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:33)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:41)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:63)
	at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:479)
	at dev.kord.gateway.Event$Companion.getByDispatchEvent(Event.kt:132)
	at dev.kord.gateway.Event$Companion.deserialize(Event.kt:72)
	at dev.kord.gateway.Event$Companion.deserialize(Event.kt:39)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:33)
	at kotlinx.serialization.json.Json.decodeFromString(Json.kt:85)
	at dev.kord.gateway.DefaultGateway.read(DefaultGateway.kt:191)
	at dev.kord.gateway.DefaultGateway$readSocket$$inlined$collect$1.emit(Collect.kt:134)
	at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:61)
	at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(Channels.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

This issue prevents me from handling messages with embeds (i.e. due to embedded links) added to them at all.

Status of voice

This issue will function as a overview of introducing voice:

  • Heart beating
  • IP Discovery
  • Audio formatting (Lava Player)
  • Resuming
  • UDP connection and audio forwarding to channels

Stop using java.awt.Color

Kord uses java.awt.Color to represent colors. This issue suggests moving to our own/a multiplatform implementation.

Why switch from java's implementation

  • People using jlink to reduce the size of the JRE are forced to include the java.desktop module for what in our use case is a glorified integer. It comes down to ~30 megabytes which would be nice to shave off.
  • We'll move to multiplatform eventually, and will be required to implement a Color regardless (or risk depending on an npm module!). We might as well make one that fits all platforms.

Isn't this outside of Kord's scope

It is, technically, outside of our scope. A color class is not related to the Discord API. A simple Color(r,g,b) should be sufficient though. The alternative would be to just accept an integer, which would be a dramatically worse user experience.

Issue originally made by @BartArys

Preserve at least one non-snapshot version

The latest released version appears to be 0.6.10, but this is not resolvable (including by the plugin in the readme). Shouldn't the latest released version be preserved in distribution hubs?

"DiscordInteractionGuildMember" is missing "pending" and "is_pending" flags

{"channel_id":"513405772911345664","data":{"id":"812846584084955136","name":"online"},"guild_id":"268353819409252352","id":"814909719054647316","member":{"deaf":false,"is_pending":false,"joined_at":"2017-01-10T12:22:16.323000+00:00","mute":false,"nick":null,"pending":false,"permissions":"4294967295","premium_since":null,"roles":["297051132793061378","334711955736625185","401353261266763778","650869752524439563","693210200496144424"],"user":{"avatar":"8bd2b747f135c65fd2da873c34ba485c","discriminator":"4185","id":"123170274651668480","public_flags":131712,"username":"MrPowerGamerBR"}},"token":"-","type":2,"version":1}

I have no idea why there are both flags, but they are there when receiving a Discord Interaction.

is_pending is being deprecated

Kord only starts one shard

Code to reproduce:

    val kord = Kord(args.firstOrNull() ?: error("token required")) {
        sharding { (0..1) }
    }

    kord.on<MessageCreateEvent> {
        if (message.author?.isBot == true) return@on
        if (message.content == "!ping") message.channel.createMessage("pong")
    }

    kord.login { playing("!ping to pong") }

See #194 for more

Exception when forcefully killing process in linux

Process is started using builded jar. Then killed with pkill -f 'java -jar'. After that, the following exception is thrown. It's not really breaks functionality or something, since i kill process anyway, but I don't think it should be there either (good thing to remove from CI log)

root@2zvn: pkill -f 'java -jar'                                                                       
Exception in thread "main" java.lang.ClassCastException: class com.gitlab.kordlib.gateway.State$Detached cannot be cast to class com.gitlab.kordlib.gateway.State$Running (com.gitlab.kordlib.gateway.State$Detached and com.gitlab.kordlib.gateway.State$Running are in unnamed module of loader 'app')                                                         
        at com.gitlab.kordlib.gateway.DefaultGateway$start$2.invokeSuspend(DefaultGateway.kt:143)                                  
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)                                  
        at kotlinx.coroutines.internal.ScopeCoroutine.afterResume(Scopes.kt:32)                                                    
        at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:113)                                               
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)                                  
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)                                                             
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)                                   
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)                          
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)                            
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)   

Use kotlinx-datetime

The madlads over at Jetbrains just released another banger library that brings Java's DateTime API to kotlin-multiplatform. Since our project's only real remaining dependency on the Java stdlib is the datetime API we can use this to yeet that stuff out of there and then realize that we just imported megabytes of typealiases. It'll be grand.

Note that kotlinx-datetime does not typealias java.time. As such, we should leave the external API using java time intact, simply providing kotlin's version as an alternative, and remove java time as an internal dependency.

Cache conflict when using Guilds and GuildMembers intents together

I've attached a project that just connects and prints the member list from the ready event.

On the discord end, I've turned on the GUILD_MEMBERS flag.

I'm finding that if I configure Kord with just +Intent.GuildMember then I get all the members back from guild.members against my test server. But if I add +Intent.Guild it only returns the bot's own user.

	val kord = Kord(botToken) {
		intents {
			+Intent.Guilds // If uncommented then the full member list isn't seen
			+Intent.GuildMembers
		}
	}

It's weird to turn on an intent and get less data. The difference seems to be that Guild is causing the cache to be initialized prematurely. I'm wondering if that's supposed to happen or how I should work around it. This is kind of a one-off; I don't necessarily want the whole member list in cache, but if I ask for it, it's surprising if I don't get it. I guess I can use RestEntitySupplier directly...

Lots of places lack full documentation

Summary

Currently the project lacks full documentation with description and informative block tags in lots of places.

Goal

I open this issue with the intent of starting to fully document the whole project, little by little.

Motivation

Documentation is key to allow developers to explore the library on their own making it easier and faster to write better code.

Relevant resources

Documenting Kotlin Code
Dokka

/label ~Enhancement

**Issue originally posted by @Tmpod

Exception in thread "main" java.lang.NoSuchMethodError

Stacktrace: 
Exception in thread "main" java.lang.NoSuchMethodError: java.io.ByteArrayOutputStream.toString(Ljava/nio/charset/Charset;)Ljava/lang/String;
	at com.gitlab.kordlib.gateway.DefaultGateway.deflateData(DefaultGateway.kt:185)
	at com.gitlab.kordlib.gateway.DefaultGateway.read(DefaultGateway.kt:191)
	at com.gitlab.kordlib.gateway.DefaultGateway$readSocket$$inlined$collect$1.emit(Collect.kt:134)
	at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
	at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt)
	at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:73)
	at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:55)
	at com.gitlab.kordlib.gateway.DefaultGateway$asFlow$1.invokeSuspend(DefaultGateway.kt:227)
	at com.gitlab.kordlib.gateway.DefaultGateway$asFlow$1.invoke(DefaultGateway.kt)
	at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:60)
	at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:210)
	at com.gitlab.kordlib.gateway.DefaultGateway.readSocket(DefaultGateway.kt:351)
	at com.gitlab.kordlib.gateway.DefaultGateway$start$2.invokeSuspend(DefaultGateway.kt:128)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(PipelineContext.kt:243)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:199)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:68)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(PipelineContext.kt:149)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(PipelineContext.kt:243)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:199)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:68)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(PipelineContext.kt:149)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(PipelineContext.kt:243)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:199)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:68)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(PipelineContext.kt:149)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(PipelineContext.kt:243)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:199)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:68)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(PipelineContext.kt:149)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Process finished with exit code 1

Test Framework

Test Framework

Summary

provide a testing framework that helps the developer to ensure the correct functioning of their code.

Goals

  • Ability to write test code that simulates real-world breaking changes
  • Provide snapshots from a server that already exists on Discord.
  • Ability to use production code in your tests

Non-Goals

  • This work doesn't aim to be a complete replacement of the Discord API while testing.
  • This work doesn't aim to create or change requests, responses, or entities.

Motivation

Testable API allows developers to expect bugs and security flaws, while this is true, testing with the Discord API isn't because of the rate limits which forces developers to use error-prone approaches like manual testing, or time-consuming approaches like creating another bot for this goal.

The aim of the feature is to limit these scenarios, by providing a a black-box environment that simulates real-world scenarios that also allows using the elements defined in production code.

Description

kordx.test will have a class that simulates the behavior of its parent class.

XGateway - A mocked version of Gateway
XRequestHandler - A mocked version of the RequestHandler

by having classes with such behaviors, we would be able to simulate interactions with DAPI effectively.

Before testing, the user would specify snapshot entities which are entities retrieved from Discord and then acted upon offline, leaving the original entities on Discord unchanged reducing API calls and IO operations.

// TODO Add code

The content will change depending on 0.4.0's implementation.

Current limitations

REST implementations don't help to achieve this goal due to the following reasons not limited to them:

Route parameters can't be accessed

While sending a response, Discord is given the parameters of the targeted function in the request (ChannelID, WebhookID, etc...), to mock this behavior, REST must expose the routeParams variable in Request.

Unserialized payload

Working with discord requires rest calls, but when working on a local computer (de)serializing Requests or responses becomes unnecessary but the current RequestHandler forces Request as a parameter and Response as a return type.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.