K
Kord•4mo ago
Tic

Check if embed, actionRow etc. has been modified

Hello With the InteractionResponseModifyBuilder, we can define actionRow, embeds etc. However, I would like to avoid to send the same content to the API (to reduce network interaction) Need: So if my message had 2 embeds and 2 actionRows, but only the actionRows are modified since the last rendering, the request must send only the update about actionRows and not Embeds. So naively, I thought to verify with equals the old and new actionRows built, but that not works. For example
println(EmbedBuilder() == EmbedBuilder()) // false
println(EmbedBuilder().apply { title = "Hello" } == EmbedBuilder().apply { title = "Hello" }) // false
println(EmbedBuilder() == EmbedBuilder()) // false
println(EmbedBuilder().apply { title = "Hello" } == EmbedBuilder().apply { title = "Hello" }) // false
have you a better solution to do that?
58 Replies
gdude
gdude•4mo ago
Check each property manually That's how I did it in the KordEx welcome extension
Tic
TicOP•4mo ago
We should create the equals method in kord, that should be more simple for everyone no?
gdude
gdude•4mo ago
it would, of course
Tic
TicOP•4mo ago
@LustigerLurch @SchlaubiBus An opinion about that? (sorry I don't know who maintains hardly the project) If that's great for you, I will create a branch etc. to do that Have you the link of the files that your created for that? That could be helpful
Tic
TicOP•4mo ago
Oh wow
LustigerLurch
LustigerLurch•4mo ago
Yes, we maintain Kord. As for comparing builders with builders or builders with entities, this seems like a hard thing to do for a few reasons: * It's not very clear to me how optionals vs. nullables should be compared (I have the feeling even Discord isn't really sure what means what for those). Is a null value and an Missing value considered equal? Kord builders are written to only expose nullable types but can be backed by an optional and nullable value that changes from Missing to null on first assignment. How do we handle this? * If we'd like our API to be consistent, implementing this would mean a lot of work to do for all builders (we'd have to implement equals and hashCode for a lot of classes). * There might be some property of some entity that isn't relevant for the comparison in every case, think of something like edited_timestamp for messages. How do we decide which to include? If possible I suggest to have some custom logic for this on your side instead, here is an idea how to do it: * Have a (data) class that represents the content of a message and use that data to "render" the actual message. * Keep a reference to an instance of that class representing the last sent message. You might also persist it somewhere if needed or reconstruct it from a message. * Use that to determine which parts need to be updated. I imagine something like this:
data class WelcomeMessage(
val content: String,
val rules: List<Rule>,
val links: List<Link>,
) {
data class Rule(val title: String, val description: String)
data class Link(val text: String, val url: String)
}

fun MessageBuilder.renderWelcomeMessage(message: WelcomeMessage, previous: WelcomeMessage? = null) {
if (message.content != previous?.content) {
content = message.content
}
if (message.rules != previous?.rules) {
for (rule in message.rules) {
embed {
title = rule.title
description = rule.description
color = ...
}
}
}
if (message.links != previous?.links) {
for (link in message.links) {
actionRow {
linkButton(link.url) {
label = link.text
}
}
}
}
}

// use it like this:
var currentMessageData = WelcomeMessage(...)
var message = channel.createMessage { renderWelcomeMessage(currentMessageData) }

// ...
val newMessageData = WelcomeMessage(...)
message = message.edit { renderWelcomeMessage(newMessageData, previous = currentMessageData) }
currentMessageData = newMessageData
data class WelcomeMessage(
val content: String,
val rules: List<Rule>,
val links: List<Link>,
) {
data class Rule(val title: String, val description: String)
data class Link(val text: String, val url: String)
}

fun MessageBuilder.renderWelcomeMessage(message: WelcomeMessage, previous: WelcomeMessage? = null) {
if (message.content != previous?.content) {
content = message.content
}
if (message.rules != previous?.rules) {
for (rule in message.rules) {
embed {
title = rule.title
description = rule.description
color = ...
}
}
}
if (message.links != previous?.links) {
for (link in message.links) {
actionRow {
linkButton(link.url) {
label = link.text
}
}
}
}
}

// use it like this:
var currentMessageData = WelcomeMessage(...)
var message = channel.createMessage { renderWelcomeMessage(currentMessageData) }

// ...
val newMessageData = WelcomeMessage(...)
message = message.edit { renderWelcomeMessage(newMessageData, previous = currentMessageData) }
currentMessageData = newMessageData
Tic
TicOP•4mo ago
- Yes I think missing and null are similar, so the compare will not be harder. For the equal and hashcode, we just need to consider missing as null and all builder will match - That's true, but I think that could be a real benefits for developers. As you can see, I need this feature, in kordex too and I easily imagine that other devs need this for some components I can initialize this for all components and for the next time, we just need to maintain them in case of new fields (that will not be hard) - I imagine edited_timestamp is a field where the value is invented by Discord API and not the dev. In that case, we can create the basic equals for all fields and maybe a isSimilar method that ignores these types of fields. But in reality, the use of Equals and Hashcode will be used before sending a message, so only the fields defined by the dev will be present, in that case, if I have an object with toto and titi fields and defined values for both, I would like to compare all values not partially Yes that could be works If I create my own EmbedBuilder and transform it in the final step to a Kord.EmbedBuilder but if everyone needs to do that, that could be annoying for a lot of developer and I think a library should be easy to use to realize a common feature @LustigerLurch can I try to create a branch and will see what the result will be?
LustigerLurch
LustigerLurch•4mo ago
the thing is, you still will have to write some code similar to renderWelcomeMessage, that won't change
Tic
TicOP•4mo ago
Yes but I think, I can handle it differently with equals and I would like to try it if possible
LustigerLurch
LustigerLurch•4mo ago
how would you do it?
Tic
TicOP•4mo ago
For my app, for the moment I have different component (Embed, Button, Text, etc.) and each inject directly a new data in the dedicated builder So yes basically I can duplicate the builder structure but that's annoying and personal. Create equals will be helpful for everyone Honestly I don't understand the problem to do that, the equals and hashcode are common function present in all objects. So there is no specific difficulty to implement them
Tic
TicOP•4mo ago
So yes for the moment, according to my app, I need to duplicate the code like that. The only advantage I see to do that in my side, is I can customize the builder to add function, but that's possible too with extension function and kord builder
Tic
TicOP•4mo ago
And I need to do that for each used Kord builder. Instead of fix a equals & hashcode methods that don't work, I need to do that. That's a non sense ... It's preferable to fix the methods in the lib in my opinion
LustigerLurch
LustigerLurch•4mo ago
and how do you use these builders? and do you use the kord builders in their dsl form? if not, why? 👀
Tic
TicOP•4mo ago
I'm using Kord builder, and that's why I would like to fix the equals & hashcode For example, I have a component like that:
class ConfirmComponent(
kord: Kord,
override val id: String,
container: Container,
eventScope: CoroutineScope = container
) : ButtonComponent(kord = kord, container = container, eventScope = eventScope) {

override suspend fun render(builder: ActionRowBuilder) {
builder.interactionButton(
style = ButtonStyle.Primary,
customId = id
) {
emoji = BotConfiguration.Emoji.validate
}

registerListener()
}
}
class ConfirmComponent(
kord: Kord,
override val id: String,
container: Container,
eventScope: CoroutineScope = container
) : ButtonComponent(kord = kord, container = container, eventScope = eventScope) {

override suspend fun render(builder: ActionRowBuilder) {
builder.interactionButton(
style = ButtonStyle.Primary,
customId = id
) {
emoji = BotConfiguration.Emoji.validate
}

registerListener()
}
}
So here, you see I'm using the kord builder To create all needed row, it's this code:
override suspend fun renderRowComponents(builder: InteractionResponseModifyBuilder) {
val rows = messageBuilder.getRows()
if (rows.isEmpty()) {
builder.components = mutableListOf()
return
}

rows.forEach { components ->
builder.actionRow {
components.forEach { component ->
component.render(this)
}
}
}
}
override suspend fun renderRowComponents(builder: InteractionResponseModifyBuilder) {
val rows = messageBuilder.getRows()
if (rows.isEmpty()) {
builder.components = mutableListOf()
return
}

rows.forEach { components ->
builder.actionRow {
components.forEach { component ->
component.render(this)
}
}
}
}
(The builder store the components, lifecycle, in some order etc. in short, several things are managed in it So when I render, I keep the last Kord InteractionResponseModifyBuilder and I would like to compare the last and the new rendering to not sending unnecessarily the data to Discord and minimize the network consumption I don't know if it's understandabl
Tic
TicOP•4mo ago
@LustigerLurch So I created the PR and hope that will be reasonable accepted 😂 https://github.com/kordlib/kord/pull/968
GitHub
fix: Implementation for Equals & Hashcode in Builder by Distractic ...
Context When we compare the builder, like EmbedBuilder, the comparison between two instances will always return false no matter the field set. For example: println(EmbedBuilder() == EmbedBuilder())...
LustigerLurch
LustigerLurch•4mo ago
and where in this code would having equals on the builders help?
Tic
TicOP•4mo ago
In my project (private for the moment) And I'm waiting this PR to continue 😅
Tic
TicOP•3mo ago
Hello @LustigerLurch I have an issue with my custom builder When I put "null" has value for embed or components, the embeds or components are removed from the interaction. Normally, it's when you put an empty list no?
No description
Tic
TicOP•3mo ago
If everything is null , I have the expected error REST request returned an error: 400 Cannot send an empty message null so no problems but when I have 1 Component & null embeds, the embeds disapears in Discord interface .. but the value "null" should mean "nothing change" no? otherwise if I went to remove the embeds I put a mutableList() @gdude [he/him] Have you an idea about that too?
gdude
gdude•3mo ago
Null should mean it isn't removed, yeah
Tic
TicOP•3mo ago
well so I don't understand
Tic
TicOP•3mo ago
No description
Tic
TicOP•3mo ago
Before update:
No description
Tic
TicOP•3mo ago
After update:
No description
LustigerLurch
LustigerLurch•3mo ago
it uses an optional and nullable field internally. if the setter isn't called, it remains Optional.Missing, when you set it to null, it'll be Optional.Null. this maps to what the endpoint can do (optional and nullable parameters).
Tic
TicOP•3mo ago
aah so I need to set it to missing?
LustigerLurch
LustigerLurch•3mo ago
that's what i meant with optional and nullable values here btw you can't, it's internal to the builder you instead shouldn't touch it
Tic
TicOP•3mo ago
Ok so I have an issue here - First render: embeds = list(..) - Update (second render): No change in embeds, so embeds should be missing Because if I don't set the embeds, the value will always be the first list(..) so in the REST request, the body will contains the embeds field
LustigerLurch
LustigerLurch•3mo ago
you need to check for changes in the second render. if there aren't any, just don't set the properties of the kord builder, just like here
Tic
TicOP•3mo ago
yes but, if I don't use the setter the value of builder will always be the first list(..) no? so .. the request body will always contain the list and not a missing property
LustigerLurch
LustigerLurch•3mo ago
that's one reason why you shouldn't reuse builder objects
Tic
TicOP•3mo ago
Well so, I should always send all structure of the message even if embeds doesn't have changes
LustigerLurch
LustigerLurch•3mo ago
if you use kord's dsl functions, you shouldn't even have to create builder instances yourself but just use them as a receiver in a lambda / extension function no, a fresh builder will default to everything not required to be missing/null (whatever is allowed for a particular endpoint). so you just add what you need, use it for one request, and then let go of it.
Tic
TicOP•3mo ago
is there a way to have the body request sent to discord in log?
LustigerLurch
LustigerLurch•3mo ago
trace level logs it
Tic
TicOP•3mo ago
So I tried and seems that works
gdude
gdude•3mo ago
Reusing builders can actually be handy The KordEx welcome extension has to figure out how and when to update messages and in that sense it uses fresh builders and does a ton of very annoying comparisons
LustigerLurch
LustigerLurch•3mo ago
it will look something like this:
Tic
TicOP•3mo ago
I need to use logback with kotlin multiplatform? (Never did that with KMP)
LustigerLurch
LustigerLurch•3mo ago
logback avec kotlin multiplatform?
gdude
gdude•3mo ago
You can't, logback is jvm Avec is "with" in French
LustigerLurch
LustigerLurch•3mo ago
what platform do you target? JVM or JS?
Tic
TicOP•3mo ago
french word ah ah sorry
LustigerLurch
LustigerLurch•3mo ago
for JVM, you can use any slf4j implementation, for other platforms (JS for now) you need to use https://github.com/oshai/kotlin-logging
Tic
TicOP•3mo ago
for my test JVM so yes I can use logback ah yes this lib! I forgot thanks And how do you know that 🥲
gdude
gdude•3mo ago
Studied French for 8 years
Tic
TicOP•3mo ago
😮
gdude
gdude•3mo ago
Don't remember most of it tho
Tic
TicOP•3mo ago
ah 🥲
gdude
gdude•3mo ago
They teach it at school here
Tic
TicOP•3mo ago
You can understand or not at all?
gdude
gdude•3mo ago
I can understand some of it But this is off topic
LustigerLurch
LustigerLurch•3mo ago
they do here too, but i chose latin over french - idk why though xD
Tic
TicOP•3mo ago
Why 🥲 We have course for Latin too, but everyone prefer escape this language
gdude
gdude•3mo ago
It's okay, all you had to do was say you hate yourself /lh
Want results from more Discord servers?
Add your server