Skip to content

Instantly share code, notes, and snippets.

@msciotti
Last active February 21, 2021 09:37
Show Gist options
  • Save msciotti/b1dabde51ebe2f3e875faf73a7a3509b to your computer and use it in GitHub Desktop.
Save msciotti/b1dabde51ebe2f3e875faf73a7a3509b to your computer and use it in GitHub Desktop.

Resolving IDs to Objects in Interactions

Sup. We've heard the community feedback and want to take some steps in reducing the number of necessary data lookups when using interactions. One of the most common suggestions was turning IDs into resolved objects in the interactions payload. Here's how we're thinking of doing that.

  • IDs that are requested as arguments in a command will stay just IDs
  • You will also receive a new resolved field on interactions that include the objects that correspond to those IDs
  • The top-level guild_id and channel_id will not be resolved

For arguments, here are the data fields you will receive:

  • Member: the guild member object, if we can resolve one, without a user
  • User: the full user object (always returned if requested)
  • Channel: a partial channel object containing id, type, name, and permissions (which is the computed permissions for the invoking user in that channel, including overwrites)
  • Role: the full role object

These fields will be resolved into a resolved object on the top level of the interaction model. resolved contains a dictionary of object types (channels, users, etc) that each contain a dictionary of id to resolved object.

Here's an example payload:

{
    "type": 2,
    "token": "A_UNIQUE_TOKEN",
    // all standard iteraction stuff in the docs
    "id": "786008729715212338",
    "guild_id": "290926798626357999",
    "channel_id": "645027906669510667", 
    "data": { 
        "name": "cardsearch",
        "id": "771825006014889984",
        "options": [
          {
            "name": "role",
            "value": "53908232506183680"
          },
          {
            "name": "channel",
            "value": "53908232506183680"
          },
          {
            "name": "user",
            "value": "53908232506183680"
          },
        ],
        "resolved": {
          "members": {
            "53908232506183680": { // the member object }
          },
          "users": {
            "53908232506183680": { // the user object }
          },
          "roles": {
            "53908232506183680": { // role object }
          },
          "channels": {
            "53908232506183680": { // partial channel object }
          }
        }
    }
}

Why not guild id and channel ID

We looked through the models and reasoned about whether or not there was a use case for sending this data every time about the invoking channel or guild. Ultimately, we felt that the most-needed data was present elsewhere in the interactions payload.

More specifically, we reasoned that bots that needed specific information about an object would request it as an argument. For example, if you are using /ban, you don't need to know the topic or name of the invoking channel. You will want to know information about the target channel, if there is one, which is why we are resolving some of those fields (with room to add more if necessary).

Furthermore, a lot of the important information about the request is present in other parts of the interaction payload. For example, you can use member.permissions to check the invoker's permissions without needing to know every role on the guild and do the math yourself. While there are bots that want to know things like invoking channel or guild name, or topic, etc. it is not a common enough use case to send that data to every bot, and we felt those were best left to individual GET requests and caching.

However, now that we have this system, there is room in the future if it's necessary.

Why a resolved field?

One, we can do it without a breaking change, which is nice. However, making a breaking change here would've been fine.

The server is always going to receive only the id from the client. Clients are not authoritative sources of data, so we need to do the lookups server-side.

So, we've got a bunch of IDs. There are two ways to do this:

  • Resolve them into a separate object
  • Resolve them in place

Resolving them in place (replacing IDs with objects) requires us to loop over the nested structure and sequentially query for each object, which are blocking operations, and would mean the user waits a long time to get a response.

Putting the resolved objects into the resolved field allows us to do asynchronous mass-lookups of data and stuff them all into an object when they're complete. This scales much better, especially when planning ahead for things like array-type arguments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment