kordlib / kord Goto Github PK
View Code? Open in Web Editor NEWIdiomatic Kotlin Wrapper for The Discord API
License: MIT License
Idiomatic Kotlin Wrapper for The Discord API
License: MIT License
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
member.getVoiceStateOrNull()
[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)
This issue serves as a reminder to add a core representation for guild templates.
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.
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.
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.
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).
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
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
.
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
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"
kord/gateway/src/main/kotlin/Command.kt
Line 188 in c6911b5
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.
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
jcenter will got EOL at May, 1st, 2021(https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/).
A badge in README was replaced from jcenter to maven central in 40fa27e
(#159), but artifacts are not found in maven central(https://search.maven.org/search?q=dev.kord).
Coult you deploy the artifacts to maven central?
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)
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.
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
.
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:
Optional<T?>
, an optional cannot contain nullOptional<T>?
you'd use optional?.orElse(null)
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?>
)value: T? = optional.value
and can be null safe if smart-casted to Optional.Value
.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.
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
andOptional.Value
.
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.
Related discord issue: discord/discord-api-docs#2118
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.
provide a testing framework that helps the developer to ensure the correct functioning of their code.
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.
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.
REST implementations don't help to achieve this goal due to the following reasons not limited to them:
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
.
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.
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.
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.
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
Provide a usable and extensible command DSL that facilitates the creation of bots.
Support a command DSL with the following features:
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.
Conceptually, discord command libraries follow a similar pattern in how commands are structured: [prefix][command name] [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)
).
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()
}
}
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;
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)
}
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.
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;
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.
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.
Most useful for logging, this event won't be allowed to influence the control flow of the command.
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.
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.
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
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
It would be nice to have an Intellij plugin that allows you to view the edits to your entities in real-time.
examples: [Embed Virtualizer] (https://leovoel.github.io/embed-visualizer/)
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.
Currently the project lacks full documentation with description and informative block tags in lots of places.
I open this issue with the intent of starting to fully document the whole project, little by little.
Documentation is key to allow developers to explore the library on their own making it easier and faster to write better code.
/label ~Enhancement
**Issue originally posted by @Tmpod
The gateway currently resets it retry counter whenever a connection has been successfully established, we should consider moving this behaviour to the Hello
event instead to cover scenarios where the connection is established but immediately closed.
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
This acts as a reminder to add Slash Commands to Kord.
Further information will be added later
This issue will function as a overview of introducing voice:
gateway.send(RequestGuildMembers(guildId = listOf(guild), presences = true))
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) ~[?:?]
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.
GuildBehavior#kick
and MemberBehavior#kick
are missing the ability to add a reason.
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.
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:
ReadyEvent
to GatewayReadyEvent
to better communicate the intention of this event.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.
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.
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.
preview_asset docs state that the field is nullable while it is also optional
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...
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
Create Remote
implementations of Gateway
, RequestHandler
and DataCache
that can comminucate rate limits between multiple Kord processes.
Make it viable for one bot token to be used over multiple Kord processes by:
This issue does not intend to alter existing code (unless necessary), the implementation of this feature should be its own module.
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.
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
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
Kord uses java.awt.Color
to represent colors. This issue suggests moving to our own/a multiplatform implementation.
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.Color
regardless (or risk depending on an npm module!). We might as well make one that fits all platforms.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
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.
{"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.
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 ()
.
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?
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.