How to Make a Looping Discord Bot Task that is Invoked When a Message is Posted in Python
Question:
I am trying to write a discord bot that posts yeterday’s Wordle solutions but I cannot seem to figure out how to get a task to be invoked by a message and then have that task loop. I tried to use a while loop but then the bot would only work for one server. Trying to use a looping task does not work either. Here is the code
import imp
import json
import requests
import discord
import os
import time
from datetime import date, timedelta
from discord.ext import tasks
client = discord.Client()
#channel_id_exists = False
#channel_id = 0
@client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
@client.event
async def on_message(message):
if message.author == client.user:
return
if message.content.startswith('!wordle_setup'):
await message.delete()
await message.channel.send("Setting up...")
channel_id = message.channel.id
await wordle_guess(channel_id).start()
@tasks.loop(seconds=10)
async def wordle_guess(channel_id):
message_channel = client.get_channel(channel_id)
yesterday = date.today() - timedelta(days=1)
year = yesterday.year
month = yesterday.month
day = yesterday.day
word_guess = json.loads(requests.get(
"https://najemi.cz/wordle_answers/api/?day={0}&month={1}&year={2}".format(day, month, year)).text)["word"]
await message_channel.send("Word guess for " + yesterday.isoformat() + " : " + word_guess + 'nhttps://www.merriam-webster.com/dictionary/' + word_guess)
client.run("TOKEN")
Either the bot gets stuck at setup, or the the task doesn’t loop in 10 seconds, or the bot only works for one server. I use the setup command to prevent needing to hardcode the channel id into the code.
Answers:
I have solved the issue by wrapping the code in a client class and allowing the storage of multiple channel ids. The new code is shown here.
import asyncio
import imp
import json
import requests
import discord
import os
import time
from datetime import date, timedelta
from discord.ext import tasks
import threading
print("test!")
class MyClient(discord.Client):
channel_id = []
channel_id_exists = False
async def on_ready(self):
print('We have logged in as {0.user}'.format(self))
async def on_message(self, message):
if message.author == self.user:
return
if message.content.startswith('!wordle_setup'):
await message.delete()
await message.channel.send("Setting up...")
self.channel_id.append(message.channel.id)
self.channel_id_exists = True
@tasks.loop(seconds=3600*24)
async def wordle_guess(self):
if self.channel_id_exists:
yesterday = date.today() - timedelta(days=1)
year = yesterday.year
month = yesterday.month
day = yesterday.day
for channel_id_iter in self.channel_id:
message_channel = self.get_channel(channel_id_iter)
word_guess = json.loads(requests.get(
"https://najemi.cz/wordle_answers/api/?day={0}&month={1}&year={2}".format(day, month, year)).text)["word"]
await message_channel.send("Wordle guess for " + yesterday.isoformat() + " : " + word_guess + 'nhttps://www.merriam-webster.com/dictionary/' + word_guess)
client = MyClient()
client.wordle_guess.start()
client.run("TOKEN")
You can also launch a task from a command.
In my case, using the module interactions, I have a discord function that has to run every hour or when the command is triggered. There may be better solutions but this one worked for me.
from discord.ext import tasks
import interactions
...
bot = interactions.Client(bot_token,...)
...
# manually triggered command
@bot.command( name='sync_roles', ...)
async def command_sync_roles(ctx: interactions.CommandContext):
global busy
if busy: return
bot._loop.create_task(sync_roles())
# scheduled job
@tasks.loop(hours=1)
async def cron_sync_roles():
global busy
if busy: return
bot._loop.create_task(sync_roles())
# I use busy as a global variable to avoid double calls
busy = False
async def sync_roles():
global busy
busy = True
## Your code here
busy = False
...
cron_sync_roles.start()
bot.start()
I am trying to write a discord bot that posts yeterday’s Wordle solutions but I cannot seem to figure out how to get a task to be invoked by a message and then have that task loop. I tried to use a while loop but then the bot would only work for one server. Trying to use a looping task does not work either. Here is the code
import imp
import json
import requests
import discord
import os
import time
from datetime import date, timedelta
from discord.ext import tasks
client = discord.Client()
#channel_id_exists = False
#channel_id = 0
@client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
@client.event
async def on_message(message):
if message.author == client.user:
return
if message.content.startswith('!wordle_setup'):
await message.delete()
await message.channel.send("Setting up...")
channel_id = message.channel.id
await wordle_guess(channel_id).start()
@tasks.loop(seconds=10)
async def wordle_guess(channel_id):
message_channel = client.get_channel(channel_id)
yesterday = date.today() - timedelta(days=1)
year = yesterday.year
month = yesterday.month
day = yesterday.day
word_guess = json.loads(requests.get(
"https://najemi.cz/wordle_answers/api/?day={0}&month={1}&year={2}".format(day, month, year)).text)["word"]
await message_channel.send("Word guess for " + yesterday.isoformat() + " : " + word_guess + 'nhttps://www.merriam-webster.com/dictionary/' + word_guess)
client.run("TOKEN")
Either the bot gets stuck at setup, or the the task doesn’t loop in 10 seconds, or the bot only works for one server. I use the setup command to prevent needing to hardcode the channel id into the code.
I have solved the issue by wrapping the code in a client class and allowing the storage of multiple channel ids. The new code is shown here.
import asyncio
import imp
import json
import requests
import discord
import os
import time
from datetime import date, timedelta
from discord.ext import tasks
import threading
print("test!")
class MyClient(discord.Client):
channel_id = []
channel_id_exists = False
async def on_ready(self):
print('We have logged in as {0.user}'.format(self))
async def on_message(self, message):
if message.author == self.user:
return
if message.content.startswith('!wordle_setup'):
await message.delete()
await message.channel.send("Setting up...")
self.channel_id.append(message.channel.id)
self.channel_id_exists = True
@tasks.loop(seconds=3600*24)
async def wordle_guess(self):
if self.channel_id_exists:
yesterday = date.today() - timedelta(days=1)
year = yesterday.year
month = yesterday.month
day = yesterday.day
for channel_id_iter in self.channel_id:
message_channel = self.get_channel(channel_id_iter)
word_guess = json.loads(requests.get(
"https://najemi.cz/wordle_answers/api/?day={0}&month={1}&year={2}".format(day, month, year)).text)["word"]
await message_channel.send("Wordle guess for " + yesterday.isoformat() + " : " + word_guess + 'nhttps://www.merriam-webster.com/dictionary/' + word_guess)
client = MyClient()
client.wordle_guess.start()
client.run("TOKEN")
You can also launch a task from a command.
In my case, using the module interactions, I have a discord function that has to run every hour or when the command is triggered. There may be better solutions but this one worked for me.
from discord.ext import tasks
import interactions
...
bot = interactions.Client(bot_token,...)
...
# manually triggered command
@bot.command( name='sync_roles', ...)
async def command_sync_roles(ctx: interactions.CommandContext):
global busy
if busy: return
bot._loop.create_task(sync_roles())
# scheduled job
@tasks.loop(hours=1)
async def cron_sync_roles():
global busy
if busy: return
bot._loop.create_task(sync_roles())
# I use busy as a global variable to avoid double calls
busy = False
async def sync_roles():
global busy
busy = True
## Your code here
busy = False
...
cron_sync_roles.start()
bot.start()