Command to check performance of command - discord.py

I have a command to check the performance of another command which returns info like how long the command took to run and if an error occured within it. But this only works with commands, that don't have a permission limit like having administrator perms.
How can I fix this so that I can bypass the permissions limit of the command that will be checked the performance of?
The code that I currently have is:
#commands.command(hidden=True)
#is_owner()
async def perf(self, ctx, *, command):
await asyncio.sleep(0.25)
await ctx.message.delete()
"""Checks the timing of a command, attempting to suppress HTTP and DB calls."""
msg = copy.copy(ctx.message)
msg.content = ctx.prefix + command
new_ctx = await self.bot.get_context(msg, cls=type(ctx))
new_ctx._db = PerformanceMocker()
# Intercepts the Messageable interface a bit
new_ctx._state = PerformanceMocker()
new_ctx.channel = PerformanceMocker()
new_ctx.author = ctx.author
if new_ctx.command is None:
return await ctx.send('No command found')
print(new_ctx.content)
print(new_ctx.author.permissions)
start = time.perf_counter()
try:
await new_ctx.command.invoke(new_ctx)
except commands.CommandError:
end = time.perf_counter()
success = False
try:
await ctx.send(f'```py\n{traceback.format_exc()}\n```')
except discord.HTTPException:
pass
else:
end = time.perf_counter()
success = True
await ctx.send(f'Status: {success} Time: {(end - start) * 1000:.2f}ms')

I would suggest checking out jishaku, which has a builtin debug command that will output any errors and the total time taken.
To answer your question directly, you should take a look at commands.Command.__call__ which will bypass all checks, converters, and cooldowns.

Related

Discord.py create an embed message command

I was trying to make a embed command with time limit, só i started doing it, and at my first try to make it timed it didn't work.
So i searched here, found one sample and tried to do like it (as you can see at my code below), but it didn't work too.
Then i tried to copy that code and pasted at my bot.
Guess...it didn't work -_-, now i'm here, asking gods to help.
client.command(aliases=["createEmbed", "embedCreate"])
async def embed(ctx,):
Questions = ["Titulo?", "Cor? (Hex sem #)", "Descrição?"]
Responses = []
def check(m):
return m.author == ctx.author and m.channel == ctx.channel
#try:
await ctx.send(Questions[0])
title = await client.wait_for('message',timeout=2.0,check=check)
#except asyncio.TimeoutError(*title):
#await ctx.send("Demorou muito")
#return
#else:
Responses.append(title.content)
await ctx.send(Questions[1])
cor = await client.wait_for('message',timeout=25.0,check=check)
Responses.append(cor.content)
await ctx.send(Questions[2])
descrição = await client.wait_for('message',timeout=25.0,check=check)
Responses.append(descrição.content)
cor2 = Responses[1]
corHex = int(cor2, 16)
embedVar = discord.Embed(title=Responses[0], description=Responses[2], color=corHex)
await ctx.send(embed=embedVar)
At my code you can see the piece with #, that is the piece that i tried to test the time command.
If i remove the time command, it works.
It is better to loop through questions and get the date after that make the embed. Below is an example of how to get the Title and Description. You can modify it how you like
#client.command(aliases=["createEmbed", "embedCreate"])
async def embed(ctx):
questions = ["Title?", "Description?"]
responses = []
def check(m):
return m.author == ctx.author and m.channel == ctx.channel
for question in questions:
try:
await ctx.send(question)
message = await client.wait_for('message', timeout=15, check=check)
except asyncio.TimeoutError:
await ctx.send("Timeout")
return
else:
responses.append(message.content)
embedVar = discord.Embed(title=responses[0], description=responses[1])
await ctx.send(embed=embedVar)

How to break out of an (asyncio) websocket fetch loop that doesn't have any incoming messages?

This code prints all messages from a websocket connection:
class OrderStreamer:
def __init__(ᬑ):
ᬑ.terminate_flag = False
# worker thread to receive data stream
ᬑ.worker_thread = threading.Thread(
target=ᬑ.worker_thread_func,
daemon=True
)
def start_streaming(ᬑ, from_scheduler = False):
ᬑ.worker_thread.start()
def terminate(ᬑ):
ᬑ.terminate_flag = True
def worker_thread_func(ᬑ):
asyncio.run(ᬑ.aio_func()) # blocks
async def aio_func(ᬑ):
async with \
aiohttp.ClientSession() as session, \
session.ws_connect(streams_url) as wsock, \
anyio.create_task_group() as tg:
async for msg in wsock:
print(msg.data)
if ᬑ.terminate_flag:
await wsock.close()
The problem is that if no messages arrive, the loop never gets the chance to check terminate_flag and never exits.
I tried creating an external reference to the runloop and websocket:
async with \
aiohttp.ClientSession() as session, \
session.ws_connect(streams_url) as wsock, \
anyio.create_task_group() as tg:
ᬑ.wsock = wsock
ᬑ.loop = asyncio.get_event_loop()
... and modifying my terminate function:
def terminate(ᬑ):
# ᬑ.loop.stop()
asyncio.set_event_loop(ᬑ.loop)
async def kill():
await ᬑ.wsock.close()
asyncio.run(kill())
... but it does not work.
I can't afford to rearchitect my entire application to use asyncio at this point in time.
How to break out of the loop?
You should use asyncio.wait_for or asyncio.wait and call wsock.__anext__() directly instead of using async for loop.
The loop with asyncio.wait should look something like this:
next_message = asyncio.create_task(wsock.__anext__())
while not self.terminate_flag:
await asyncio.wait([next_message], timeout=SOME_TIMEOUT,)
if next_message.done():
try:
msg = next_message.result()
except StopAsyncIteration:
break
else:
print(msg.data)
next_message = asyncio.create_task(wsock.__anext__())
SOME_TIMEOUT should be replaced with the amount of seconds you want to wait continuously for the next incoming message
Here is the documentation for asyncio.wait
P.S. I replaced ᬑ with self, but I hope you get the idea
Note that to read data you should not create a new task as mentioned here:
Reading from the WebSocket (await ws.receive()) must only be done inside the request handler task;
You can simply use timeout.
async def handler(request):
ws = web.WebSocketResponse() # or web.WebSocketResponse(receive_timeout=5)
await ws.prepare(request)
while True:
try:
msg = await ws.receive(timeout=5)
except asyncio.TimeoutError:
print('TimeoutError')
if your_terminate_flag is True:
break
aiohttp/web_protocol.py/_handle_request() will dump errors if you don't write try/except or don't catch the right exception. Try testing except Exception as err: or check its source code.

Discord.py don't require space for tempmute command

I have this code for my discord.py bot, for my tempmute command:
#bot.command()
async def tempmute(ctx, member: discord.Member, time: int, d, *, reason=None):
guild = ctx.guild
mutedRole = discord.utils.get(guild.roles, name="Muted")
if not mutedRole:
mutedRole = await guild.create_role(name="Muted")
for channel in guild.channels:
await channel.set_permissions(mutedRole, speak=False, send_messages=False)
for role in guild.roles:
if role.name == "Muted":
await member.add_roles(role)
embed = discord.Embed(title="TempMuted!", description=f"{member.mention} has been tempmuted.", colour=discord.Colour.red())
embed.add_field(name="Reason:", value=reason, inline=False)
embed.add_field(name="Time for the mute:", value=f"{time}{d}", inline=False)
await ctx.send(embed=embed)
if d == "s":
await asyncio.sleep(time)
if d == "m":
await asyncio.sleep(time*60)
if d == "h":
await asyncio.sleep(time*60*60)
if d == "d":
await asyncio.sleep(time*60*60*24)
await member.remove_roles(role)
embed = discord.Embed(title="Unmute (temp mute expired) ", description=f"Unmuted -{member.mention} ", colour=discord.Colour.light_gray())
await ctx.send(embed=embed)
return
However, when using the command, it has to be typed like this: "!tempmute #user 10 m" if I wanted a 10 minute mute. Notice how it is "10 m". How would I make it so "10m" would work (without a space). So "!tempmute #user 10m"? If a user writes it without a space at the moment, the error "discord.ext.commands.errors.BadArgument: Converting to "int" failed for parameter "time"." occurs, probably as it isn't recognising a number with the letter at the end. Thanks
The best way to solve this would be to create your own Converter and typehint the class in the argument. As documented here
So instead of converting your time in your command function, you would do it in the converter making the syntax more cleaner.
Example of a converter
time_regex = re.compile(r"(\d{1,5}(?:[.,]?\d{1,5})?)([smhd])")
time_dict = {"h":3600, "s":1, "m":60, "d":86400}
class TimeConverter(commands.Converter):
async def convert(self, ctx, argument):
matches = time_regex.findall(argument.lower())
time = 0
for v, k in matches:
try:
time += time_dict[k]*float(v)
except KeyError:
raise commands.BadArgument(f"{k} is an invalid time-key! h/m/s/d are valid!")
except ValueError:
raise commands.BadArgument(f"{v} is not a number!")
return time
This is an example of a Time converter, where it will use a regex and converts it into an int in seconds. Which accepts <number><smhd>, example 2d.
The library calls TimeConverter().convert during the command invocation, hence we create a method called convert, which accepts Context object and arguments as str. All you have to do is return something, or raise an error if there is an error.
In order to use this, you would do it as follows
#bot.command()
async def tempmute(ctx, member: discord.Member, time: TimeConverter, *, reason=None):
...
await member.add_roles(role)
await asyncio.sleep(time)
await member.remove_roles(role)
...
The command invocation would be
!tempmute #user 2d here's the reason
I have been working on a tempmute command myself. I would say the command would work fine with a simple dictionary. If you want to convert time units a simple dictionary would do it and you wouldn't require a hell lot of code.
First I would add the code and then give a brief explanation.
Code:
#MyBot.command()
#commands.has_permissions(manage_roles=True)
async def tempmute(ctx, member: discord.Member, time, *, reason=None):
await ctx.message.delete()
if member.guild_permissions.administrator:
ifadmin_embed = discord.Embed(title='Member is Administrator!', description=f'The user, {member.mention} can\'t be muted as he/she is an administrator.', color=0xff0000)
ifadmin_embed.set_author(name='NucleoBot')
ifadmin_embed.set_footer(text=ctx.author)
await ctx.channel.send(embed=ifadmin_embed, delete_after=10.0)
else:
if discord.utils.get(ctx.guild.roles, name='Muted'):
muted_role = discord.utils.get(ctx.guild.roles, name='Muted')
else:
perms = discord.Permissions(send_messages=False, add_reactions=False, connect=False, speak=False)
await ctx.guild.create_role(name='Muted', permissions=perms)
muted_role = discord.utils.get(ctx.guild.roles, name='Muted')
time_convert = {'s' : 1 , 'm' : 60 , 'h' : 3600 , 'd' : 86400, 'y' : 31536000}
mute_time = int(time[0]) * time_convert[time[-1]]
await ctx.message.delete()
role_if_muted = discord.utils.find(lambda r: r.name == 'Muted', ctx.guild.roles)
if role_if_muted in member.roles:
alreadymuted_embed = discord.Embed(title='Already Muted!', description=f'The user, {member.mention} is already muted for {mute_time} seconds.', color=0xff0000)
alreadymuted_embed.set_footer(text=ctx.author)
alreadymuted_embed.set_author(name='NucleoBot')
await ctx.channel.send(embed=alreadymuted_embed, delete_after=10.0)
else:
if reason == None:
await member.add_roles(muted_role)
tempmuted_embed = discord.Embed(title='Temporary Mute Successfull!', description=f'{member.mention} has been muted for {mute_time} seconds successfully! \n \n Reason: No reason given.', color=0x4fff4d)
tempmuted_embed.set_author(name='NucleoBot')
tempmuted_embed.set_footer(text=ctx.author)
else:
await member.add_roles(muted_role)
tempmuted_embed = discord.Embed(title='Temporary Mute Successfull!', description=f'{member.mention} has been muted for {mute_time} seconds successfully! \n \n Reason: {reason}', color=0x4fff4d)
tempmuted_embed.set_author(name='NucleoBot')
tempmuted_embed.set_footer(text=ctx.author)
await ctx.channel.send(embed=tempmuted_embed, delete_after=10.0)
await asyncio.sleep(mute_time)
await member.remove_roles(muted_role)
Explanation:
My code has almost everything here that you need for a mute command. Only the members with roles that have manage_role permissions to execute this command. Also if someone try to mute a member who is an administrator of the server, an embed would pop up to say, Member is an admin.
The code also has the aspect that if no reason is given it would not show any error but post an embed made for No Reason command, i.e., if no reason is mentioned. It also checks that if the server doesn't have a role named Muted. It the role exist it would simply use it otherwise first it would create a role named Muted with denial of important permissions and use it for the mute.
Now the time conversion. The line which is used for this:
time_convert = {'s' : 1 , 'm' : 60 , 'h' : 3600 , 'd' : 86400, 'y' : 31536000}
This command takes second as basic value for the conversion then converts every other unit in terms of seconds. Here s is equated to 1 and m is equated to 60 as I minute is equal to 60 seconds. Similarly doing that same with other units we derive everything to seconds. This dictionary turns everything to seconds and using asyncio we create a timer for mute.
Hope this helps, if you still get any doubt, please feel free to ask me.
Thank You!

Testing a function wrapper that returns a coroutine

I have a function similar to the following:
def wrapper(fn, client):
async def helper(*args, **kwargs):
id_ = fn(*args, **kwargs)
while True:
await asyncio.sleep(60)
status = client.get_status(id_)
if status == 'OK':
break
return status
return helper
Basically it takes a function, which launches a compute job somewhere. The coroutine then asks the status of this compute job every 60 seconds and if the status is OK then it breaks from the infinite loop.
To test it I am using:
classs Test_wrapper(unittest.TestCase):
def test_wrapper(self):
client = get_client()
aio_fn = wraper(fn, client)
coro = aio_fn(arg1, arg2, arg3=arg3, arg4=arg4)
loop = asyncio.get_event_loop()
loop.run_until_complete(coro)
loop.close()
I have also tested variations of the alternatives shown in this answer by I do not get it to work. I have run the wraper outside pytest and it works.
I have tested this wrapper running the code it is suppose to run, but the tests keep failing. Do you have any clue?
No error message or exeption is displayed when it fails, it only says that it fails.

Why does using asyncio.ensure_future for long jobs instead of await run so much quicker?

I am downloading jsons from an api and am using the asyncio module. The crux of my question is, with the following event loop as implemented as this:
loop = asyncio.get_event_loop()
main_task = asyncio.ensure_future( klass.download_all() )
loop.run_until_complete( main_task )
and download_all() implemented like this instance method of a class, which already has downloader objects created and available to it, and thus calls each respective download method:
async def download_all(self):
""" Builds the coroutines, uses asyncio.wait, then sifts for those still pending, loops """
ret = []
async with aiohttp.ClientSession() as session:
pending = []
for downloader in self._downloaders:
pending.append( asyncio.ensure_future( downloader.download(session) ) )
while pending:
dne, pnding= await asyncio.wait(pending)
ret.extend( [d.result() for d in dne] )
# Get all the tasks, cannot use "pnding"
tasks = asyncio.Task.all_tasks()
pending = [tks for tks in tasks if not tks.done()]
# Exclude the one that we know hasn't ended yet (UGLY)
pending = [t for t in pending if not t._coro.__name__ == self.download_all.__name__]
return ret
Why is it, that in the downloaders' download methods, when instead of the await syntax, I choose to do asyncio.ensure_future instead, it runs way faster, that is more seemingly "asynchronously" as I can see from the logs.
This works because of the way I have set up detecting all the tasks that are still pending, and not letting the download_all method complete, and keep calling asyncio.wait.
I thought that the await keyword allowed the event loop mechanism to do its thing and share resources efficiently? How come doing it this way is faster? Is there something wrong with it? For example:
async def download(self, session):
async with session.request(self.method, self.url, params=self.params) as response:
response_json = await response.json()
# Not using await here, as I am "supposed" to
asyncio.ensure_future( self.write(response_json, self.path) )
return response_json
async def write(self, res_json, path):
# using aiofiles to write, but it doesn't (seem to?) support direct json
# so converting to raw text first
txt_contents = json.dumps(res_json, **self.json_dumps_kwargs);
async with aiofiles.open(path, 'w') as f:
await f.write(txt_contents)
With full code implemented and a real API, I was able to download 44 resources in 34 seconds, but when using await it took more than three minutes (I actually gave up as it was taking so long).
When you do await in each iteration of for loop it will await to download every iteration.
When you do ensure_future on the other hand it doesn't it creates task to download all the files and then awaits all of them in second loop.

Resources