godofgrunts
godofgrunts

Reputation: 262

How do I send a message from a thread using discord.py?

I want my discord users to be able to queue up some tasks. In this example it is just pinging websites, but I eventually want it to do other long running tasks. When I run my bot, I get the following error:

ping-bot.py:22: RuntimeWarning: coroutine 'respond_to_channel' was never awaited
  respond_to_channel()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Here's the code:

import os, discord, subprocess, asyncio
from discord import app_commands
from dotenv import load_dotenv
from time import sleep
from threading import Thread

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
CHANNEL=os.getenv('CHANNEL_ID')
GUILD=os.getenv('GUILD_ID')

queue = []


def ping_test():
    global queue
    old_queue = queue.copy() 
    while True:
        if queue != old_queue and len(queue) > 0:
            old_queue = queue.copy()
            retcode = subprocess.call("ping " + "-c 5 " + old_queue[0][0], shell=True)
            queue.pop(0)
            if len(queue) > 0:
                respond_to_channel()
                print(f"Now working on {queue[0][0]}")
        sleep(1)

async def respond_to_channel():
    ping_channel = client.get_channel(CHANNEL)
    await ping_channel.send(f"Testing... Now working on {queue[0][0]}")

class MyClient(discord.Client):
    def __init__(self):
        super().__init__(intents=discord.Intents.default())
        self.synced = False
        daemon = Thread(target=ping_test, daemon=True, name='Monitor')
        daemon.start()

    async def on_ready(self):
        print(f'Logged on as {self.user}!')
        if not self.synced:
            await tree.sync(guild = discord.Object(id = GUILD))
            self.synced = True

client = MyClient()
tree = app_commands.CommandTree(client)

@tree.command(name = "greet", description = "Greetings!", guild = discord.Object(id = GUILD))

async def self(interaction: discord.Interaction, name: str):
    await interaction.response.send_message(f"Hello {name}! I was made with Discord.py!")
    print(f'Sent from {interaction.user}')
    
@tree.command(name = "ping-test", description = "Use my computer to ping websites", guild = discord.Object(id = GUILD))

async def self(interaction: discord.Interaction, website: str):
    queue.append([website, interaction.user.id])
    await interaction.response.send_message(f"Hello <@{interaction.user.id}>! You want me to ping {website}. There are currently {len(queue) - 1} jobs in front of you.", file=discord.File('test.png'))


client.run(TOKEN)

I've found some 'solutions' that do work for the ping test, but I am going to allow my discord users to use my computer for some computationally heavy tasks that can take 5+ minutes. These other solution I found break the bot if the task takes more than a few minutes.

For this reason, I decided to implement a thread that process items in a list that will tag the user in a channel when it is done.

Upvotes: 0

Views: 810

Answers (2)

godofgrunts
godofgrunts

Reputation: 262

I actually wanted to use discord.ext.tasks as it handles everything for you and runs in the async loop so nothing breaks.

I changed the thread call to

@tasks.loop(seconds = 1)
async def ping_test():
    global queue
    global being_worked 
    global client
    ping_channel = client.get_channel(CHANNEL)
    if len(queue) > 0:
        if queue[0] != being_worked[0]:
            being_worked = queue.copy()
            retcode = subprocess.call("ping " + "-c 5 " + being_worked[0][0], shell=True)
            queue.pop(0)
            await ping_channel.send(f"Done working on {being_worked[0][0]}")
            if len(queue) > 0:
                await ping_channel.send(f"Testing... Now working on {queue[0][0]}")
                print(f"Now working on {queue[0][0]}")
    return

So now the full code looks like this:

import os, discord, subprocess
from discord import app_commands
from discord.ext import tasks
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
CHANNEL=int(os.getenv('CHANNEL_ID'))
GUILD=os.getenv('GUILD_ID')

queue = []
being_worked = []

@tasks.loop(seconds = 1)
async def ping_test():
    global queue
    global being_worked 
    global client
    ping_channel = client.get_channel(CHANNEL)
    if len(queue) > 0:
        if queue != being_worked:
            being_worked = queue.copy()
            retcode = subprocess.call("ping " + "-c 15 " + being_worked[0][0], shell=True)
            queue.pop(0)
            await ping_channel.send(f"Done working on {being_worked[0][0]}")
            if len(queue) > 0:
                await ping_channel.send(f"Testing... Now working on {queue[0][0]}")
                print(f"Now working on {queue[0][0]}")
    return

class MyClient(discord.Client):
    def __init__(self):
        super().__init__(intents=discord.Intents.default())
        self.synced = False

    async def on_ready(self):
        print(f'Logged on as {self.user}!')
        if not self.synced:
            await tree.sync(guild = discord.Object(id = GUILD))
            self.synced = True
        await ping_test.start()
        

client = MyClient()
tree = app_commands.CommandTree(client)

@tree.command(name = "greet", description = "Greetings!", guild = discord.Object(id = GUILD))

async def self(interaction: discord.Interaction, name: str):
    await interaction.response.send_message(f"Hello {name}! I was made with Discord.py!")
    print(f'Sent from {interaction.user}')
    
@tree.command(name = "ping-test", description = "Use my computer to ping websites", guild = discord.Object(id = GUILD))

async def self(interaction: discord.Interaction, website: str):
    queue.append([website, interaction.user.id])
    await interaction.response.send_message(f"Hello <@{interaction.user.id}>! You want me to ping {website}. There are currently {len(queue) - 1} jobs in front of you.", file=discord.File('test.png'))


                
client.run(TOKEN)

Upvotes: 1

cheekysim
cheekysim

Reputation: 106

Adding await in front of the function respond_to_channel() should fix the issue.

await respond_to_channel()

Upvotes: 0

Related Questions