Writing Unittests/Pytests
I have a discord bot and I'm trying to write tests for it. I tried a library called distest and it worked well for certain things but not all..
I have a second bot that sends a message and checks the response, but it's just not working with Unittests or Pytest.
Here's an example of one of my tests,
it's checking for the reply "Pong!" if "ping" was sent.
I run my bot that needs to be tested before calling the script with pytest
from discord.ext import commands
from dotenv import load_dotenv
import os
import pytest
TOKEN = "bot token of tester bot"
bot = commands.Bot(command_prefix='?/')
bot.run(TOKEN)
target_id = "ID of bot to be tested"
channel_id = "ID of channel of where it will be tested"
async def test_ping():
correct_response = 'Pong!'
channel = await bot.fetch_channel(channel_id)
await channel.send("ping")
def check(m):
return m.content == correct_response and m.author.id == target_id
response = await bot.wait_for('message', check=check)
assert (response.content == correct_response)
Pytests is stuck on collecting and when I tried Unittests it just hung and did nothing
You did try to register the ping command after running the bot. bot.run(TOKEN) must be end of the code. Also you must put a decorator to resgistering command. So the code will be this:
from discord.ext import commands
from dotenv import load_dotenv
import os
import pytest
TOKEN = "bot token of tester bot"
bot = commands.Bot(command_prefix='?/')
target_id = "ID of bot to be tested"
channel_id = "ID of channel of where it will be tested"
#bot.command(name="ping") #if name did not entered, function name will be the command name
async def test_ping(ctx): #Every command takes context object as first parameter
correct_response = 'Pong!'
channel = await bot.fetch_channel(channel_id)
await channel.send("ping")
def check(m):
return m.content == correct_response and m.author.id == target_id
response = await bot.wait_for('message', check=check)
assert (response.content == correct_response)
bot.run(TOKEN)
Basicly context object:
await ctx.send("message") #sends message to command's invoked channel.
ctx.guild #returns server if not channel is a DM channel
ctx.channel #returns channel
ctx.author #returns message's author
ctx.message #returns message
ctx.message.content #returns the messsge's content
Related
I have a problem. How can I check which emoji the user reacted with? That did not work for me How do you check if a specific user reacts to a specific message [discord.py]
I want to check if the reaction is ✅ or ❌
folder structure
├── main.py
├── cogs
│ ├── member.py
The problem is that I don't get an error message. Nothing happens. As soon as I react with an emoji, nothing happens.
member.py
import discord
from discord.ext import commands
from datetime import datetime
class command(commands.Cog):
def __init__(self, bot):
self.bot = bot
#commands.Cog.listener()
async def on_message(self, message):
...
...
for emoji in emojis:
await msg.add_reaction(emoji)
await message.author.send('Send me that ✅ reaction, mate')
def check(reaction, user):
return user == message.author and str(reaction.emoji) == '✅'
res = await self.bot.wait_for(
"reaction_add",
check=check, timeout=None
)
print(res.emoji)
if res.content.lower() == "✅":
await message.author.send("Got it")
else:
await message.author.send("Thanks")
confirmation = await self.bot.wait_for("reaction_add", check=check)
await message.author.send("You responded with {}".format(reaction.emoji))
async def setup(bot):
await bot.add_cog(command(bot))
import asyncio
import os
from dotenv import load_dotenv
import discord
from discord.ext import commands
from discord.ext.commands import has_permissions
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
import discord
from discord.utils import get
class MyBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix=commands.when_mentioned_or('-'), intents=intents, max_messages=1000)
async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
async def setup_hook(self):
for file in os.listdir("./cogs"):
if file.endswith(".py"):
extension = file[:-3]
try:
await self.load_extension(f"cogs.{extension}")
print(f"Loaded extension '{extension}'")
except Exception as e:
exception = f"{type(e).__name__}: {e}"
print(f"Failed to load extension {extension}\n{exception}")
bot = MyBot()
bot.run(TOKEN)
You're using a function called check, and it doesn't exist - like your error is telling you. There's one in your class, but that isn't in the same scope so you can't just call it using its name.
To access a method in a class, use self.<name>.
Also, you should only pass the check function, not call it.
(..., check=self.check
EDIT the code in the original question was edited. You're not loading your cogs asynchronously. Extensions & cogs were made async in 2.0. Docs: https://discordpy.readthedocs.io/en/stable/migrating.html#extension-and-cog-loading-unloading-is-now-asynchronous
Put this code in your #commands.Cog.listener() decorator, and the code will work if your cogs loader is working. If you would like me to show you my cogs loader, I can.
accept = '✅'
decline = '🚫'
def check(reaction, user):
return user == author
messsage = await ctx.send("test")
await message.add_reaction(accept)
await message.add_reaction(decline)
reaction = await self.bot.wait_for("reaction_add", check=check)
if str(reaction[0]) == accept:
await ctx.send("Accepted")
elif str(reaction[0]) == decline:
await ctx.send("Denied")
Not sure that I need to use the both runs at the same time, but:
from multiprocessing.dummy.connection import Client
from telnetlib import DM
from typing_extensions import Required
import discord
from discord.utils import get
from discord.ext import commands
from dislash import InteractionClient, Option, OptionType
from dislash.interactions import *
client = discord.Client()
from message import *
#client.event
async def on_message(message):
if message.author == client.user:
return
User_id = message.author.id
if message.channel.id == 1009530463108476968:
NewMessage = message.content.split(' ', 1)[0]
LimitLenght = len(NewMessage) + 11
if len(message.clean_content) >= LimitLenght:
await message.delete()
await message.author.send("Hello, " + f"<#{User_id}>" + "\nPlease, don't send any messages that break the **Counting-game** rules!\nIt's forbidden to post a comment that is longer than 10 characters.")
# More code
#=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
bot = commands.Bot(command_prefix="!")
inter_client = InteractionClient(bot)
#inter_client.slash_command(name="help", description="Shows the help-menu")
async def help(ctx):
embedVar = discord.Embed(title="Test project", description="*The blue text is clickable.*\n⠀", color=0x0000ff)
embedVar.add_field(name="Rules", value='To see rules write **/Rules**\n⠀', inline=False)
await ctx.reply(embed=embedVar, delete_after=180)
bot.run('ToKeN')
client.run('ToKeN')
If you run this code and comment the "bot.run('ToKeN')", the first part of the code will work (def on_message), however the command '/help' will not work. If you change it (comment 'client.run('ToKeN')'), the command '/help' will work, but def on_message not.
What are the possible solutions? Thanks.
The .runs block each other and prevent from running. You shouldn't be using a client and a bot anyway. Use one commands.Bot instead. It subclasses a Client and it should be able to do everything you can do with the client.
bot = commands.Bot(command_prefix="!")
inter_client = InteractionClient(bot)
#bot.event
async def on_message(message):
...
#inter_client.slash_command(name="help", description="Shows the help-menu")
async def help(ctx):
...
bot.run(token)
from discord.ext import commands
import discord
bot = commands.Bot(command_prefix="!")
#bot.event
async def on_guild_channel_create(channel):
message = "This Message is sent via DM"
user = bot.get_user("My_Discord_ID")
await user.send(message)
bot.run("TOKEN")
I ran the code but nothing happened. I am not sure whats going on.
To create a private message with a user, you must use the User.create_dm() method to obtain a channel. Once you have fetched that channel, you can send content to the user.
import discord
#bot.event
async def on_guild_channel_create(channel):
user = bot.get_user("My_Discord_ID")
channel = await user.create_dm()
try: await channel.send("This Message is sent via DM")
except discord.HTTPException: print("User has DMs disabled")
I'm trying to make my Discord bot have a short interaction with users when greeting, but it seems to skip some code. I haven't been able to a solution so far.
Here is the part of the code I am having trouble with:
import discord
from discord.ext import commands
import os
import asyncio
import requests
import json
import random
from aiohttp import request
#client.event
async def on_message(message):
await client.process_commands(message)
if message.author == client.user:
return
username = str(message.author).split('#')[0]
msg = message.content.lower()
if message.content.startswith('greet'): #this is just to activate this part of code
channel = message.channel
await channel.send('Say my name!')
def checkmessage(m):
return m.content == '#Botty' and m.channel == channel #name of bot is Botty, but it can't detect this part, no error whatsoever.
msg = await client.wait_for('message', check=checkmessage)
await channel.send('Hi, {.author}!'.format(msg))
Any help is kindly appreciated, thank you!
Instead of checking the content of the new message, just check if the bot was mentioned in the message. This can be done by checking if Client.user is in the list Message.mentions:
#client.event
async def on_message(message):
...
if message.content.startswith('greet'): #this is just to activate this part of code
channel = message.channel
await channel.send('Say my name!')
def checkmessage(m):
return client.user in m.mentions and m.channel == channel
msg = await client.wait_for('message', check=checkmessage)
await channel.send('Hi, {.author}!'.format(msg))
On a side note, I would recommend checking out the discord.ext.commands bot framework, rather than checking the contents of every message sent.
When you do a #mention to ping others on discord. The input will become formatted as <#!{user-id-here}>.
So, the discord python will see the message string as <#!0123456789> not as #Botty. You can verify it by using print(message.content).
Alright, with your code:
def checkmessage(m):
return m.content == '#Botty' and m.channel == channel
It certainly will not be able to recognize the <#!0123456789> string.
Hence, first, you have to use client.user.id to get the user id of your bot, and then match it instead of matching #Botty.
Ultimately, your code should looks like this:
def checkmessage(m):
return m.content == f"<#!{client.user.id}>" and m.channel == channel
This will allow the function to recognize #mention, hope this helps.
Hey
How I am supposed to input a string from a user in Discord.py. I am getting errors while trying to get input from user. It would be great if someone can help me :)
import discord
from discord import embeds
from discord.ext.commands import Bot
from discord.ext import commands
import time
client = discord.Client()
embed = discord.Embed()
bot = commands.Bot(command_prefix='.')
#client.event
async def on_message(message):
if message.author == client.user:
return
if message.content.startswith('.gdrive'):
first_embed = discord.Embed(title="Unlimited Google Drive Storage", color=0x2bff00)
new_embed = discord.Embed(title='Made by zSupremeSniper0 & toxicXvoid', color=0x2bff00)
new_embed2 = discord.Embed(title='Please Enter your gmail', color=0x2bff00)
# send a first message with an embed
msg = await message.channel.send(embed=first_embed)
# edit the embed of the message
time.sleep(5)
await msg.edit(embed=new_embed)
time.sleep(5)
await msg.edit(embed=new_embed2)
You can use Client.wait_for to wait for a user input:
# edit the embed of the message
time.sleep(5)
await msg.edit(embed=new_embed)
time.sleep(5)
await msg.edit(embed=new_embed2)
def check(m):
return m.channel == message.channel
msg = await client.wait_for('message', check=check)
await message.channel.send("OK")