Search code examples
pythondiscorddiscord.py

How do I auto send a message using discord.py?


I made a sample code snippet to try and see if I can automate my discord bot so that it automatically sends a message at a set interval (seconds):

import os
import discord
import discord.ext
from discord import app_commands
import asyncio

TOKEN = 'BOT TOKEN HERE'
GUILD = 'GUILD NAME HERE'

intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)

@client.event
async def on_ready():

    for guild in client.guilds:
        if guild.name == GUILD:
            break

    print(
        f'{client.user} has successfully connected to the following guild(s):\n'
        f'{guild.name}(id: {guild.id})'
    )
    await client.change_presence(activity=discord.Activity(name='anything', type=discord.ActivityType.playing))

@client.event
async def auto_send():
    await send_message('MESSAGE HERE')
    await asyncio.sleep(i)    #replace 'i' with any number(seconds)

async def send_message(message):
    channel = await client.fetch_channel('CHANNEL ID HERE') #must be of type 'int'
    await channel.send(message)


client.loop.create_task(auto_send())
client.run(TOKEN)

After running the code I'm getting this error:

Traceback (most recent call last):
  File "", line 92, in <module>
    client.loop.create_task(auto_send())
    ^^^^^^^^^^^^^^^^^^^^^^^
  File "", line 140, in __getattr__
    raise AttributeError(msg)
AttributeError: loop attribute cannot be accessed in non-async contexts. Consider using either an asynchronous main function and passing it to asyncio.run or using asynchronous initialisation hooks such as Client.setup_hook

I tried moving the client.loop.create_task() command into the async function definition, and I got no output at all, without any error messages.

My goal was to try and see if the bot automatically sends a message to a specific channel after a set interval (say 10 seconds).

I don't understand this error, so please let me know if there is a fix. Thank you in advance.

Extra info:

  1. I'm using Python 3.11 IDLE and running the program in Pycharm.
  2. I use discord.Client instead of commands.Bot()
  3. Discord.py version: 2.2.3

Solution

  • You can make your bot sends a message each X seconds by creating an asyncio task.

    discord.py provides the discord.ext.tasks module to easily do that. Here is how:


    Step 1: Create a Loop using the tasks.loop decorator:

    @tasks.loop(seconds=10)
    async def auto_send(channel : discord.TextChannel):
        await channel.send("Test message")
    

    I'm creating a Loop called auto_send that recieves a TextChannel as parameter telling the bot where to send the message. The bot will send the "Test message" in the channel provided each 10 seconds as configured in the decorator.


    Step 2: Launch the Loop using tasks.Loop.start, you can do it on the global scope. However, I'm going to do it in the on_ready event to be able to fetch the channel before launching the task:

    @client.event
    async def on_ready():
    
        if not auto_send.is_running():
            channel = await client.fetch_channel('channel id (as int)')
            auto_send.start(channel)
    
        print('Ready')
    

    The on_ready event can be called more than once:

    This function is not guaranteed to be the first event called. Likewise, this function is not guaranteed to only be called once. This library implements reconnection logic and thus will end up calling this event whenever a RESUME request fails.

    We can check if the task is already running using tasks.Loop.is_running to avoid launching it more than once.


    Your Full code:

    import discord
    from discord.ext import tasks
    
    TOKEN = 'BOT TOKEN HERE'
    GUILD = 'GUILD NAME HERE'
    
    intents = discord.Intents.default()
    intents.message_content = True
    client = discord.Client(intents=intents)
    
    @tasks.loop(seconds=10)
    async def auto_send(channel : discord.TextChannel):
        await channel.send('Test message')
    
    @client.event
    async def on_ready():
    
        if not auto_send.is_running():
            channel = await client.fetch_channel('channel id (as int)')
            auto_send.start(channel)
    
        for guild in client.guilds:
            if guild.name == GUILD:
                break
    
        print(
            f'{client.user} has successfully connected to the following guild(s):\n'
            f'{guild.name}(id: {guild.id})'
        )
    
        await client.change_presence(
            activity=discord.Activity(name='anything', type=discord.ActivityType.playing)
        )
    
    client.run(TOKEN)
    

    References: