Skip to content

Commit

Permalink
Respect 2000 character limit in builtins::servers (serenity-rs#204)
Browse files Browse the repository at this point in the history
* Split builtins::servers message into header and body

Don't append servers that would push beyond the 2000 character limit from discord.

* Rewrite servers function

Unify "unavailable", "private" and overflowing guilds into "hidden" guilds (summarized as "remaining servers" in the output)

* Fit even more lines

And add safe guard in case any future dev accidentally makes the last line longer than 60 chars and makes the response go beyond 2000 chars

---------

Co-authored-by: kangalioo <[email protected]>
  • Loading branch information
NotNorom and kangalio authored Sep 18, 2023
1 parent 3ff829d commit 3afd86c
Showing 1 changed file with 55 additions and 52 deletions.
107 changes: 55 additions & 52 deletions src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,70 +225,73 @@ pub async fn servers<U, E>(ctx: crate::Context<'_, U, E>) -> Result<(), serenity

let show_private_guilds = ctx.framework().options().owners.contains(&ctx.author().id);

/// Stores details of a guild for the purposes of listing it in the bot guild list
struct Guild {
/// Name of the guild
name: String,
/// Number of members in the guild
num_members: u64,
/// Whether the guild is public
is_public: bool,
// Aggregate all guilds and sort them by size
let mut hidden_guilds = 0;
let mut hidden_guilds_members = 0;
let mut shown_guilds = Vec::<(String, u64)>::new();
for guild_id in ctx.cache().guilds() {
match ctx.cache().guild_field(guild_id, |g| {
(
g.name.clone(),
g.member_count,
g.features.iter().any(|x| x == "DISCOVERABLE"),
)
}) {
Some((name, member_count, is_public)) => {
if !is_public && !show_private_guilds {
hidden_guilds += 1; // private guild whose name and size shouldn't be exposed
} else {
shown_guilds.push((name, member_count))
}
}
None => hidden_guilds += 1, // uncached guild
}
}
shown_guilds.sort_by_key(|(_, member)| u64::MAX - member); // sort largest guilds first

let guild_ids = ctx.cache().guilds();
let mut num_unavailable_guilds = 0;
let mut guilds = guild_ids
.iter()
.map(|&guild_id| {
ctx.cache().guild_field(guild_id, |guild| Guild {
name: guild.name.clone(),
num_members: guild.member_count,
is_public: guild.features.iter().any(|x| x == "DISCOVERABLE"),
})
})
.filter_map(|guild| {
if guild.is_none() {
num_unavailable_guilds += 1;
}
guild
})
.collect::<Vec<_>>();
guilds.sort_by_key(|guild| u64::MAX - guild.num_members); // descending sort
// Iterate guilds and build up the response message line by line
let mut response = format!(
"I am currently in {} servers!\n",
shown_guilds.len() + hidden_guilds
);
if show_private_guilds {
response.insert_str(0, "_Showing private guilds because you are a bot owner_\n");
}
let mut guilds = shown_guilds.into_iter().peekable();
while let Some((name, member_count)) = guilds.peek() {
let line = format!("- **{}** ({} members)\n", name, member_count);

let mut num_private_guilds = 0;
let mut num_private_guild_members = 0;
let mut response = format!("I am currently in {} servers!\n", guild_ids.len());
for guild in guilds {
if guild.is_public || show_private_guilds {
let _ = writeln!(
response,
"- **{}** ({} members)",
guild.name, guild.num_members
);
} else {
num_private_guilds += 1;
num_private_guild_members += guild.num_members;
// Make sure we don't exceed a certain number of characters below the 2000 char limit so
// we have enough space for the remaining servers line
if response.len() + line.len() > 1940 {
for (_remaining_guild_name, members) in guilds {
hidden_guilds += 1;
hidden_guilds_members += members;
}
break;
}

response += &line;
guilds.next(); // advance peekable iterator
}
if num_private_guilds > 0 {
let _ = writeln!(
response,
"- [{} private servers with {} members total]",
num_private_guilds, num_private_guild_members
);
}
if num_unavailable_guilds > 0 {
if hidden_guilds > 0 {
let _ = writeln!(
response,
"- [{} unavailable servers (cache is not ready yet)]",
num_unavailable_guilds
"- {} remaining servers with {} members total",
hidden_guilds, hidden_guilds_members
);
}

if show_private_guilds {
response += "\n_Showing private guilds because you are the bot owner_\n";
// Final safe guard (shouldn't be hit at the time of writing)
if response.len() > 2000 {
let mut truncate_at = 2000;
while !response.is_char_boundary(truncate_at) {
truncate_at -= 1;
}
response.truncate(truncate_at);
}

// If we show sensitive data (private guilds), it mustn't be made public, so it's ephemeral
ctx.send(|b| b.content(response).ephemeral(show_private_guilds))
.await?;

Expand Down

0 comments on commit 3afd86c

Please sign in to comment.