Search code examples
pythondiscorddiscord.pybots

Discord Bot Looping Task from Command


Good afternoon all, I should preface this post with the statement that I am pretty new to Python and know enough to get myself into trouble, but not enough to always get out of trouble...this is one of those instances I can't get out.

I am attempting to create a Discord Bot using discord.py that has the ultimate goal of being started from a command in Discord, starts counting the days since the start command was issued, and sends a message to Discord every morning with the count. I also would have a command to reset the count and another to cancel the counter. For testing purposes, I have created code to count minutes instead of days and a task loop of only 10 seconds rather than a full day.

My problem is that I am attempting to use a discord.ext task for the loop, but I clearly don't know how to use it properly, and my research online has not made it any more clear to me. I'm hoping some folks here can steer me in the right direction. I have supplied my code below.

What I expect to happen when I execute my code:

  • I issue the $start arg command and the bot sends a message of "It has been X minutes"
  • Every 10 seconds the bot sends the same message until 1 minute has passed, then message reads "It has been X+1 minutes"
  • Continue looping until canceled

What actually happens:

  • I issue the $start arg command and the bot sends a message of "It has been X minutes"
  • Nothing else. No other messages or anything happens in Discord, no errors shown in the console.

The code is currently being hosted on replit.com, hence the keep_alive function using UpTimeRobot to keep the bot alive. Something to note, I originally used asyncio.sleep() to just wait to send the new message. This worked fine for shorter periods (like a few minutes to hours) but even with UpTimeRobot doing its thing, I can't get 100% uptime of the Bot and I think whenever the bot went offline for a few minutes, it stopped my counter loop and that was that. This is what lead me to look into Tasks, as I read they can be used to continue loops on reconnection if the bot goes offline for a bit.

import discord
import os
import keep_alive
from datetime import date, datetime, time, timedelta
from discord.ext import commands, tasks

bot = commands.Bot(command_prefix="$")
init_count = 0
count = 0
channel_id = My_Channel_ID_Here

@bot.event
async def on_ready():
    print('We have logged in as {0.user}'.format(client))

@tasks.loop(seconds=10, count=None, reconnect=True)
async def min_counter():
    global count
    global init_count
    count = init_count
    today = datetime.today()
    now = datetime.today()
    channel = bot.get_channel(channel_id)

    if today.minute != now.minute:
            # increment count by 1
      count = count + 1
      today = now
      print('Count: ' + str(count))
      await channel.send('It Has Been ' + str(count) + ' Minutes.') # Send message of minutes count

@bot.command() # Command to start the counter
async def start (ctx, arg: int): # arg is initial number of minutes to start with
    global init_count
    init_count = arg
    await ctx.send('It Has Been ' + str(init_count) + ' Minutes.') # Send message of minutes count
    min_counter.start()

@bot.command() # Command to reset counter
async def reset (ctx):
    global count
    count = 0
    await ctx.send('It Has Been 0 Minutes.') # Send message that count is not zero

@bot.command() # Command to stop the counter
async def stop (ctx):
    min_counter.cancel()
    await ctx.send('Counter Stopped.')

keep_alive.keep_alive() # keep alive function call
bot.run(os.getenv('TOKEN')) # Discord bot private token call

Solution

  • Upon quick inspection you are looping the method min_counter() but every time it is called you are changing the values of now and today to datetime.today().

    So when you go to compare the values here:

    if today.minute != now.minute:
      # increment count by 1
      count = count + 1
      today = now
      print('Count: ' + str(count))
      await channel.send('It Has Been ' + str(count) + ' Minutes.') # Send message of minutes count
    

    This will always evaluate to False.

    Easy Fix

    Although I don't like the use of globals too much, here is how we could fix this keeping the style you have now.

    Start the counter and initialize a variable named start_time in global scope:

    @bot.command() # Command to start the counter
    async def start (ctx, arg: int): # arg is initial number of minutes to start with
        global init_count, start_time
        start_time = datetime.today()
        await ctx.send('It Has Been ' + str(init_count) + ' Minutes.') # Send message of minutes count
        min_counter.start()
    

    Then check if start_time is equal to now in the loop:

    @tasks.loop(seconds=10, count=None, reconnect=True)
    async def min_counter():
        global count, start_time, init_count
        now = datetime.today()
        channel = bot.get_channel(channel_id)
    
        if start_time != now:
          # increment count by 1
          count = count + 1
          print('Count: ' + str(count))
          await channel.send('It Has Been ' + str(count) + ' Minutes.') # Send message of minutes count
    

    Let me know if everything works for you, I'd be happy to help further if needed.