discord-jda / JDA

Java wrapper for the popular chat & VOIP service: Discord https://discord.com
Apache License 2.0
4.29k stars 739 forks source link

Enable referencing or instantiation of InteractionHooks outside of events #1665

Closed Arraying closed 3 years ago

Arraying commented 3 years ago

General Troubleshooting

Feature Request

Currently, interactions such as slash commands or button presses are presented to you to handle through a gateway event (unless you opt to receive them as a WebHook, but more on that later).

It would be very helpful to be able to somehow reference InteractionHooks in outside of events in order to draft responses without having to keep a reference to the event., as this is not always possible. For this I propose two possible solutions.

Solution 1: Enable construction of an InteractionHook

The interaction callback URL comprises of a unique ID and a token. Therefore, this solution would be to allow instantiation of an interaction response through any JDA instance. This would take the form of:

InteractionHook createInteractionResponse(long id, String token);
InteractionHook createInteractionResponse(String id, String token);

This would create a neat abstraction for the HTTP call to the interaction callback route. While it is possible to achieve this functionality by just instantiation InteractionHookImpl, first-party API support would greatly enhance the experience.

Solution 2: Keep a SnowflakeCacheView of Interactions

Exactly as the title says it. This would entail having a SnowflakeCacheView of all interactions per JDA instance (so in the case of sharding, per shard). InteractionHookImpl already has a timeout handle, so there is not much cost to expanding that to remove the interaction from a timed cache. It's not like you are explicitly adding a new scheduled task. This has the advantage over solution 1 because it means that all "available" interactions are iterable. This also allows for streaming/filtering which I would consider useful but I have not mentioned it in the example use cases.

Additionally, this feature could be coupled with an InteractionCachePolicy that enables the developer to enable/disable the functionality and further fine-tune caching to their linking.

Example Use-Case

I don't have any explicit code examples on hand, but I hope I can describe the scenarios sufficiently.

Example 1: Large computation

Consider a (hypothetical) mathematical or otherwise computationally intense functionality in a bot, anything that takes e.g. 5-10 seconds to be computed. The bot is able to run every computation within the 15 minute response window. Upon receiving the command/interaction, the response is immediately deferred. The command creates a task (i.e. the computation) and the task object has the interaction ID and token within it.

The task gets added to a task queue, with priority. Numerous worker threads will, concurrently, work through the requests in the order of priority (a combination of first come first serve and their subscription tier). When the worker has processed a task, let's say 8 seconds after the command, it will then read the interaction ID/token and either look up the interaction in the cache (solution 2) or create its own response (solution 1) and send it.

Example 2: Going beyond the gateway

This is less of a hypothetical and an actual use case I have. Discord provides you with the ability to receive interactions as a POST webhook. The majority of my commands for this bot go beyond the Discord API and are utilities/external services where it is sufficient to respond with a single message.

It contains an embedded REST web server which receives real-time updates from my other microservices and takes care of my command handling (i.e. responding to commands after a request to external services). However, there is no way to do this, without being able to create an interaction response myself. I can receive the interaction, but there is no first-party support to instantiate a response without the gateway!

This is further made worse by the fact that gateway and webhook commands are mutually exclusive! So, anyone like me who uses Spring MVC (or other frameworks) and wants to handle commands purely through HTTP is out of luck. In my opinion, that is a very significant feature that JDA is missing out on.

Final words

Personally, I believe that the minimum JDA could do is implement solution 1. While normally I'm quite fond of all the abstraction the library provides, I feel as if the way interactions are designed right now don't really do the API justice, especially seeing that Discord appears to be moving more towards REST while currently JDA requires the gateway. Should this feature request get approved, I'm happy to implement solution 1 myself (perhaps 2, if time allows me to) sometime within the next few weeks. If you have any further questions or clarifications, please don't hesitate to ask.

MinnDevelopment commented 3 years ago

Solution 1: Enable construction of an InteractionHook

There is no problem with using the internal implementation if you want to. First-party support would require a lot more effort since we would need to define systems for both HTTP and gateway and have a consistent interface for both. This is non-trivial and I have intentionally decided not to go through with it.

In the end, the InteractionHook is simply a webhook anyway. You can just use the webhook endpoints directly without JDA's interface.

Solution 2: Keep a SnowflakeCacheView of Interactions

Absolutely won't ever happen. You can just cache it yourself without problems.

Example 1: Large computation

What prevents you from adding the interaction hook to your task?

Example 2: Going beyond the gateway

This does not make a lot of sense. If you use the gateway you would not benefit from doing an HTTP webhook as well. Either you make an HTTP system or a gateway system. Discord has designed it to work like this and JDA is primarily focused on gateway connections. I was considering supporting both and came to the conclusion that it didn't make sense at all.

Why would you make an HTTP system and then forward it to the gateway to handle? This makes no sense at all. The benefits of the HTTP system are that you can load-balance and build a serverless setup, this means you would ideally never interact with the gateway in any way. My suggestion is to either go full gateway or full HTTP.

Arraying commented 3 years ago

Solution 1: Enable construction of an InteractionHook

There is no problem with using the internal implementation if you want to. First-party support would require a lot more effort since we would need to define systems for both HTTP and gateway and have a consistent interface for both. This is non-trivial and I have intentionally decided not to go through with it.

In the end, the InteractionHook is simply a webhook anyway. You can just use the webhook endpoints directly without JDA's interface.

The reason why I'm not writing raw HTTP requests outside from JDA is that I'd prefer everything to be bundled in one. I supposed I could use JDA's HTTP client for this to minimize the effort of me having to set that up, but at the end of the day, the main feature why I even bother with API wrappers for such tasks is that I don't want to do ratelimit handling myself. If there's a trivial way to handle such requests using the appropriate buckets, or even define my own buckets with JDA's existing infrastructure let me know.

Example 1: Large computation

What prevents you from adding the interaction hook to your task?

My service failed, now what? My point is, the entire interaction is incredibly volatile as it only exists (once, as far as I know) in memory. If something does crash or I am forced to restart it, I'm able to recover my own data but there is no API-supported way of recreating something that I need in order to interact with the commands. I cannot think of another entity or functionality that cannot be re-created after the process exited given the ID for this is stored somewhere. Having to use a workaround like InteractionHookImpl is, in my opinion, an incredibly flawed design.

Example 2: Going beyond the gateway

This does not make a lot of sense. If you use the gateway you would not benefit from doing an HTTP webhook as well. Either you make an HTTP system or a gateway system. Discord has designed it to work like this and JDA is primarily focused on gateway connections. I was considering supporting both and came to the conclusion that it didn't make sense at all.

Why would you make an HTTP system and then forward it to the gateway to handle? This makes no sense at all. The benefits of the HTTP system are that you can load-balance and build a serverless setup, this means you would ideally never interact with the gateway in any way. My suggestion is to either go full gateway or full HTTP.

I think you misunderstood my point. I'm trying to go full HTTP, but because of the lacking methods (no way to create an interaction response), I can't do that. I don't actually use the gateway, I terminate the connection (albeit a bit hacky through JDAImpl) and then just use the retrieveX methods whenever I need to retrieve something, which is not often at all.

Regardless, that is my problem, and a specific use case. Not being able to retrieve an interaction in order to respond to it is a problem that will, at one point or another, irritate other users too. I've decided to bring this up because it's not just affecting me, but also some folks who want an easy to use system and are primarily using interactions to engage with the user, and are frustrated at the fact that they are required to constantly reference the gateway object in one way or another.

I still strongly believe that not being able to reference an interaction - and more importantly, the way to respond to one - if you've somehow managed to lose the reference to the gateway object but are still within the reply window is not particularly great. Yes, you may primarily have designed JDA around the gateway and that is a respectable decision, however you have to take into account that slash commands and other interactions have been designed around REST (e.g. lack of registering commands through gateway, the option to use interactions as webhooks instead). There are perfectly legitimate reasons why gateway-using individuals would want something like this.

MinnDevelopment commented 3 years ago

The reason why I'm not writing raw HTTP requests outside from JDA is that I'd prefer everything to be bundled in one.

JDA is not a library for HTTP-only setups. If you want to achieve that use something else.

If something does crash or I am forced to restart it, I'm able to recover my own data but there is no API-supported way of recreating something that I need in order to interact with the commands

I don't plan on implementing memory backups into JDA. This is an unreasonable expectation and I don't think it makes a lot of sense to worry about this kind of scenario as crashes like this can usually not be recovered anyway due to many other issues such as the token and id not being known (unless you back it up every time, which is a bit insane for a token thats only valid for 15 minutes).

I think you misunderstood my point. I'm trying to go full HTTP, but because of the lacking methods (no way to create an interaction response), I can't do that. I don't actually use the gateway, I terminate the connection (albeit a bit hacky through JDAImpl) and then just use the retrieveX methods whenever I need to retrieve something, which is not often at all.

Then you are already using JDA incorrectly. This library does not and will not support gateway-less setups any time soon. If you want to go full HTTP you should stop using JDA.

Having to use a workaround like InteractionHookImpl is, in my opinion, an incredibly flawed design.

You can also simply use any existing libraries other than JDA which support this. It is simply a webhook. And according to your own comments, you don't seem to care about using internals in unintended ways anyway.


In summary, you are using JDA in unintended ways and these use-cases will likely not be supported any time soon. Fundamentally, JDA expects a gateway connection and will only work properly with that setup. If you want to use HTTP-only there are good alternatives you can use other than JDA.

The InteractionHook is a simple interface that just supports the webhook endpoints which are trivial to implement yourself. Additionally, the implementation provided by JDA is specifically designed to properly work with the gateway setup as it does some hackery to make things run in the correct sequence. Using our implementation for an HTTP-only setup would not make sense and I have no interest in supporting such use-cases either.

I have a webhook library that works independently from JDA. If you want, you can PR interaction support to that library by extending the classes there. Eventually, JDA will also support webhooks and with that likely also the interaction webhook endpoints, however that will not happen any time soon due to time constraints.