[Bug]: AddRoleAsync() and AddRolesAsync() removes roles after adding them
See original GitHub issueCheck The Docs
- I double checked the docs and couldn’t find any useful information.
Verify Issue Source
- I verified the issue was caused by Discord.Net.
Check your intents
- I double checked that I have the required intents.
Description
AddRoleAsync()
and AddRolesAsync()
removes roles after adding them when using Discord.Net 3.10.0.
After downgrading to Discord.Net 3.9.0, AddRoleAsync()
and AddRolesAsync()
functions as intended.
Version
3.10.0
Working Version
3.9.0
Logs
n/a
Sample
public async Task HandleMenuInteraction(SocketMessageComponent messageComponent)
{
switch(messageComponent.Data.CustomId)
{
case "role-selection":
Console.WriteLine($"{messageComponent.User.Username} has changed their role subscriptions.");
var values = messageComponent.Data.Values.ToList<string>();
Console.WriteLine($"{messageComponent.User.Username} has selected the following role subscriptions:");
foreach(var v in values) {
Console.WriteLine(v);
}
var user = (IGuildUser) messageComponent.User;
if(user != null)
{
List<string> allowedRoles = new List<string>()
{
"Games",
"Technology",
"Programming",
"Art",
"Music"
};
List<ulong> addedRoles = new List<ulong>();
List<ulong> removedRoles = new List<ulong>();
string roleSubscriptions = "";
foreach(var i in allowedRoles) {
try {
var role = user.Guild.Roles.First(x => x.Name == i);
if(values.Contains(role.Name)) {
addedRoles.Add(role.Id);
roleSubscriptions += $@"
:bell: {role.Name}";
Console.WriteLine($"{role.Name} ({role.Id}) was added to {messageComponent.User.Username}.");
}
else {
removedRoles.Add(role.Id);
roleSubscriptions += $@"
:no_entry_sign: {role.Name}";
Console.WriteLine($"{role.Name} ({role.Id}) was removed from {messageComponent.User.Username}.");
}
}
catch(Exception ex) {
Console.WriteLine(ex);
}
}
await user.AddRolesAsync(addedRoles);
await user.RemoveRolesAsync(removedRoles);
await messageComponent.DeferAsync();
await messageComponent.ModifyOriginalResponseAsync(x => x.Content = $"{user.Mention} is now subscribed to the following channels: {roleSubscriptions}");
}
break;
}
}
Packages
Discord.Net V3.9.0
Issue Analytics
- State:
- Created 6 months ago
- Comments:27 (4 by maintainers)
Top Results From Across the Web
userManager.AddToRoleAsync() - Error: role does not exist
I'm creating a user registration system using .NET Core, Identity Core, and MVC Core. I'm able to create users and create roles in...
Read more >Untitled
Adding roles on discord 170+ Good Discord Role Ideas and Names ... roles with Zapier Zapier [Bug]: AddRoleAsync() and AddRolesAsync() removes roles after...
Read more >(Possible Bug) Roles are getting removed every time ...
When you've joined a Discord server and entered a voice channel, you either click the button next to 'Voice Disconnected' and leave the...
Read more >Roles.RemoveUserFromRoles(String, String[]) Method
Removes the specified user from the specified roles.
Read more >C# (CSharp) IGuildUser.ModifyAsync Examples
Now.AddSeconds(30), async() => { if (guser.VoiceChannel == null) { // If they haven't been muted yet, remove them from the 'to mute' list...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Previous versions implemented
AddRolesAsync
by locallyforeach
ing over the list and callingAddRoleAsync
once for each role. That means if you want to add 5 roles, it would make 5 separate API calls (and accumulate 5 hits against your rate limit).Discord has an endpoint for bulk-editing roles, and naturally the bulk-edit methods should use that endpoint. But that endpoint sets the user’s roles to whatever list is received, rather than adding or removing. Which means
AddRolesAsync
now locally needs to grab the user’s current role list and update it to add any new IDs, then send the full new list to the server.Take this example:
That first
AddRolesAsync
will calculate the new role list as[1, 2, 3, 4]
and send that to the server. That’s fine, the user’s roles are now[1, 2, 3, 4]
. But the local cache isn’t updated until we receive a gateway message from the server telling us about the modifiedUser
.The second
AddRolesAsync
will calculate the new role list as[1, 2, 5, 6]
and send that to the server. The user’s roles are now[1, 2, 5, 6]
. Because we haven’t yet processed the gateway message, so the local cache is stale, so it’s not possible to calculate the correct set of roles.So why aren’t we processing the gateway messages in between the two API calls? Well, that depends. If your handler is configured as
RunMode.Sync
(the default), that’ll be one reason: the handler blocks the gateway thread and no gateway messages can be processed until the entire handler is finished. UsingRunMode.Async
could help, but that can cause several other issues (you can then have multiple handlers run in parallel, so you need to make sure your code has no race conditions. Also the post-execution flow and error handling are completely different, see docs and docs.But even using
RunMode.Async
wouldn’t definitively solve this: there’s no guarantee that you’ll receive the gateway message and process it before the next bulk-edit command is run, since multiple async threads are non-deterministically ordered by nature. And without hooking the user updated event there wouldn’t be any way to be sure that the cache is updated before the second call.The simple solution is: don’t use multiple bulk (AddRoles/RemoveRoles) role editing methods in a single handler. Do the merge operations yourself and make a single request; Misha provided example code: https://github.com/discord-net/Discord.Net/issues/2655#issuecomment-1494670584. Alternatively do a
foreach
loop over the roles you want to add/remove and call the singular methods, which is exactly what the old behaviour was - just be aware that this is not ratelimit-friendly.e: I wonder if there’s any reason the bulk-edit methods can’t update the local cached copy directly? Maybe it’s just hard to do with how it’s currently implemented :\
Ah, yup, that’s most likely the issue
@douglasparker You could use
user.ModifyAsync
instead in this particular case the code would look likeThis adds & removes roles in a single API request, so it’s more ratelimit friendly.