I would like to create a simple smoke test for my application using pytest. It would be really simple for me, that's why I tried. The problem is that bots does not reacts for other bot's message as I noticed. I looked at the code (bot.py) and modified the process_commands just for the smoke test. Unfortunately it's still works only for human messages during testing.
Edited code, a complete test (only HASH and channel id are imported)
from discord.ext import commands
import pytest
import threading
import time
from build_config import HASH, channel_id
bot = commands.Bot(command_prefix = '.', description="This is a test bot for stackoverflow!")
class Test_Message(commands.Cog):
#commands.command(pass_context = True)
async def message(self, ctx):
await ctx.send("text_message")
def run_bot():
bot.add_cog(Test_Message())
bot.run(HASH.DEBUG)
#pytest.fixture
def create_bot():
t1 = threading.Thread(target=run_bot, args=[])
t1.start()
class Test_Events:
successful = False
#staticmethod
#bot.event
async def on_ready():
channel = bot.get_channel(channel_id)
await channel.send('.message')
#staticmethod
#bot.event
async def on_message(message):
await bot.process_commands(message)
if (message.channel.name == "general" and message.clean_content == "text_message"):
Test_Events.successful = True
#staticmethod
#bot.event
async def process_commands(message):
ctx = await bot.get_context(message)
await bot.invoke(ctx)
#pytest.mark.asyncio
async def test_Smoke(create_bot):
must_end = time.time() + 60
while time.time() < must_end:
if Test_Events.successful:
break
time.sleep(1)
assert Test_Events.successful
Basically someone marked this one as a solution in Allow Discord Rewrite bot to respond to other bots, but it doesn’t work for me.
Edit: So I debug the discord.py, unfortunately there is at least another check in get_context self._skip_check(message.author.id, self.user.id).
So it turned out that the creators of discord.py created an overwritable process_commands function. That's why you can remove the
if message.author.bot:
return
part. Unfortunately this is not enough for anything, there is such a part deep in the code which reject the bot commands:
if self._skip_check(message.author.id, self.user.id):
return ctx
The solution is to take this check out:
bot._skip_check = lambda x, y: False
I only use this workaround for testing, it is forbidden to use anything like this one on a normal server.
Related
I'm developing discord bot with discord.py==2.1.0.
I use cog to write the main function that I wanna use, but I found when the whole bot is wrapped in async function and called by asyncio.run(), my terminal won't show any error message when there is any runtime error in my cog script.
Here is the example application. I stored my bot token in environment variable.
bot.py
import os
import discord
from discord.ext import commands
import asyncio
token = os.environ["BOT_TOKEN"]
class Bot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.members = True
intents.message_content = True
description = "bot example."
super().__init__(
command_prefix=commands.when_mentioned_or('!'),
intents=intents,
description=description
)
async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')
bot = Bot()
async def load_extensions():
for f in os.listdir("./cogs"):
if f.endswith(".py"):
await bot.load_extension("cogs." + f[:-3])
async def main():
async with bot:
await load_extensions()
await bot.start(token)
asyncio.run(main())
./cogs/my_cog.py
from discord.ext import commands
class Test(commands.Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_ready(self):
print("Ready")
#commands.command()
async def command(self, ctx):
test_error_message() # An runtime error example
print("Command")
async def setup(client):
await client.add_cog(Test(client))
Command that I run in terminal to start the bot.
python bot.py
When I type !command in the discord channel, there is no error message showing in the terminal, but there is no "Command" printed out so I'm sure the code stopped at the line I called test_error_message()
I expected that it should show error message normally, but I cannot find useful reference to make it work :(
There is one reason I need to use asyncio, I have a task loop to run in the bot, like the code below.
from discord.ext import tasks
#tasks.loop(seconds=10)
async def avatar_update():
# code here
async def main():
async with bot:
avatar_update.start()
await load_extensions()
await bot.start(token)
I would happy to know if there are some great practice to handle error in this situation.
Thanks!
Client.start() doesn't configure logging, so if you want to use that then you have to do it yourself (there's setup_logging to add a basic config). run() configures logging for you.
For more info, read the docs. https://discordpy.readthedocs.io/en/stable/logging.html?highlight=logging
I'm trying to record twitch chat messages while still recognizing commands, however, recording the messages using event_message seems to stop the command from working.
Anybody know how you would recognize/respond to commands and record message at the same time?
from twitchio.ext import commands
import time
from datetime import datetime
class Bot(commands.Bot):
def __init__(self):
super().__init__(
token='oauth:',
prefix='!',
initial_channels=['channelname']
)
# This function isn't running; event_message is stealing the input!
#commands.command(name='ping')
async def my_command(self, ctx):
print('Caught ping')
await ctx.send('pong')
# records the messages (or in this case, the time a message is sent)
async def event_message(self, message):
print(time.mktime(datetime.now().timetuple())) # prints the unix second
if __name__ == '__main__':
bot = Bot()
bot.run()
Reducing this down even more, the same issue occurs here; with the bot responding to the command, but not printing the message author or content:
from twitchio.ext import commands
import os
bot = commands.Bot(
token='', prefix="!", initial_channels=[""], client_id='', nick='', client_secret='')
#This isn't working; Should still grab the message author/name and print.
Ive tried twitchio versions between 1.3.0 and 2.3.0, but the issue persists.
#bot.event
async def event_message(ctx):
print(ctx.author.name)
print(ctx.content)
await bot.handle_commands(ctx)
#This is running
#bot.command(name='test')
async def test_command(ctx):
await ctx.send('this is a test response')
if __name__ == '__main__':
bot.run()
Issue was not including an "await bot.handle_commands(ctx)" command below the event_message function.
Attached below is a working code
from twitchio.ext import commands
class Bot(commands.Bot):
def __init__(self):
super().__init__(token='', prefix="!", initial_channels=[""])
#commands.command()
async def ping(self, ctx):
print('Caught Ping')
await ctx.send(f'Pong!')
async def event_message(self, message):
print(message.author.name, message.content)
await bot.handle_commands(message) # <--This is the new line
bot = Bot()
bot.run()
NOTE: An error is raised by the event_message function when the bot sees one of its' own messages in chat. This doesn't seem to inhibit function, but it can be fixed by changing the code to the following:
async def event_message(self, message):
if hasattr(message.author, 'name'): # <-- New Line
print(message.author.name, message.content)
await bot.handle_commands(message)
I understand that usually the discord bots are in a listening (blocking) loop, but how can I create a function that connects, send a message or perform any action and disconnect in a non blocking flow?
I'm using discord.py and I'm looking for something like:
import discord
TOKEN = "mYtOkEn"
discord.connect(TOKEN)
discord.send("I'm sending this message")
discord.disconnect()
I already tryied playing with the async but have problems with the threading, so was wondering if there is something more simple.
It is for a button that when clicked, perform that action but after that it can continue working on other tasks
Thanks beforehand
One way you could accomplish this is by using a custom event loop.
Example:
import discord
import asyncio
from threading import Thread
TOKEN = "secret"
client = discord.Client()
def init():
loop = asyncio.get_event_loop()
loop.create_task(client.start(TOKEN))
Thread(target=loop.run_forever).start()
#client.event
async def on_message(message):
if message.author == client.user:
return
await message.channel.send('Hello!')
#client.event
async def on_ready():
print("Discord bot logged in as: %s, %s" % (client.user.name, client.user.id))
init()
print("Non-blocking")
Take a look at this for more info: C-Python asyncio: running discord.py in a thread
Thank you for your help and support. With the SleepyStew answer I could find the path to solve it and went this way:
import discord
import asyncio
def discord_single_task():
# Define Coroutine
async def coroutine_to_run():
TOKEN = "Secret"
# Instantiate the Client Class
client = discord.Client()
# # Start (We won't use connect because we don't want to open a websocket, it will start a blocking loop and it is what we are avoiding)
await client.login(TOKEN)
# Do what you have to do
print("We are doing what we want to do")
# Close
await client.close()
# Create Loop to run coroutine
loop = asyncio.new_event_loop()
llll = loop.create_task(coroutine_to_run())
loop.run_until_complete(llll)
return 'Action performed successfully without a blocking loop!'
I started learning python today and made a Discord bot. I have a few problems:
If message.author == was used in the on_message, but the bot continued to reply to itself.
Afterwards, a new bot was created with a new token and the code didn't work.
I searched a lot on this site and Google. I didn't find any solution. It's okay to modify my code. Everything is from the internet, so it can be a mess. Please help me.
import discord
import asyncio
from discord.ext import commands
client = discord.Client()
bot = commands.Bot(command_prefix="!")
#bot.event
async def on_ready():
print('Loggend in Bot: ', bot.user.name)
print('Bot id: ', bot.user.id)
print('connection was succesful!')
print('=' * 30)
#client.event
async def on_message(message) :
if on_message.content.startswith('!의뢰'):
msg = on_message.channel.content[3:]
embed = discord.Embed(title = "브리핑", description = msg, color = 0x62c1cc)
embed.set_thumbnail(url="https://i.imgur.com/UDJYlV3.png")
embed.set_footer(text="C0de")
await on_message.channel.send("새로운 의뢰가 들어왔습니다", embed=embed)
await client.process_commands(message)
client.run("My bot's token.")
Your code was messy, but it should work now. I included comments to let you know how everything works. I think the good starting point to making your own bot is reading documentation. Especially Quickstart that shows you a simple example with explanation.
Write !example or hello to see how it works.
import discord
import asyncio
from discord.ext import commands
# you created 'client' and 'bot' and used them randomly. Create one and use it for everything:
client = commands.Bot(command_prefix="!") # you can change commands prefix here
#client.event
async def on_ready(): # this will run everytime bot is started
print('Logged as:', client.user)
print('ID:', client.user.id)
print('=' * 30)
#client.event
async def on_message(message):
if message.author == client.user:
return
if message.content.startswith('hello'): # you can change what your bot should react to
await message.channel.send("Hello! (This is not a command. It will run everytime a user sends a message and it starts with `hello`).")
await client.process_commands(message)
#client.command()
async def example(ctx): # you can run this command by sending command name and prefix before it, (e.g. !example)
await ctx.send("Hey! This is an example command.")
client.run("YOUR TOKEN HERE")
Good afternoon! I am new to Python , and I am working on a discord bot. I keep suffering from this error: AttributeError: 'Client' object has no attribute 'command'. I tried everything to repair this, but I did not know. Any help would be fine. Please help me!
Here is the code:
import discord
import random
from discord.ext import commands
class MyClient(discord.Client):
client = commands.Bot(command_prefix = '?')
# Start
async def on_ready(self):
print('Logged on as', self.user)
# Latency
client = discord.Client()
#client.command()
async def ping(ctx):
await ctx.send(f'Pong! {round(client.latency * 1000)}ms')
# 8ball
#client.command(aliases=['8ball'])
async def _8ball(ctx, *, question):
responses = ['Biztosan.',
'Nagyon kétséges.']
await ctx.send(f'Kérdés: {question}\nVálasz: {random.choice(responses)}')
# Clear
#client.command()
async def clear(ctx, amount=5):
await ctx.channel.purge(limit=amount)
await ctx.send(f'Kész!')
async def on_message(self, message):
word_list = ['fasz', 'kurva', 'anyad', 'anyád', 'f a s z', 'seggfej', 'buzi', 'f.a.s.z', 'fa sz', 'k U.rv# any#dat']
if message.author == self.user:
return
messageContent = message.content
if len(messageContent) > 0:
for word in word_list:
if word in messageContent:
await message.delete()
await message.channel.send('Ne használd ezt a szót!')
messageattachments = message.attachments
if len(messageattachments) > 0:
for attachment in messageattachments:
if attachment.filename.endswith(".dll"):
await message.delete()
await message.channel.send("Ne küldj DLL fájlokat!")
elif attachment.filename.endswith('.exe'):
await message.delete()
await message.channel.send("Ne csak parancsikont küldj!")
else:
break
client = MyClient()
client.run(token)
There are a multitude of ways to make your bot, and it seems you tried to mash 2 ways of making it together.
Option 1: using the pre-made commands bot class
client = commands.Bot(command_prefix = '?')
client.command()
async def command_name(ctx, argument):
#command function
client.run(token)
Option 2: making you own subclass of client
class MyClient(discord.Client):
async def on_message(self, message):
if message.content.startswith('command_name'):
#command functionality
client = MyClient()
client.run()
You can use either of the two options, but not both at the same time (you could actually do that, but not in the way you did)
Also I would advice staying away from discord.py to learn python as asyncio is pretty complex
Why don't you simply define client like this?
client = commands.Bot(...)
Also you have defined it a couple of times in the code, delete them all and define it only ONCE at the top of the code after the imports. This is a really bad question, you should learn a lot more python before diving deeper into discord.py.