I'll summarize the information discussed in the comments, as well as the answer provided by OP in the edit to their original question. I believe this deserves to be a community wiki answer but I lack the reputation to mark it as such.
The original code halted unexpectedly after around 200 members. While the exact cause is unclear, fixing the following inefficiencies resolved the issue, suggesting that the problem stemmed from one (or a combination) of them.
guild = discord.utils.get(bot.guilds, name = GUILD)
The guild object is already in the context
object.
✓ OP deleted this line, and further uses of guild
were changed to context.guild
.
ServerMembers = [i for i in guild.members]
First, guild.members
is already a list, making this list comprehension unnecessary. ServerMembers
will be the exact same list as guild.members
every time this line is ran.
In addition, guild.members
itself potentially is potentially an incomplete list of members. guild.members
relies on the bot's internal cache, which can be incomplete if the member list wasn’t fully populated. Switching to fetch_members()
ensures the bot gets a complete list from the Discord API, even if the cache is incomplete.
✓ OP writes async for user in context.guild.fetch_members(limit = None):
if (EmployeeRoleUS or EmployeeRoleCA) in user.roles
As explained in this link by Queuebee, the above conditional does not work as intended. Assuming both EmpolyeeRoleUS
and EmployeeRoleCA
are not None, the above line is equivalent to just if EmployeeRoleUS in user.roles
(the Canadian role is ignored).
✓ OP fixes this line to if (EmployeeRoleUS in user.roles) or (EmployeeRoleCA in user.roles)
await guild.get_member(user.id).add_roles(guild.get_role(EMPLOYEE_FORMER_ROLE))
The user
object is already an instance of Member
from guild.fetch_members()
.
An additional suggestion not in OPs solution would be to define EmployeeFormerRole = guild.get_role(EMPLOYEE_FORMER_ROLE)
once so it does not need to be redefined for every member. This would make the final line become user.add_roles(EmployeeFormerRole)
✓ OP changes it to user.add_roles(guild.get_role(EMPLOYEE_FORMER_ROLE))
which is sufficient.
await guild.get_member(user.id).edit(roles = []) # Remove all roles
✓ OP opted to remove this line completely.
await asyncio.sleep(15)
The Discord.py library automatically handles rate limits internally.
✓ OP removed the manual await asyncio.sleep(15)
.
After making these changes, OP reported that the bot processed over 1,000 members successfully in around 24 minutes (~1-2 seconds per member) — confirming that the fixes resolved the halting issue.
It is unknown what exactly fixed the original issue, but it can be presumed that it was one of or a combination of the above changes.
@bot.command(name = "prune_unverified", help = "prune the unverified employees", enabled = True)
@commands.has_role("Owner")
async def prune_unverified(context):
await context.message.delete()
VerifiedEmployees = []
PrunedUsers = []
EmployeeRoleUS = context.guild.get_role(EMPLOYEE_ROLE)
EmployeeRoleCA = context.guild.get_role(EMPLOYEE_CA_ROLE)
VerifiedRole = context.guild.get_role(EMPLOYEE_VERIFIED_ROLE)
FormerRole = context.guild.get_role(EMPLOYEE_FORMER_ROLE)
# Fetch members directly from Discord API
async for user in context.guild.fetch_members(limit=None):
if (EmployeeRoleUS in user.roles) or (EmployeeRoleCA in user.roles):
if VerifiedRole in user.roles:
VerifiedEmployees.append(user)
else:
PrunedUsers.append(user)
# Update roles for pruned users
for user in PrunedUsers:
await user.edit(roles=[]) # Remove all roles
await user.add_roles(FormerRole) # Add former employee role
# Create CSV files of results
with open("pruned_users.csv", mode="w") as pu_file:
pu_writer = csv.writer(pu_file)
pu_writer.writerow(["Nickname", "Username", "ID"])
for user in PrunedUsers:
pu_writer.writerow([user.nick, f"{user.name}#{user.discriminator}", user.id])
with open("verified_users.csv", mode="w") as vu_file:
vu_writer = csv.writer(vu_file)
vu_writer.writerow(["Nickname", "Username", "ID"])
for user in VerifiedEmployees:
vu_writer.writerow([user.nick, f"{user.name}#{user.discriminator}", user.id])
# Send results to Discord
embed = discord.Embed(
description=f":crossed_swords: **{len(PrunedUsers)} users were pruned by <@{context.author.id}>.**"
f"\n:shield: **{len(VerifiedEmployees)} users completed re-verification.**",
color=discord.Color.blue()
)
await context.send(embed=embed)
await context.send(file=discord.File("pruned_users.csv"))
await context.send(file=discord.File("verified_users.csv"))