twilight-rs / twilight

Powerful, flexible, and scalable ecosystem of Rust libraries for the Discord API.
https://discord.gg/twilight-rs
ISC License
673 stars 132 forks source link

Ambiguity between Guild Joining and Unavailable Guilds becoming Available #2372

Open fdnt7 opened 1 month ago

fdnt7 commented 1 month ago

Summary

twilight-model on the latest commit (f58b540 as of writing) deserialises these two distinct events:

as the same object, namely GuildCreate::Available(Guild { unavailable: false, .. }). This behaviour should be undesirable, as information was lost about which event it was.

Steps to reproduce

  1. Copy the minimal bot setup as stated in the twilight-gateway example here. Pin twilight-http, twilight-gateway and twilight-model to revision f58b540.
  2. Apply the following diff:
    10a11
    > use twilight_model::gateway::payload::incoming::GuildCreate;
    60c61,70
    <         tracing::debug!(?event, shard = ?shard.id(), "received event");
    ---
    >         match event {
    >             Event::Ready(_) => tracing::info!("Bot is ready"),
    >             Event::GuildCreate(guild_create) => {
    >                 match *guild_create {
    >                     GuildCreate::Available(guild) => tracing::info!(?guild.unavailable, ?guild.id, "Available"),
    >                     GuildCreate::Unavailable(guild) => tracing::info!(?guild.unavailable, ?guild.id, "Unavailable"),
    >                 }
    >             }
    >             _ => continue
    >         }
  3. Prepare a bot account. Before running the bot, add it to some guild with some ID ID1.
  4. Run the bot via running DISCORD_TOKEN="..." cargo run, replacing ... with the bot's token.
  5. Wait until "Bot is ready" appears in the log. Then, add the bot to another guild with some ID ID2.

Expected Behaviour

Upon the same time as "Bot is ready" appears in the log, the guild of ID ID1 should become available, appearing in the logs as an event of a guild becoming available. This is a documented behaviour as scenario 1. Then, as the bot is added to another guild of ID ID2, it should appear in the logs as an event of joining a guild. These two events are distinct so the logs should reflect that.

Actual Behaviour

The logs are as shown:

YYYY-MM-DDTHH:MM:SS.FFFFF0Z  INFO rust_playground: Bot is ready
YYYY-MM-DDTHH:MM:SS.FFFFF1Z  INFO rust_playground: Available guild.unavailable=false guild.id=Id<GuildMarker>(ID1)
YYYY-MM-DDTHH:MM:SS.FFFFF2Z  INFO rust_playground: Available guild.unavailable=false guild.id=Id<GuildMarker>(ID2)

(Time information is redacted, but time FFFFF0 and FFFFF1 should be negligibly near each other, and FFFFF2 should be different than the other two timestamps.)

Due to how twilight deserialises these two distinct events, they both show up in the logs as the exact same thing as shown by the logs above, losing the information about whether which was originally sent.

Additional Information

Although not documented, it can be verified that the Discord Gateway differentiate these two events as the following:

And this is exactly how Hikari, a python Discord API library, handle this: It separates the event into GuildJoin and GuildAvailable, and upon ShardReady, the unavailable guilds become available with GuildAvailable, and guild joins register as GuildJoin, correctly differentiating the two. Here's the python code which was used to verify:

import os
import logging

import hikari

bot = hikari.GatewayBot(token=os.environ["DISCORD_TOKEN"])

@bot.listen()
async def shard_ready(event: hikari.ShardReadyEvent) -> None:
    logging.info("Bot is ready")

@bot.listen()
async def guild_available(event: hikari.GuildAvailableEvent) -> None:
    logging.info(f"Available {event.guild.id}")

@bot.listen()
async def guild_join(event: hikari.GuildJoinEvent) -> None:
    logging.info(f"Join {event.guild.id}")

bot.run()

There was a discussion on this Twilight Discord guild a long time ago, but it seems like the thread has been abandoned, so I am bringing this up again (as I was the original poster). As far as I remembered, there was a discussion about how Hikari simply drops events with unavailable field present and is true in GuildCreate (as seen here) and how that was not the preferred way to handle events in Twilight, which I thought was a fair point. However, Twilight can both remain this crucial differentiation and still keep every information about the event as it was received, without dropping any events, as well.

I've seen and tried both #2330 and #2361, which seems related. Pinning to their individual revisions, neither of them addresses this issue.