#We're making a wrapper over the Discord API using Java!!
1 messages · Page 2 of 1
oooh
tbf i wont want it to get merged either
why lol
it was kinda garbage
It's good to see we can fine tune it
not good enough quality for tj bot
install eclipse
i have never used eclipse
i started with intellij
then neovim + intellij
For?
i found 2
Optional<Integer> status,
Optional<String> image
)
implements DiscordRequest {
public @interface EventListener {
}
formatter
Try mess around with it and see what you come up with
Aim for the best quality formatting that we can all agree is perfect and the formatting war can be over 
might as well do it for tj bot
or not
i fked
reverted
i'll make atomic commits now so it'll be easier to trace back later
second is fixed
@restive void @plain ledge check formatting, lmk if u feel smth needs to be changed
wdym?
@orchid portal Should we use Methanol just for multipart data or switch everything to it?
isn't it just an extension to already existing java http?
(Everything being pretty much just the HttpClient)
Lightweight HTTP extensions for Java
no
kk
lets wait for wazei
I think to integrate it (besides registering whatever we need for jackson), I will just add a set body publisher method since they already have a builder for multipart, which just returns a BodyPublisher
I think we should mandate @JsonProperty on all fields of a model, wazel is doing goofy stuff
rpcOrigin is not even correct
also always use List not array
idk if it works but dont
yeah
Taz can u check out that Pr I created
@orchid portal
header should be defined using DiscordRequestBuilder#header method
ok
that is not possible, then we'll need to change the DiscordRequestDispatcher as well
wdym
the DiscordRequestDispatcher only adds the application/json Content-Type when the headers list is empty
ohh u mean like body(bodyPublisher, header)?
I mean if u use that method for multipart data u will need to specify the Content-Type as multipart i believe, so what I was asking is if we should just combime those methods
like inside the method add 'Content-Type: multipart/form-data as a header
no coz a generic body(BodyPublisher) will be used for other stuff like BodyPublishers.ofFile as well
ok
if u want we can do multipartBody(MultipartBodyPublisher) and then u can do that
i'd prefer that
Yeah
I'll include both methods's for future use
yea
formatting is done on my side u guys can give ur opinion
You got an SS?
bro see the pr
what pr
schizo
nvm
im sped
is the dif 1 tab instead of 2?
u blind mf
yes
that also
along with many other things
what did we decide for docs? Not starlight right
whats starlight
Only the cutuest buetifulist docs site ever 😉
looks good y not starlight then?
i'd just use mkdocs, if theres no other option
wazei was against it
@orchid portal I am going through guild models rn
and there are quite a few enums, should I just through those all in the models folder?
I feel like it's getting kinda cluttered
wazel put application models in guild
separate that
and see if i missed any gateway model move them to gateway.events.handlers.codec.models
im not doing that in this pr
no idea
:|
@orchid portal am I refractoring the models to match requests package?
Looks good, merged it
Preciate it 💯
i have some time, what are the questions you peeps want to ask me?
Uh, read that one issue I made so we can discuss that
Or close it if those models won’t be user facing
And then I left a comment on the guild api issue
responses - tldr; yes to both
So they aren’t user facing
Ok
it might be better for our sanity to let them be user facing
as much as I want to create wrappers over them, it's just so much manual labour that we could probably avoid
let's think about it when the time comes if we get benefits over creating a wrapper
Wait
I just looked into Jackson
We can just do @JsonValue on the getValud method
We don’t need to write a annotation processor
I’ll go through tonight all the models and make sure they are complete and switch it all to enum
ooh thanks for looking into this! @plain ledge 
And for the guild endpoints do everything including members and stuff
yes please
Ok
formatting
its better to merge it quick
And the pr for multipart data olease
@light loom can you merge Taz’s spotless thing asap so that we don’t have to deal with new PRs having as much merge conflicts
merged
Tonight I’ll finish the stickers pr and then pr the enum changes
and then tmr I will finish guilds (still need to figure out how to handle partial channels and roles cause I’m not clear on what they are)
A partial channel is a regular Channel object
The reason it's called partial is because they don't give all the fields back which is okay for us
We have some future work to add caching, we'll keep a track of all objects and when we receive partials ones, we can just update the fields when we receive them later
And keep the cache updated
@orchid portal Make sure to run spotless on the user pr 😉
yea sure
I got about 70% done with the enum PR, in doing it I went over all the models and noted missing fields. I didn't do it all for guild and message since it was intimidating but still need to do that. I also added a couple new models to missing stuff like invites and refractored the models packageto match the discord docs website (same as endpoints package)
Will have another PR for channel api endpoints ready later today
you've done them all? 👀
so grateful there's no conflicts on #22 from taz's PR 
yea ur scared for nothing
ur not editing any file
only making new ones
rebase and apply the new spotless
What data type are we using for snowflakes btw?
since the message is pinned now ig u should keep updating the team
or just remove it
coz ig nopox is doing the most work
its unfair to not have him
why am I up there 
didnt do anything
I would just remove the team section
people can look it up on discord
good call
long
u pinned
the best project in the entire #1150852739467849829 section
I will be gone next week and then I still have some finals, I hope I can catch up after that
shouldnt there be a custom class?
- the team bit was clickbait 😄 - you guys were just the first ones I spoke about the project with and wanted to contribte ❤️
I just saw the enum PR...
I think we are just using long

It should be
okay it is!
It's just model changes and refractors
tested it and it decoded properly?
lmaoo
when u say tested it wdym
the models changes
do it quickly!
is there a testing bot or smth
i have a task to create a testing framework for you peeps
arent we using imposter
yeah we are but it's just there and not being used by the code 😦
what so the testing framework,
- Spins up imposter so we can make http requests
- Spin up the
Discordinstance - Call dif get requests and make sure everything works
@light loom Is this intentional or can i refactor it and remove the duplication
Gotcha
Ill probs just get the remaining channel endpoints done
Got like 4 or so remaining
Np
@light loom
sorry multi tasking, i missed this - so yes you can run imposter up in the mocks directory, this will start the server on localhost:8080 you can then use that url to make a request to the API
and it'll return the fields
they have a java lib that you can reference in the code so we don't need to use imposter up
so is that just working with junit then to prepare the testing env
we just need to add the library into gradle and setup the setup the boilerplate
and then modifying the discord class to be able to work locally
How should i handle a response body for an endpoint like this? Or should i just ignore it for now
ignore it for now
are we using junit5 for testing?
Ight
anything
what's the issue?
Was just wondering if i should do anything for the response body
I don't think we are doing response bodies yet
public record ListPublicArchivedThreadsRequest(
long channelId,
Optional<LocalDateTime> before, // Represents ISO8601 timestamp
Optional<Integer> limit) implements DiscordRequest {
@Override
public DiscordRequestBuilder create() {
return new DiscordRequestBuilder()
.get()
.path("/channels/%s/threads/archived/public".formatted(channelId));
}
}
Ye this is what i have rn
perfect, just add the query params
hey
look at smth else that uses the time
I think Taz has some annotation that we do on them
Oh ok
@light loom https://docs.imposter.sh/embed_jvm/ this right
Ya i didnt know what we were using for ISO8601 so i just put localdatetime for now
yess
they have examples on their github
yup
Alright, I'll leave that enum pr on hold till this is done and then add tests and shi
@light loom Can imposter do gateway events or no
No 😦
But you can use mockito maybe
I will work on the test thing tmr if someone wants to work with me I'll prob be in vc
So just to remember, using Imposter is integration testing
Not to be confused with unit testing
So we are not using junit to instansiate imposter correct?
(I am somewhat new to testing)
We are using it to instantiate it but maybe I can explain this tomorrow as I'm going to bed soon
I can give you a run down of testing and the differences between the approaches
With this, the channel API should be done
@plain ledge what is @JsonValue
@light loom should we have a separate record for PartialChannel/User/Guild?
No need because we'll be maintaining a cache of all the models and update them whenever we get data
If it's a partial object, we just update the cached object with the updated fields
what about here
I just had those like that cause I didn’t know what the plan wss
Just pass a regular Channel/User
On Jackson the @JsonIgnoreProperties will make it not throw when fields are missing
But you'll still get the partial content for the other fields
wait if a field is nullable and it's a primitive wont that error
no? null primitives default to respective zero value
oh yeah
@plain ledge did u just removed these
they are put in the correct place I believe
wait, nvm it's msising a couple. I'll write that down
Since im done with channel API, ima pick up Application and Audit Logs API tasks since theyre quick if u guys dont mind assigning me
You can start on them I’ll assign you when I get home
What data type should we use for image data in the application API?
BufferedImage or something?
I believe it’s a string
spotless!
Weird, i applied spotless before committing but ill try again ig
we have new spotless now, rebase and then apply spotless
@light loom when u would be able to explain to me the dif between unit and integration
come vc? @plain ledge
sure 1moment
mocking the websocket gateway is much harder if we want it like imposter
instead it's better to just use mockito
and mock the method responses
wiremock ftw. but yeah, lots of stuff
What’s that?
let's u mock the http responses of an api, e. g discord api
That’s nice, but I would say it’s generally harder to test applications that uses some api
i. e. mocking on the outermost level of ur library, so that u truly get full test coverage
otherwise u can't do integration tests in ur case
or need to leave a significant part of ur code untested
like, suppose u want to test ur method that bans users. how would u do that. it sends a api request to discord and interprets the returned response...
unless u create ur own mock layer in between somewhere and then throw mockito at it, its much easier in that case to just use wiremock to intercept and redirect the http traffic, returning a mocked response
depending on the details of the setup, might be worth a thought 🙂
we are using imposter for the http api
and then we were discussing what to do for the websocket portion of it
i see makes sense
yup so imposter uses the openapi spec to generate a mock http server that we can use for integration testing
i forgot about wiremock!
that's pretty cool
openapi is such a cool thing. so many tools build upon it
Your code though isn't complete? 
Better to remove that to-do and update the ticket with what's missed
ahh fk
no i'll just do it
I'll review tomorrow because I wanna look into that image code you have
@light loom ok its done now
Rebase main onto my branch?
yes
i aint looking at that
Made PRs for application api and audit log api
pr maxxing
approved
@restive void had a chance to look at the comment I left? you missed one of the endpoints 
Updated the main post to include all the contributors so you guys don't get unnoticed 
@plain ledge i need ur approval as well
There was only 1 endpoint in there im pretty sure
2 hehe
Idk if im blind but i only see 1 in the audit logs API lmao
Get Guild Audit Log
GET/guilds/{guild.id}/audit-logs
this is the one you added
wait
I'm also being blind
can you extend it
When an app is performing an eligible action using the APIs, it can pass an X-Audit-Log-Reason header to indicate why the action was taken. More information is in the audit log entry section.
sorry it was just 1 endpoint, i thought it had 2 modes but it was just the optional header
Ye
hey peeps, i need your opinon
by using records we kinda messed up because we can't cache and update
so... here's my hacky solution:
package com.javadiscord.jdi.core.cache;
import com.javadiscord.jdi.internal.models.channel.DefaultReaction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class Cache {
private static final Logger LOGGER = LogManager.getLogger(Cache.class);
private final Map<Long, Object> models = new HashMap<>();
@SuppressWarnings("unchecked")
public <T> T getFromCachedById(long id) {
return (T) models.get(id);
}
public boolean isCached(long id) {
return models.containsKey(id);
}
public void cache(long id, Object model) {
if(models.containsKey(id)) {
updateCachedChannel(id, model);
} else {
models.put(id, model);
}
}
@SuppressWarnings("unchecked")
private <T> void updateCachedChannel(long id, T model) {
T cached = (T) models.get(id);
try {
Class<?> recordClass = cached.getClass();
Constructor<?> constructor = recordClass.getDeclaredConstructors()[0];
Object[] currentValues = new Object[constructor.getParameterCount()];
for (int i = 0; i < currentValues.length; i++) {
Field field = recordClass.getDeclaredFields()[i];
field.setAccessible(true);
currentValues[i] = field.get(cached);
}
// Create a new instance of the record with updated values from the model
T updated = (T) constructor.newInstance(
Stream.of(constructor.getParameters())
.map(parameter -> {
try {
Field field = recordClass.getDeclaredField(parameter.getName());
field.setAccessible(true);
Object value = field.get(model);
return value != null ? value : currentValues[(int) Stream.of(constructor.getParameters()).filter(p -> p == parameter).count() - 1];
} catch (IllegalAccessException | NoSuchFieldException e) {
LOGGER.error("Failed to update cache for model {}", id, e);
return null;
}
})
.toArray()
);
models.replace(id, updated);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
LOGGER.error("Failed to update cache for model {}", id, e);
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Cache cache = new Cache();
DefaultReaction defaultReaction = new DefaultReaction(1, "test");
cache.cache(defaultReaction.emojiId(), defaultReaction);
System.out.println(cache.isCached(defaultReaction.emojiId()));
DefaultReaction updated = new DefaultReaction(defaultReaction.emojiId(), "hello");
cache.updateCachedChannel(defaultReaction.emojiId(), updated);
DefaultReaction fromCache = cache.getFromCachedById(defaultReaction.emojiId());
System.out.println(fromCache.emojiName());
}
}
It's a bit crazy but it saves us from writing new objects or updating all the records
advice on alternatives or improvements?
why cant you group them instead of using Object
well let me go through the whole code snippet
but it would mean a new cache for ALL of these
I thought of maybe only caching the important ones like users, channels etc
but it's still a fair bit though my hack lets us cache everything and anything
Now I might need to do what you suggested - that is keep a separate list of models because I think Ids might clash
Alternatively, I can use a Key object which contains the Class and ID and use that - to not need a new List for each model
(or a string for perf e.g. "id|className")
what kind of id is that btw, because it will clash if its like the discord id
for example a thread and its starting message have the same discord id
Updated it - these are discord IDs
so I have updated it to use the Model name as part of the ID field
It means passing X.class but small price?
I have raised https://github.com/javadiscord/java-discord-api/pull/70
Best to review via commits
@light loom will that be wrapped by the user facing api or is it intended to be used by the user?
I think maybe we'll either have a wrapper or let users access it directly
It's part of the reason i put inside the core package
Or maybe, we'll have helper methods for the "generic" stuff and still give access to the full cache for anything else
Hard to tell because we don't understand the API to it's fulliest - like sure we've implemented most of the API however that knowledge is in our short term memory 😄
How do u want it handled?
I just have it like this rn with an Optional<String> reason param
reason.ifPresent(reason -> discordRequestBuilder.putHeader("X-Audit-Log-Reason", reason));
Since there are so many things that support it we may be better off adding something for it in the user facing api
like .withAuditLog reason and we just modify the DiscordRequestBuilder
Ya
It's on hold till we implement testing
that's what wazei wanted, im gonna start testing this week unless u wanna start it now
which pr's need reviewing?
y
user apai
.
I've been sick this weekend and just sat around
@orchid portal
request my review on user pr I can't for some reason
@orchid portal There is just one change then it looks good
no worries - i can sort it out
yup ^^
@restive void just confirm for me, all the channel endpoints are done now right?
commented on #66 okay to approve it @plain ledge
Just check the conflict
oop, those missing notes are when I was going through the models on discord docs and noting down missing fields. The auditLogChanges was becauase I wrote down a todo to ask about how we handle mixed data types (https://discord.com/developers/docs/resources/audit-log#audit-log-change-object)
if they're primitives but mixed e.g. string/int use String
if it's an object, I guess Object? Until we find a better way to enforce it?
we can do type checks in the constructor
or generics e.g. <T extends B | C>
hey @light loom can you check the docs and confirm something? I am reading the audit log docs and I am thinking that it can either be a snowflake (long) or a string, but I'm not that confident that I am reading them correcltly
snowflakes I interpreted as longs
https://discord.com/developers/docs/resources/audit-log#audit-log-change-object-audit-log-change-exceptions this is what im looking at
what bit specifically?
user_id? snowflake Entries from a specific user ID
action_type? integer Entries for a specific audit log event
before? snowflake Entries with ID less than a specific audit log entry ID
after? snowflake Entries with ID greater than a specific audit log entry ID
limit? integer Maximum number of entries (between 1-100) to return, defaults to 50
Object Changed Change Key Exceptions Change Object Exceptions
Command Permission snowflake as key The changes array contains objects with a key field representing the entity whose command was affected (role, channel, or user ID), a previous permissions object (with an old_value key), and an updated permissions object (with a new_value key)
Invite and Invite Metadata Additional channel_id key (instead of object's channel.id)
Partial Role $add and $remove as keys new_value is an array of objects that contain the role id and name
Webhook avatar_hash key (instead of avatar)
oop,,
(Looking at the audit log change structure)
so the Command Permission looks like an Map of Command Permission objects that use the Long id as the value
{
1: {
...
},
2: {
...
}
}```
maybe something like this
Since we have imposter you can also invoke the API locally and see the output
But this is an exception case so I'm not too sure on what the exact output could be, maybe raise it as tech debt?
Well I've got that missing thing noted down in the code so should we just ignore that for now and relook at it in the future?
that's fine 🙂
I commited initial integratioin test tthing
ima head to bed now
goodnight 🙂
looks good, cool changing the BASE_URL to an env var
and you're on the right track!
i wanna get in on this but this stuff looks too difficult 
skill issue
Try get comfortable with the codebase, ask questions and figure out what we've done 
It'll at least help you gain some knowledge on how we've designed the code and maybe give you an understanding of the Discord API
thanks 
down to vc for a min?
sure
Yep
And ill push up the x-audit-log reason header handling to my PR later
@light loom I think our next thing after these integration tests is response handling, should we get that done by the end of this week?
Yup, but I wanna go through everything we've done so far a d make sure it's all correct
@plain ledge maybe you should create a PR with the current work before continuing down the path of doing all the other end points. Instead continue and create unit tests for the other bits
The reason being, I was thinking about this hard and I think we'll need to test more bits in the area you're currently in but we're missing implementation
The step we're missing is actually using the response received from the API request and I think the way we're using Jackson at the moment, everything will succeed and we'll always get 200s
Ok maybe instead of making one big PR for all those integration tests, they can be included in the endpoints PRs
Atleast going forward
Yeah exactly and you've done a fantastic job of setting up the infrastructure
So everything is in place for when we're ready
Ok wel you kinda did it 
If you haven’t already I will create a Pr for the current integration testijg in a couple hours and than finish up the guilds PR and I should have everything I am assigned to off my plate
My cache PR is getting kinda big and out of scope lmao
but I added all the annotations I think we need
(dw it wasn't all manual, I auto generated it using the handler names)
Sweet
So we will just annotate functions with those and then call that function in the handler?
Yeah so hopefull we can do
@EventListener
public class ExampleListener {
@ChannelCreate
public void exampleChannelCreate(Channel channel) {
System.out.printf("Channel created: %s", channel.name());
}
My auto generation is sketchy though so we need to test this so hard
I'm parsing the classes and generating the boilerplate that way
streaming my development rn
Updated my PR: https://github.com/javadiscord/java-discord-api/pull/70
it freaking works!!!
EVENT_TYPE_ANNOTATIONS.put(EventType.CHANNEL_CREATE, ChannelCreate.class);```
just need to add this for every event/annotation combo... more manual labour...
Wanna merge the integration tests rn
Sure thing
I’ll fix the enum pr in a sec
merged
yup
@light loom
is this a mistype or is that all intentional for those all to be included if content is present
👍
@light loom I left a couple of comments but the rest looks good
Can’t, I’m at school my lunch bouta be over
np np
Nah i gotta leave for class in a bit
@MessageCreate
public void example(Message message, Discord discord) {
if(!message.author().bot()) {
System.out.printf("Message received %s", message.content());
discord
.sendRequest(new CreateMessageRequest(message.channelId(), message.content()))
.onSuccess(System.out::println)
.onError(System.out::println);
}
}
it works so perfectly
I made a simple echo bot 😄
Is the channel branch getting merged into main soon or are there more things that need to be done for it?
just some small fixes needed
nopox left review comments, once they're done we can merge ❤️
what do you guys think of this syntax?
discord
.sendRequest(new CreateMessageRequest(message.channelId(), message.content()))
.onSuccess(System.out::println)
.onError(System.out::println);
``` we'll need to wrap this ofc
or... do we? 😄
Now a cool feature that would be nice is... Hot Reloading
The reason is hot reloading is totally possible to do because of how we're loading the event listeners
I like it
Maybe we can do like discord.sendMessage which returns a MessageBuilder
So you can do like .withEmbeds
Mm yeah maybe we can
I did some experimenting with hot reload and I got it working with creating new files but for some reason when I modify an existing one and reload it, the changes are not picked up 😦
But that's because of how Java's class loader works, it caches the object once it's been loaded
So it means, creating a custom class loader 😅
Pain
It would be kinda aids for a user to have to call the constructor with all the Optionals we have
Yeah definitely, we should use proper builders
could use a Customizer/Configurer like spring does. java discord.sendMessage( (msgConfig) -> msgConfig .sendRequest(new CreateMessageRequest(message.channelId(), message.content())) .onSuccess(System.out::println) .onError(System.out::println) );
(i have no context on this discussion)
@orchid portal ur spotless config sucks
it's not formatting everything and leaves inconsistencies
for example
it won't format the second one
or the first one
If you update the config
Can you wait til we merge enum PR so there aren’t a ton of conflicts
Sweet, thx
@plain ledge i'm just doing your review comments on /channel
can you show me a snippet of where the MultiPartBodyHandler is used?
oh i found it need to rebase
can you explain the update I need to do?
@Override
public DiscordRequestBuilder create() {
return new DiscordRequestBuilder()
.patch()
.path("/channels/%s/messages/%s".formatted(channelId, messageId))
.body(
Map.of(
"content", content,
"embeds", embeds,
"flats", flags,
"allowed_mentions", allowedMentions,
"components", components,
"files", files,
"payload_json", payloadJson,
"attachments", attachments
)
)
.putHeader(
"Content-Type",
files.isEmpty() || attachments.isEmpty()
? "application/json"
: "multipart/form-data"
);
}
I also wanna change the project structure a little bit - and make the models a seperate gradle module
@light loom look at the sticker pr
Cause it doesn’t use a body you interact directly with the Multipart data handler
ah cool thanks
Also maybe while your at it, would you be able to write a integration test for that just to make sure it’s doing the thing properly (assuming imposter checks the headers and stuff)
I don't understand this api
I pushed broken code for the EditMessageRequest
Let's get back to it later? 🙂
Sure
can you review the PR?
Gimme 35m until lunch starts
@plain ledge https://github.com/javadiscord/java-discord-api/pull/70 added some unit tests there, you might be interested in seeing how they're developed
(this PR is mega and still work in progress but i'll split it out later)
im fixing the conflicts
give me a deadline
By tomorrow morning 😁
done
and ig u were talking about formatting
yes its not very strict
coz then it starts to get bad
It needs to be strict because now we have inconsistency
i'll try fix the thing u were talking about
Because it's not formatting parts so they all look different as if a formatter wasn't even used
@light loom how much longer are you gonna be on tonight
I'm in bed, what's up
Never mind then
What is itt
No, 🔫 sleep
knew it
idk
@light loom was this todo missed or is this for future
I left that there for the future
ah
Once all the PRs are merged (or at least the big ones) I'm gonna do a codebase tidy up
@light loom I just put the final touches on guild pr
except for one thing, in one of the requests I need to pass in a timestamp in that ISO whatever the full name is format. I think I saw you annotate that inside the request previously which didn't make much sense to me since that shouldn't work.
@plain ledge i approved your PR
when we write tests for all this stuff, we can fix it then
I think we have like 2 small endpoints left to implement then we've at least got everything in code ❤️
ahh yes tdd
well more so acceptance testing
the bits we have all been working on are fundamental components, if they don't work or work as expected, they're high priority fixes for a go-live
we have 1 more core component to add which is the wrapper/response handling over these DiscordRequest classes we've made
once that's done - we have something that has fully integrated with discord!
and given we've only been working on this project for a month, our progress has been really flipping amazing so big thank you to all for the contributions!
ready for review
i reviewed a merged pr
ahh
myb
it's okay, I am doing another refactoring
it's unclear at the moment which models are used for the gateway specifically and for the API
and there's shared models that we can't see clearly
so I'll do that
yeah so I was thinking about it, what Taz was discussing last week about having it like #1223284116263276637 message
It seems good except often a request just returns a guild so do we make all the models extend DiscordResponse
I like the simplicity we have and creating a new class that contains a map of requests <-> model is easier
@plain ledge what are ur ideas on the user facing models?
Yeah so what we discussed in the enum pr was the models being user facing so we don't have a bunch of repeated models. and then what I was just saying to wazei is if they are gonna be user facing we should move them out of the internal package
besides the package, do have any benefit to writing a wrapper over them?
I'll pick up implementing the webhook api if someone wants to assign me later
awesome
Who gave the Small label to the webhook api 💀 its got like 15 endpoints
it does!?
Ya
Gotcha
tferdous17
There
ty done 🙂
@light loom So we are creating a mediator DiscordResponse between the request and model
Likely a blank interface, just to identify that that record represents a response?
Aight
Do we have something to handle X-Audit-Log-Reason better?
Theres a lot of endpoints in webhook api that supports it
mm wasn't it just give the optional param and if it's present, set the header?
Ya
Idk if theres a better way to handle it than that
Some short .withAuditLogReason() thing (I believe Nopox mentioned it before)
Yeah I was saying since there are so many things that use it so we can modify the DiscordRequestBuilder in some wrapper
Or we can go back and add it to all the endpoints if we decide to later
I don’t think we discussed the implementation past needing to handle it, it’s written down as technical debt atm
public class DiscordResponseParser {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final Map<Class<? extends DiscordRequest>, Class<?>> map = new HashMap<>();
static {
map.put(ChannelCreateInviteRequest.class, Channel.class);
}
public static Object parse(Class<?> clazz, DiscordResponse response) throws JsonProcessingException {
return OBJECT_MAPPER.readValue(response.body(), map.get(clazz));
}
DiscordRequestDispatcher discordRequestDispatcher = new DiscordRequestDispatcher("");
public void sendRequest(DiscordRequest request) {
discordRequestDispatcher.queue(request).onSuccess(res-> {
try {
Channel channel = (Channel) parse(Channel.class, res);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
});
}
}
i was thinking this
but we have to do this casting
But then you have to check the docs in order to figure out what the response type is
mm no, it's still internal, we'll need builders for the DiscordRequests we have created
e.g.
or helpers
e.g. discord.getGuild("..").getChannel("...").sendMessage(..);
I see
I am not gonna be able to work on thst for a couple days since I need to make a wrapper for Roblox api
np 🙂
@orchid portal @plain ledge done review comments; https://github.com/javadiscord/java-discord-api/pull/70
Was checking your gradle file
Only add logging api's
implementation is a runtime dependency at best
Plus, slf4j is WAY more popular than apache-logging, https://mvnrepository.com/open-source/logging-frameworks
Can you raise a PR? 👀
no, no time right now
Just letting you know
I'll give it a quick look over
(god I hate gradle)
there's nothing to hate lol
Yeah, it's called MAVEN central for a reason 😄
yeah because maven created the repo
yeah, exactly, use that 😄
I mean, use actual mvn
the xml is too verbose and messy
the 94 lines of gradle would be 1000 lines of xml
If that's your only complaint...
a pretty valid complaint actually
also, we're kinda just using it for dependency management and creation of the jar/deployment to maven
so the 1-liners are better for us implementation 'org.javassist:javassist:3.30.2-GA' for example
or being able to specify the group and version by just doing
Hmm, so you don't like verbosity? But you did decide to implement the entire discord api manually yourself instead of generating it from the open api spec?
group = 'com.javadiscord'
version = project.version
we can't generate it from the openapi spec though
Which you even use in your tests...
using the openapi spec, we can create mock endpoints
you can't write actual functions automatically to call those endpoints in java
if you're thinking like swagger's generator or something familiar, it wouldn't give us the level of control we need, we'd still need to refactor all the classes
I’ll check this out when I get home
i force merged 
I wanna do a big code cleanup
more so, the models and gateway and just re-look at them, see what's missing
Ok
Describe the PR
Some refactoring in place:
Opted for _ in package names in internal for better readability
Moved the APIs outside of the impl package, the impl package wasn't giving any benefi...
lots of files because of the repackage - but it's a small PR in the nature of changes
also implemented Application Role Connection Metadata Records
@Test
void testDecodingThreadMember() {
String input = """
{
"id": 1,
"user_id": 10,
"join_timestamp": "2024-04-25T21:37:44Z",
"flags": 0
}
""";
try {
ThreadMember threadMember = OBJECT_MAPPER.readValue(input, ThreadMember.class);
assertEquals(1, threadMember.threadId());
assertEquals(10, threadMember.userId());
assertEquals(OffsetDateTime.parse("2024-04-25T21:37:44Z"), threadMember.joinTime());
assertEquals(0, threadMember.flags());
} catch (JsonProcessingException e) {
fail(e.getMessage());
}
}
also added a unit test for one of the models
and found the date parsing that was added didn't work so fixed those 🙂
(got the sample response body from invoking the mock endpoint)
And created a utility for handling those images 🙂
Sweeeeet
bro the code looks so nice rn
for example
this is almost mirroring the API structure perfectly
and it's super nice because there's a 1-2-1 match
the second package lmao
yeah _ for readability
yea
and we can clearly see what's missing
nice progress
it's all thanks to the contributors!!
i just realized the time
lel
low-key just wanna spend all night working on it 

package com.javadiscord.jdi.internal.api.audit_logs;
import java.util.Optional;
import com.javadiscord.jdi.internal.api.DiscordRequest;
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
public record GetGuildAuditLogRequest(
long guildId,
Optional<Long> userId,
Optional<Integer> actionType,
Optional<Long> before,
Optional<Long> after,
Optional<Integer> limit,
Optional<String> reason
)
implements DiscordRequest {
@Override
public DiscordRequestBuilder create() {
DiscordRequestBuilder discordRequestBuilder = new DiscordRequestBuilder().get()
.path("/guilds/%s/audit-logs".formatted(guildId));
userId.ifPresent(val -> discordRequestBuilder.queryParam("user_id", val));
actionType.ifPresent(val -> discordRequestBuilder.queryParam("action_type", val));
before.ifPresent(val -> discordRequestBuilder.queryParam("before", val));
after.ifPresent(val -> discordRequestBuilder.queryParam("after", val));
limit.ifPresent(val -> discordRequestBuilder.queryParam("limit", val));
reason.ifPresent(reason -> discordRequestBuilder.putHeader("X-Audit-Log-Reason", reason));
return discordRequestBuilder;
}
public static class Builder {
private final long guildId;
private Optional<Long> userId;
private Optional<Integer> actionType;
private Optional<Long> before;
private Optional<Long> after;
private Optional<Integer> limit;
private Optional<String> reason;
public Builder(long guildId) {
this.guildId = guildId;
this.userId = Optional.empty();
this.actionType = Optional.empty();
this.before = Optional.empty();
this.after = Optional.empty();
this.limit = Optional.empty();
this.reason = Optional.empty();
}
public Builder setUserId(long userId) {
this.userId = Optional.of(userId);
return this;
}
public Builder setActionType(int actionType) {
this.actionType = Optional.of(actionType);
return this;
}
public Builder setBefore(long before) {
this.before = Optional.of(before);
return this;
}
public Builder setAfter(long after) {
this.after = Optional.of(after);
return this;
}
public Builder setLimit(int limit) {
if (limit > 100 || limit < 1) {
throw new IllegalArgumentException("limit must be between 1-100");
}
this.limit = Optional.of(limit);
return this;
}
public Builder setReason(String reason) {
this.reason = Optional.of(reason);
return this;
}
public GetGuildAuditLogRequest build() {
return new GetGuildAuditLogRequest(
guildId,
userId,
actionType,
before,
after,
limit,
reason
);
}
}
}
thinking we do this
and for the request sending
discord.getGuild("...").getAuditLogRequest(GetGuildAuditLogRequest.Builder);```
no lombok because we can't handle do the validation or use optionals like we have
or we coul do
package com.javadiscord.jdi.internal.api.audit_logs;
import java.util.Optional;
import com.javadiscord.jdi.internal.api.DiscordRequest;
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
public class GetGuildAuditLogRequest implements DiscordRequest {
private record Record(
long guildId,
Optional<Long> userId,
Optional<Integer> actionType,
Optional<Long> before,
Optional<Long> after,
Optional<Integer> limit,
Optional<String> reason
) {}
private final Record record;
public GetGuildAuditLogRequest(GetGuildAuditLogRequest.Builder builder) {
record = builder.build();
}
@Override
public DiscordRequestBuilder create() {
DiscordRequestBuilder discordRequestBuilder = new DiscordRequestBuilder().get()
.path("/guilds/%s/audit-logs".formatted(record.guildId));
record.userId.ifPresent(val -> discordRequestBuilder.queryParam("user_id", val));
record.actionType.ifPresent(val -> discordRequestBuilder.queryParam("action_type", val));
record.before.ifPresent(val -> discordRequestBuilder.queryParam("before", val));
record.after.ifPresent(val -> discordRequestBuilder.queryParam("after", val));
record.limit.ifPresent(val -> discordRequestBuilder.queryParam("limit", val));
record.reason.ifPresent(reason -> discordRequestBuilder.putHeader("X-Audit-Log-Reason", reason));
return discordRequestBuilder;
}
public static class Builder {
private final long guildId;
private Optional<Long> userId;
private Optional<Integer> actionType;
private Optional<Long> before;
private Optional<Long> after;
private Optional<Integer> limit;
private Optional<String> reason;
public Builder(long guildId) {
this.guildId = guildId;
this.userId = Optional.empty();
this.actionType = Optional.empty();
this.before = Optional.empty();
this.after = Optional.empty();
this.limit = Optional.empty();
this.reason = Optional.empty();
}
public Builder setUserId(long userId) {
this.userId = Optional.of(userId);
return this;
}
public Builder setActionType(int actionType) {
this.actionType = Optional.of(actionType);
return this;
}
public Builder setBefore(long before) {
this.before = Optional.of(before);
return this;
}
public Builder setAfter(long after) {
this.after = Optional.of(after);
return this;
}
public Builder setLimit(int limit) {
if (limit > 100 || limit < 1) {
throw new IllegalArgumentException("limit must be between 1-100");
}
this.limit = Optional.of(limit);
return this;
}
public Builder setReason(String reason) {
this.reason = Optional.of(reason);
return this;
}
private Record build() {
return new Record(
guildId,
userId,
actionType,
before,
after,
limit,
reason
);
}
}
}
though...
it conflicts with the design of this being a internal class
so we could make a core.builder package that houses the builders instead of doing it within the records
might be better so we don't have to touch the records
so this instead:
package com.javadiscord.jdi.core.builder;
import com.javadiscord.jdi.internal.api.audit_logs.GetGuildAuditLogRequest;
import java.util.Optional;
public class GetGuildAuditLogRequestBuilder {
private final long guildId;
private Optional<Long> userId;
private Optional<Integer> actionType;
private Optional<Long> before;
private Optional<Long> after;
private Optional<Integer> limit;
private Optional<String> reason;
public GetGuildAuditLogRequestBuilder(long guildId) {
this.guildId = guildId;
this.userId = Optional.empty();
this.actionType = Optional.empty();
this.before = Optional.empty();
this.after = Optional.empty();
this.limit = Optional.empty();
this.reason = Optional.empty();
}
public GetGuildAuditLogRequestBuilder setUserId(long userId) {
this.userId = Optional.of(userId);
return this;
}
public GetGuildAuditLogRequestBuilder setActionType(int actionType) {
this.actionType = Optional.of(actionType);
return this;
}
public GetGuildAuditLogRequestBuilder setBefore(long before) {
this.before = Optional.of(before);
return this;
}
public GetGuildAuditLogRequestBuilder setAfter(long after) {
this.after = Optional.of(after);
return this;
}
public GetGuildAuditLogRequestBuilder setLimit(int limit) {
if (limit > 100 || limit < 1) {
throw new IllegalArgumentException("limit must be between 1-100");
}
this.limit = Optional.of(limit);
return this;
}
public GetGuildAuditLogRequestBuilder setReason(String reason) {
this.reason = Optional.of(reason);
return this;
}
public GetGuildAuditLogRequest build() {
return new GetGuildAuditLogRequest(
guildId,
userId,
actionType,
before,
after,
limit,
reason
);
}
}
discord.sendRequest(
new GetGuildAuditLogRequestBuilder(1L)
.setLimit(10)
.setBefore(99999999)
.build());
i don't like the syntax, might be too complicated for a user
I'll sleep on it
we could just have methods on guild and stuff
like
that returns an instance of the builder, with the guildid and stuff already ste
guild.sendMessage() <-- returns a CreateMessageRequestBuilder
Let’s hear it
I'll explain it in detail later but essentially, in the cache, we have a Guild model. That's essentially our way into getting guild specific data (cache)
Now what if, instead of returning the data model, we return a different Guild object
One that has all the helper methods we need
However, we can future create a hierarchy
Like a GuildWrapper(Guild guild) and then delegates all the methods to guild?
Yeah
But the guildwrapper would be a proxy object (or similar)
To the real handlers
So in each API package, we can have a class that contains all the actual request builders
Now for syntax we have 2 choices:
guild.sendMessage(channel, message)
Or
guild.getChannel(id).sendMessage(message)
or if those methods are not appealing, we can do something else
Alternatively, we could try get very black-magic
And try something using reflection and under the hood do some wacky complex logic
What a hacky black magic solution looks like? Idk
Just saying but this is way better
Yeah maybe? I was thinking the same but I'm not sure if it's the "best approach" yet
Since, I've only identified 2 ways
modules are supposed to work on its own can ur modules work on its own?
projects*
Here's a breakdown
annotations: independent - just contains stuff like @EventListener or @MessageCreate
models: independent - just contains records
api: semi-independent - requires :models
cache: independent - unit tests require :models
gateway: not-independent - requires :models :annotations :cache
but it can't do anything, gradle projects are for example spring di, i just import di part and that only works on its own
without the rest of the spring
wym it can't do anything?
if i just import annotations can it start to work as eventlistener
no so this modularity is for us
the end user will import
:core
core is the final result of everything
which is what we deploy to maven
this is just modularising the project for development - not for end user dependencies
the only module that's not independent is :gateway
actually
it is
none of the module is independent
how are they not independent?
sub-project*
it cannot function on its own
they can function on their own they just have dependencies on the non-functional components i.e. models and annotations
there's a little dependency on :cache but is its own thing that can be deployed as a separate artifact for anybody to use
it's either we duplicate all the models/annotations into each module or we just put them in their own jar (like we are now)
however once we generate the final artifacts
gateway can run on its own
api can run on its own
they can be deployed and used separately by other applications
e.g. if somebody wanted to make their own discord framework but did not want to implement the gateway themselves, they can just add the :gateway dependency and start using it
or if they want to use our models so they don't need to write it themselves, they can use :models in their own projects
:core is our framework which creates our specific discord framework
sure the modules are not 100% independent of each other - but that's normal - often in the real world you'll see shared or utils being modules that are used between sub-projects
if we're making independent artifacts, they would then be inside their own repository
Unsure if I should be putting more than just payload_json into multipartbody for Execute Webhook request
@Override
public DiscordRequestBuilder create() {
DiscordRequestBuilder discordRequestBuilder =
new DiscordRequestBuilder()
.post()
.path("/webhooks/%s/%s".formatted(webhookId, webhookToken))
.multipartBody(
MultipartBodyPublisher.newBuilder()
.textPart("payload_json", payloadJson)
.build()
);
waits.ifPresent(val -> discordRequestBuilder.queryParam("wait", val));
threadId.ifPresent(val -> discordRequestBuilder.queryParam("thread_id", val));
return discordRequestBuilder;
}
The endpoint
Someone did Execute Webhook before me?
No lol
Im still on the webhook API
Did u include everything into mulitpartbodyhandler for the poll api?
i think this is ok
Ight u guys can double check it later when i open a PR anyway
of course!
will you be fine to wait a little for a review? i currently have this open: https://github.com/javadiscord/java-discord-api/pull/84
the reason being, it's easier to rebase your PR then will be mine 
Ya im not finished with the webhook api anyway
ty
wazie,shouldn't the annotations be in gateway since that's their only use?
the annotations are used by the end user
for example
import com.javadiscord.jdi.core.Guild;
import com.javadiscord.jdi.core.annotations.EventListener;
import com.javadiscord.jdi.core.annotations.MessageCreate;
import com.javadiscord.jdi.internal.models.channel.Channel;
import com.javadiscord.jdi.internal.models.message.Message;
@EventListener
public class Example {
@MessageCreate
public void onMessageCreate(Message message, Guild guild) {
Channel channel = guild.getChannel(message.channelId());
System.out.println(channel.name());
}
}
I added a new Guild object :3
the logic behind getChannel is kinda messyyyyyyyyyyyyyyyyyy
public Channel getChannel(long channelId) {
CompletableFuture<Channel> future = new CompletableFuture<>();
if (cache.isCached(channelId, Channel.class)) {
future.complete((Channel) cache.get(channelId, Channel.class));
} else {
discord.sendRequest(new FetchChannelRequest(channelId))
.onSuccess(
res -> {
if (res.status() == 200) {
try {
Channel channel =
OBJECT_MAPPER.readValue(res.body(), Channel.class);
cache.add(channelId, channel);
future.complete(channel);
} catch (JsonProcessingException e) {
future.completeExceptionally(e);
}
} else {
future.completeExceptionally(
new RuntimeException(
"""
Failed to fetch channel %s, received status %s
%s
"""
.formatted(
channelId,
res.status(),
res.body())));
}
})
.onError(future::completeExceptionally);
}
try {
return future.get();
} catch (Exception e) {
return null;
}
}
fkin ugly right?/
yes but their only use is with the gateway so having them in their own module is reundandt
it's not used at all by the gateway
my bad
the module is
it's used by :core and :gateway
The other modules make sense:
- models are used in
apiandgateway, - but
annotationsare only used ingateway
you're right but
consider this:
import com.javadiscord.jdi.core.annotations.EventListener;
import com.javadiscord.jdi.internal.annotations.EventListener;
I don't think I understand what you are showing
but :gateway is com.javadiscord.jdi.internal
yeah unless we just create a core package inside :gateway?
the idea is "bad", maybe it leaks a bad design?
though, i have been thinking of what taz said
ig
maybe we should make annotations a thing where a user adds it and then they have access to annotation based processing
and without it, they can do manual binding?
have to think about it hard and what that means for refactoring
but it's minor organisational topic not really affecting functionality
we can decide how to organise and structure folders etc later ig
alright
no
:3
I forced merged 😄
but wait - i am doing some stuff
I have another one
I'm also making it so that :annotations is independent i.e. if a user does not include the lib then they cannot use annotation based processing. instead they have to do discord.registerListener(new SomeClass())
🔫
wait, what do people do then in annotations
do they just add the annotations to methods and go on with their day?
plz untrash that code I cannot use a library of someone who makes such code
return null 💀
I've untrashed it don't worry 
good
Scan class path, find annotations, instantiate them
rip startup performance
Not really, it's pretty quick
meh idrc about performance anymore I'm less of a premature optimizer now lol
quicker is better tho
looking forward to using this and asking questions
It takes me maybe 5 seconds to compile and have the bot online
Scanning the class path takes 1ms since it's already available
It's only just reading the bytecode but there's a library that does it quick af for us
oh? I thought you used reflection for all of that
Yeah there's reflection involved
whats missing in this library rn
for annotations though, I'd assume it's light reflection
Java has built in stuff e.g. method.hasAnnotation(..)
Just records containing the responses from the API calls
And gosh this one is gonna take ages even with a bunch of us working on it
well idk much about creating internals of a discord bot wrapper
I tried to make cooldowns and concurrency for a popular python discord lib but I didn't even know about concurrency back then or even a simple data structure like a HashMap lol
Opened a PR for webhook api
@restive void
In this what are those Object's
also I think for files you need to use filePart
It should take in Path
also ig hes using old spotless?
workflow passed
also ig for payloadjson we should accept Optional<Map<String, Object>>
idk how dat but this is the old formatting
It was stuff like Allowed Mention object, message component, attachment object but I didnt know what to put for it
Ill try incorporating the filePart stuff tmrw
If there is a missing object/data structure it'd be best to add it or atleast make an issue for it since it makes it easier to track
Gotcha
I am out of context, but shouldn’t you use empty collection instead of optional/null collections?
get in context
Then give me context
Optional beause the API does not require it to be present
if it's present the API will respond differently i.e. the data is going to be different (structure the same)
so we only include the fields in the request when optional.ifPresent
Hi peeps, can we do a little retrospective? it's been a month and here's our current achievements:
- Implemented the websocket gateway
- Implemented almost ALL of discord API
- Created an annotation processing system
- A good start on the user interface
- Create and followed an agile board on GitHub
We have done a really good job for only working on this for 1 month.
For the retrospective, could you just drop a message on:
- What went well?
- What could be done better?
- Any problems you have with the current management of this project? Is it working? Can we improve?
- Any other comments?
Thanks!
@orchid portal @restive void @plain ledge @sand peak
ready for review: https://github.com/javadiscord/java-discord-api/pull/86
(take that back, I forgot to finish GuildRequest)
uh I think I was reading bytecode when I was doing the hot reloading
Or I'm using it to get the class name from a .class file as part of the annotation processing
Will confirm when I get out of bed
You should try the classfile api tho it is in preview
I can't because of the nature of the project, ideally I should be targeting java 8
Right now, we've done development using java 21, but I'll work on migrating down to java 8 so I can support everyone
You shouldn't do that imo
Why? 👀
Keeping projects with old version make everything go backward because nobody will update
Ah and also, java 8 is no longer supported
Ah is it not? Then at least java 11, the reason being, AWS for example don't give latest java unless you manually install it
And not being able to run on older versions might be a deal breaker for people wanting to host bots on servers that haven't got updated versions
It should support 17
do u know how many record classes we use

java {
source_version = 22
target_version = 8
}
ok probably 8 wont work with 22
oh yeah java 17
can somebody finish the GuildRequest class for me? 
why use java 11 or 8
that's only somewhat useful in Minecraft situations
nowhere else is it good
use java 21
it's so much better
no
the reason being, not everyone is using java 21 so anybody on an older version cannot use this framework we're making
ok but? why should we care, they should simply use java 21 then
use java 17 minimum
heck, I don't even use java 21 on some projects because intellij defaults
use java 21
ez pz
enforce latest version
yes what squid said
it's a simple "oh i need java 21? dunno how to install, cya"
?
?
dunno how to install
no one ever
especially not people that are working with java frameworks and maven/gradle
AWS doesn't even support java 21 unless you manually install it
and?
people like weeks into java?
also all you do is go to the website
and install it
the point is, everyone should be able to use it
yeah they can
without being forced to change their current system
literally if someone doesn't know how to install java they wouldn't be learning it because they can't use it
simple as that
well everyone should have latest version anyways, so they dont need to change
but they don't
sure maybe they exist
we care because we want everyone to use our framework?
its not our part to use outdated stuff
well they can
they won't
bro I wont contribute to some project using outdated java ngl
so give up all the work java put years in just because of a few measly people who just don't wanna update?
there is literally 0 reason to not use latest version
enforce latest version, they need to update sooner or later anyways
little amount of apps require older versions because they too lazy to update that lil code base or in very very rare senarios they must
you can still write in java 21
but in those very very rare senarios there is like 0 discord development
there's just going to be a java 17 branch alongside it
how does that fix anything
infact that makes it worse
i just looked at what's LTS
dumb imo 🤷♂️
anything you make to 1 branch you make for the other
Yeah, I think supporting java 8, 11 is useless
yeah i didn't realise 11 ended
It’s gonna be aids to maintain
