So I want admins to have the ability to add roles to the list 'admin_roles'. My issue is when calling #commands.has_any_role(*admin_roles), it does not seem to look at anything that has been appended to the list.
Code -
admin_roles = []
#client.command()
#commands.has_any_role('Owner')
async def addadminrole(ctx, role):
global admin_roles
admin_roles.append(role)
#client.command()
#commands.has_any_role(*admin_roles)
async def setinfo(ctx, item, description):
global desc
if(item == 'desc'):
desc = description
So when the code is run, I get the error discord.ext.commands.errors.MissingAnyRole: You are missing at least one of the required roles:
No matter how many things get appended to the list, the has_any_role doesn't check for them.
A decorator is invoked once as soon as the module is imported, and then the function that gets called is whatever was provided by the decorator definition. The problem that you are facing is that you modify your global variable in the middle of your runtime, but this does not cause the decorator to be re-applied, and thus it uses the values from when it was invoked at import. Because admin_roles is an empty list at the time of module import, it will be passed as an empty list into commands.has_any_role. To achieve a similar level of functionality (though not the best), you can do something like:
from discord.ext.commands import MissingAnyRole, NoPrivateMessage
from discord.channel import DMChannel
#client.command()
async def setinfo(ctx, item, description):
global admin_roles
if isinstance(ctx.channel, DMChannel): #if this is a private message, roles do not apply.
raise NoPrivateMessage
if not len([role for role in ctx.author.roles if role in admin_roles]): # if the invoking Member has no roles in admin_roles, throw the same error as check_any_role
raise MissingAnyRole
global desc
if(item == 'desc'):
desc = description
I found another way to solve this issue. I understand what was wrong with my initial code since admin_roles is never called before it is accessed. This is the code I used to circumvent my issue -
#client.command()
async def setinfo(ctx, item, description):
global admin_roles
global desc
x = 0
roles = [0]*len(admin_roles)
while x < len(admin_roles):
roles[x] = discord.utils.get(ctx.guild.roles, name=admin_roles[x])
if roles[x] in ctx.author.roles:
if(item == 'desc'):
desc = description
x += 1
Related
My problem is that I need to get the self.tracker variable from the gift_bot.py file inside the cogs/invite_moduly.py file. But sadly, I don't know a way to do that. I tried a few methods like self.bot.tracker, tracker and more, but none worked.
How can I access this variable in invite_module.py?
File gift_bot.py
class GiftBot(commands.Bot):
def __init__(self):
self.tracker = InviteTracker(self)
super().__init__(command_prefix="*", intents=intents, case_insensitive=True)
async def on_ready(self):
try:
await self.tracker.cache_invites()
except:
pass
cogs/invite_module.py:
class InviteModule(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.last_member: list = []
#commands.Cog.listener("on_member_join")
async def on_member_join(self, member: discord.Member):
invite_info = await get_inviter(self.tracker, member)
With this code, I get:
Unresolved attribute reference 'tracker' for class 'InviteModule'
Assuming that you instantiate InviteModule with bot being a reference to an instance of GiftBot, it would be self.bot.tracker.
self is used to reference the instance of the class where's that method (by convention, it's not a keyword, but it's discouraged to use something other than self). So, inside InviteModule, self is an object of the class InviteModule, and inside GiftBot, it's an object the class GiftBot.
If you want to reference a property of GiftBot inside InviteModule, you must pass an instance of GiftBot to InviteModule¹, which I assume you're doing as the bot property. So, to access it inside InviteModule use self.bot, and hence, the tracker would be self.bot.tracker.
¹ Or, instead of passing the whole bot, you could pass the property itself. For instance, you could do:
class InviteModule(commands.Cog):
def __init__(self, tracker):
self.tracker = tracker
# rest of the code here
And use as:
giftBot = GiftBot()
inviteModule = InviteModule(giftBot.tracker)
I assume you want the first option, but I'm adding this for completeness.
I can be wrong, but I believe the error mentioned in the comments is actually from the IDE.
Per your comment, I suppose class Bot does not have the field tracker. So, you could either:
Change InviteModule init to accept GiftBot
Before trying to access the field tracker, check if the element is an instance of GiftBot.
If there's nothing usable in InviteModule without the field tracker, the first option is probably better:
class InviteModule(commands.Cog):
def __init__(self, bot: GiftBot): # or gift_bot.GiftBot, depending on how you're importing/connecting those files
self.bot = bot
# rest of the code
The second option would go something like:
class InviteModule(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
# rest of init
#commands.Cog.listener("on_member_join")
async def on_member_join(self, member: discord.Member):
if instanceof(self.bot, GiftBot):
invite_info = await get_inviter(self.bot.tracker, member)
# if not instance, define what's desired: ignore, log only, throw an error
I have code like this
class RolePickView(discord.ui.View):
def __init__(self):
super(RolePickView, self).__init__(timeout=None)
self.select_max_values = 1 # default value
self.author_id = None
self.select_options = {}
#discord.ui.select(placeholder='Выберите роль...', custom_id='rolepick-select')
async def select_callback(self, interaction: discord.Interaction, select: discord.ui.Select):
user = interaction.user
for role_id in select.values:
role = interaction.message.guild.get_role(int(role_id))
await user.add_roles(role)
await interaction.response.send_message("Роли выданы", ephemeral=True)
Tell me please, how can I dynamically add SelectOption to this select ?
If you want to dynamically add options to the select it might be better to construct it using a class instead of a decorator and a function (You would add it manually to the View).
I would suggest either passing a list of SelectOptions or using the add_option method.
You can find more information here: https://discordpy.readthedocs.io/en/stable/interactions/api.html?highlight=select#discord.ui.Select.add_option
So I have this file with the following code
async def check_server_restriction(ctx):
restriction = await get_restriction()
global check
if restriction[str(ctx.channel.id)]["Server"] == 1:
await ctx.send("This command is restricted. You can use it in the #bot-commands channel")
check = True
And I have another file that looks like this:
from channel_restrictions_functions import test_187, new_restriction, get_restriction, check_server_restriction, check
class whois(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
async def whois(self, ctx, member:discord.Member = None):
await test_187()
await new_restriction(ctx)
await check_server_restriction(ctx)
#print(check)
if check == True:
print("Nicht perint")
return
So basically I import the function and try to import the variable too. The problem is now when the imported function gets activated and turns check (the variable) True (in theory) nothing happens. It should work like this when the thing is in the list it should send "This command is restricted" and set check true so that in my command the if statement works and it will return.
I dont get whats the mistake. I think it says check = False every time but it should be true
I hope you can understand my problem and you know how to fix it
thx for the help
Even global variables are not truly global, they are global only within the scope of their own module. There are ways to get around this restriction and share a variable between modules, if you absolutely have to, see e.g. here. It is recommended to put all global variables into a separate module and import from there, however, see the Python docs for more on this.
That said, why would you want to do this with a global variable? It's simpler and less error prone (in the sense that the restriction status would be shared between commands if global, which might lead to commands being falsely restricted) to just have check_server_restriction return True or False and then check the return value of the function something like this:
async def check_server_restriction(ctx):
restriction = await get_restriction()
if restriction[str(ctx.channel.id)]["Server"] == 1:
await ctx.send("This command is restricted. You can use it in the #bot-commands channel")
return True
return False
In your code, you'd then use this modified function like so:
if await check_server_restriction(ctx): # the == True can be omitted
print("Nicht perint")
return
#commands.command()
async def reroll(self, ctx, channel: discord.TextChannel, id_ : int):
try:
new_gaw_msg = await ctx.channel.fetch_message(id_)
except:
await ctx.send("Failed to parse command. ID was incorrect.")
return
winners = int(winners.replace("W",""))
users_mention = []
for i in range(winners):
users = await new_gaw_msg.reactions[0].users().flatten()
users.pop(users.index(self.client.user))
winner = random.choice(users)
users_mention.append(winner.mention)
users.remove(winner)
displayed_winners = ",".join(users_mention)
await ctx.send(f"Congragulations {displayed_winners}! You have won the **{prize}**.\n{gaw_msg.jump_url}")
variable winners is referenced before assignment. I gave value to winners before using it so idk why it doesnt work. Any help is appreciated :D
You are trying to perform an operation on the winners variable before it was created.
There is a thing called name "namespace". Basically, variables created globally (without indent) and variables created locally (inside functions or loops) are different variables.
If you want to specifically use a global variable inside the function, you should first declare that the variable will be global by writing global variable_name inside the function. And only then you can perform operations with it.
There are some hidden dangers with the usage of global variables, so I suggest you read more about python namespaces and global keyword by yourself.
A more unusual question today, but maybe someone can help me.
I am working on a bot, which among other things has to do with music. The following problem:
If I change stuff in a cog and then reload it I always get an AttributeError: 'NoneType' object has no attribute 'XXXX' error for the particular command. Is there a way to fix/prevent this?
The error occurs when the bot is in a voice channel for example and then I reload the cog.
I query state for every command which has to do with music, is it maybe related to that?
state = self.get_state(ctx.guild)
Full function for get_state:
def get_state(self, guild):
"""Gets the state for `guild`, creating it if it does not exist."""
if guild.id in self.states:
return self.states[guild.id]
else:
self.states[guild.id] = GuildState()
return self.states[guild.id]
I tried to solve it with a try/except AttributeError, but of course that didn`t really work/the console still gave me the output.
Here is an example code:
#commands.command()
#commands.guild_only()
#commands.check(audio_playing)
#commands.check(in_voice_channel)
#commands.check(is_audio_requester)
async def loop(self, ctx):
"""Activates/Deactivates the loop for a song."""
state = self.get_state(ctx.guild)
status = state.now_playing.toggle_loop()
if status is None:
return await ctx.send(
embed=discord.Embed(title=":no_entry: Unable to toggle loop.", color=discord.Color.red()))
else:
return await ctx.send(embed=discord.Embed(
title=f":repeat: Loop: {'**Enabled**' if state.now_playing.loop else '**Disabled**'}.",
color=self.bot.get_embed_color(ctx.guild)))
And if I make changes in the cog, reload it and then try to run loop again I get the following error:
In loop:
File "C:\Users\Dominik\PycharmProjects\AlchiReWrite\venv\lib\site-packages\discord\ext\commands\core.py", line 85, in wrapped
ret = await coro(*args, **kwargs)
File "C:\Users\Dominik\PycharmProjects\AlchiReWrite\cogs\music.py", line 220, in loop
status = state.now_playing.toggle_loop()
AttributeError: 'NoneType' object has no attribute 'toggle_loop'
(Same error for all the other commands)
Requested, we have the GuildState class:
class GuildState:
"""Helper class managing per-guild state."""
def __init__(self):
self.volume = 1.0
self.playlist = []
self.message_queue = []
self.skip_votes = set()
self.now_playing = None
self.control_message = None
self.loop = False
self.skipped = False
def is_requester(self, user):
return self.now_playing.requested_by == user
How would I overcome this error?
The bot joins on the command play URL and then I built in the following:
if not state.now_playing:
self._play_song(client, state, video)
_play_song is mainly defined the following:
def _play_song(self, client, state, song):
state.now_playing = song
# Other things are not relevant
When you reload the cog, the states dictionary in your cog will be empty. With state = self.get_state(ctx.guild), a new GuildState object is created. From the __init__ function of the GuildState class, self.now_playing is set to None.
Because of this, status = state.now_playing.toggle_loop() will throw an AttributeError as None has no attributes (in this case, no toggle_loop attribute).
If you want to get rid of these errors, you will need to set self.now_playing correctly to something that does have the needed attributes.
If you want to keep the states dictionary as is, you can save it before reloading your cog and restore it. The below example assumes that the cog class is named TestCog.
#client.command()
async def reload(ctx):
temp = client.get_cog('TestCog').states
client.reload_extension('cog')
client.get_cog('TestCog').states = temp
Note that this may break your cog if you change how GuildState is created, as you are restoring the previous version of states.