How can I prevent getting an AttributeError after reloading a cog? - python

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.

Related

How can I get a self variable from another class?

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

Use variables of imported files

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

AttributeError: "Class" object has no attribute 'user' discord.py

When I do my discord.py project, I found out that the following codes work well in the first time but return an attribute error for the second time (in a single run).
class Test2(commands.Cog):
def __init__(self, dc_bot):
self.bot = dc_bot
self.ui = ""
#commands.command(description="get avatar")
async def getavatar(self, ctx, *, content: str):
avatar = self.bot.user.avatar_url
self.ui += content
await ctx.send(content)
await ctx.send(avatar)
self.__init__(self) # reset the values
First time it works well.
Second time it will say: AttributeError: 'Test2' has no attribute 'user'
I guess because I want to reset self.ui for the next run. And if there are a lot of "self" in my init function that I need to use, I thought (before) that it is a good idea to just called the init function. But running again self.ui = dc_bot will cause this problem I think. Could you explain why this would happen please?
There's a mistake when you re-calls the init method. self.__init__(self) is actually calling Test2. __init__(self, self) . Which overrides self.bot = self thus the attribute error when you run the command a second time. Instead you want:
self.__init__(self.bot)
But this isn't a good solution, instead you should have a "reset" helper method that does the resetting,and you call the helper method instead of init. Since normally, once the class is initiated, you don't want to call the init method again.
You are making the code way too complex try this out!
#commands.command(description = "get avatar")
async def getavatar(self, ctx, *, avamember: discord.Member = None):
userAvatarUrl = avamember.avatar_url
await ctx.send(userAvatarUrl)

Python Discord Bot #commands.has_any_role issue

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

AttributeError: module 'DiscordoragiSearch' has no attribute 'isValidMessage'

I have a module which has the following code
import DiscordoragiSearch
...
#Discord.client.event
async def on_message(message):
print('Message recieved')
#Is the message valid (i.e. it's not made by Discordoragi and I haven't seen it already). If no, try to add it to the "already seen pile" and skip to the next message. If yes, keep going.
if not (DiscordoragiSearch.isValidMessage(message)):
try:
if not (DatabaseHandler.messageExists(message.id)):
DatabaseHandler.addMessage(message.id, message.author.id, message.server.id, False)
except Exception:
traceback.print_exc()
pass
else:
await process_message(message)
Here is the relevant code from DiscordoragiSearch.py which is in the same directory as the other file.
#Checks if the message is valid (i.e. not already seen, not a post by Roboragi and the parent commenter isn't Roboragi)
def isValidMessage(message):
try:
if (DatabaseHandler.messageExists(message.id)):
return False
try:
if (message.author.name == USERNAME):
DatabaseHandler.addMessage(message.id, message.author.id, message.server.id, False)
return False
except:
pass
return True
except:
traceback.print_exc()
return False
This method is defined in the base of the module yet when I run the code I get the following error
File "/home/james/discordoragi/roboragi/AnimeBot.py", line 225, in on_message
if not (DiscordoragiSearch.isValidMessage(message)):
AttributeError: module 'DiscordoragiSearch' has no attribute 'isValidMessage'
Full code for both modules can be found here if you would like any more information I will try my best to provide it, this has been stumping me for over a month now so any help would be awesome, thanks!

Categories