discord.py Multiple Countdown Timer - python

I was able to make a countdown timer that edits itself. However When I do multiple timers, one of the timer will either lag, or they all break.
How do i make it so that when someone starts a timer, that timer will start working on its own and any other action won't affect that timer. This way multiple timers can be made.
To start a timer I would just type //timer (time)
import discord
import os
import asyncio
client = discord.Client()
#client.event
async def on_ready(): #Print when The Code Has connected to Discord
print("{0.user} Has Connected To Discord".format(client))
#client.event
async def on_message(message):
Days = 0
Hours = 0
Minutes = 0
Seconds = 0
ClockH = "00"
ClockM = "00"
ClockS = "00"
if message.author == client.user:
if ("⌛COUNTDOWN⌛") in message.content:
timer = message.content.split()
timer = list(map(int,timer[1:]))
Days = timer[0]
Hours = timer[1]
Minutes = timer[2]
Seconds = timer[3]
Finish = False
while Finish == False:
if Hours <10: ClockH = "0"+str(Hours)
else: ClockH = str(Hours)
if Minutes <10: ClockM = "0"+str(Minutes)
else: ClockM = str(Minutes)
if Seconds <10: ClockS = "0"+str(Seconds)
else: ClockS = str(Seconds)
if Days == 0:
await message.edit(content="{}:{}:{}".format(ClockH,ClockM,ClockS))
else:
await message.edit(content="{} Days | {}:{}:{}".format(Days,ClockH,ClockM,ClockS))
if Minutes>0:
if Seconds>0: Seconds-=1
else:
Minutes-=1
Seconds = 59
elif Seconds>0:
Seconds-=1
else: Finish = True
await asyncio.sleep(1)
return
if "//timer" in message.content:
timer = message.content.split()
countdown = []
timer = timer[1:]
for i in range(len(timer)):
if ("day" in timer[i].lower()):
Days = int(timer[i-1])
if ("hour" in timer[i].lower()) or ("hr" in timer[i].lower()):
Hours = int(timer[i-1])
if "min" in timer[i].lower():
Minutes = timer[i-1]
if "sec" in timer[i].lower():
Seconds = int(timer[i-1])
if ":" in timer[i]:
timer = timer[i]
index = 0
for i in range(3):
if ":" in timer:
index = timer.find(":")
if len(timer[:index]) <= 2:
countdown.append(int(timer[:index]))
timer = timer[index+1:]
else:
Error = "Please Retype Timer"
else: countdown.append(int(timer))
Hours,Minutes,Seconds = countdown
await message.channel.send("⌛COUNTDOWN⌛ {} {} {} {}".format(Days,Hours,Minutes,Seconds))
print(Days, Hours,Minutes,Seconds)
client.run(os.environ['Token'])

It's no longer necessary to edit the timestamp messages yourself, and it's very likely to be considered API abuse when you do this - bots are not supposed to do x action every y time.
Instead, you can use discord's own timer in your messages. To use a timer, you simply put <t:12345:R> with a number there that represents the timestamp. It also looks nicer and can show detailed information when you hover over it.
#client.command()
async def countdown(ctx, hour: int, minute: int, second: int):
total_time = 3600*hour + 60*minute + second
timestamp = int(time.time()) + total_time
await ctx.send(f'<t:{timestamp}:R>')
(To do this without using commands, change ctx.send to message.channel.send, and use your own definition of the total time in seconds.)
Output:

Related

Python, a timer that able to check time every minute/second without stopping everything else

#my_group.child
#lightbulb.command('five', '5 minute countdown')
#lightbulb.implements(lightbulb.SlashSubCommand)
async def subcommand(ctx):
await ctx.respond("Timer for 5 minute⌛ start!")
await ctx.respond("https://upload.wikimedia.org/wikipedia/commons/7/7a/Alarm_Clock_GIF_Animation_High_Res.gif")
for i in range(5*60):
time.sleep(1) #5 min
if i % 60 == 0 and i != 0:
await ctx.respond(str(int(i/60)) + " minute⌛ has passed!")
await ctx.respond("Timer ended!")
#my_group.child
#lightbulb.command('ten', '10 minute countdown')
#lightbulb.implements(lightbulb.SlashSubCommand)
async def subcommand(ctx):
await ctx.respond("Timer for 10 minute⌛ start!")
await ctx.respond("https://upload.wikimedia.org/wikipedia/commons/7/7a/Alarm_Clock_GIF_Animation_High_Res.gif")
for i in range(10*60):
time.sleep(1) #10 min
if i % 60 == 0 and i != 0:
await ctx.respond(str(int(i/60)) + " minute⌛ has passed!")
await ctx.respond("Timer ended!")
#my_group.child
#lightbulb.command('thirty', '30 minute countdown')
#lightbulb.implements(lightbulb.SlashSubCommand)
async def subcommand(ctx):
await ctx.respond("Timer for 30 minute⌛ start!")
await ctx.respond("https://upload.wikimedia.org/wikipedia/commons/7/7a/Alarm_Clock_GIF_Animation_High_Res.gif")
for i in range(30*60):
time.sleep(1) #30 min
if i % 300 == 0 and i != 0:
await ctx.respond(str(int(i/300)) + " minute⌛ has passed!")
await ctx.respond("Timer ended!")
When run, the timer will start however every other script is unable to run due to it being stuck in the for loop while the timer is still going. Is there an alternate way of making a timer that won't stop other scripts from running?
The issue here is probably that your threads while sleeping, are not releasing the resources for the other threads.
It should work once you change the time.sleep(1) to await asyncio.sleep(1)
You can find more information here and here.

Discord.Py: Can't rename channel and don't have any exceptions

I want to rename the channel every 5 seconds to create animation. And when I am using code below, it works were slowly and renames the channel in one minute or more.
from asyncio import sleep
from discord.ext import commands
TOKEN = '(HIDEN)'
bot = commands.Bot(command_prefix='/')
async def voice_channel_animation():
await bot.wait_until_ready()
counter = 0
channel = bot.get_guild(821422769144594472).get_channel(822470935243128872)
animation = ["🔈 Create channel", "🔉 Create channel", "🔊 Create channel"]
size = len(animation)
while True:
await channel.edit(name=animation[counter])
counter = 0 if counter + 1 == size else counter + 1
await sleep(5)
bot.loop.create_task(voice_channel_animation())
bot.run(TOKEN)
How can I fix this problem?
It's not possible, the ratelimit for editing a channel is 2 requests per 10 minutes per channel, there's no way of "bypassing" this. You're gonna have edit the channel every 5 minutes instead.
while True:
await channel.edit(name=animation[counter])
counter = 0 if counter + 1 == size else counter + 1
await sleep(300)

Running two loops at the same time on my discord bot

So my discord bot in python has the task to send an embed as soon as the user sends the message "$start". After this the code is starting a while loop and checking if a user has reacted. Simultaneously I want to edit the message of the bot every second so I can display some kind of timer to show the users how much time they have left to react but I dont know how to implement a timer running at the same time. Would it be useful to use multiprocessing for this one?
Here is my very specific code if anyone needs it to answer my question :)
#client.command()
async def start(ctx):
timer = 120
remTime = timer
seedCapital = 10000
sessionEmpty = True
players[ctx.author] = seedCapital
em = discord.Embed(title = "New Poker Session", description = "Waiting for players to join ...\nreact with 💸 to join or ▶️ to start (only host)", color = discord.Color.green())
#em.add_field(name = "2 minutes remaining from now on!", value = "")
botMsg = await ctx.send(embed = em)
await botMsg.add_reaction("💸")
await botMsg.add_reaction("▶️")
while True:
mom0 = time.perf_counter()
try:
reaction, user = await client.wait_for('reaction_add', timeout = remTime, check = lambda reaction, user: reaction.emoji in ["💸", "▶️"])
#msg = await client.wait_for('message', timeout = remTime, check = lambda m: m.channel == ctx.channel)
#print(f"receiving message: {msg.content}")
except asyncio.TimeoutError:
break
if reaction.emoji == "💸":
sessionEmpty = False
await ctx.send(f"{user.mention} joined the session!")
players[user] = seedCapital
elif user == ctx.author and reaction.emoji == "▶️":
break
mom1 = time.perf_counter()
neededTime = mom1 - mom0
remTime -= neededTime
print(remTime)
if not sessionEmpty:
await ctx.send("starting session ...")
#start session
else:
await ctx.send("Noone joined your session :(")
We can use asyncio.gather to run coroutines concurrently.
#after command
from datetime import datetime, timedelta
end_time = datetime.now() + timedelta(seconds=30) #30 seconds to react
async def coro():
for i in range(30, 0, 5):
await botMsg.edit(f'time remaining: {i}') #send another message if you don't want old content to be erased
await asyncio.sleep(5)
async def coro2():
while datetime.now() <= endtime:
#do stuff
await asyncio.gather(coro(), coro2())
Note: There might be a small delay between the completion of the two coroutines.
References:
asyncio.gather
Edit: Here is the small delay I mentioned

How do I run multiple instances of a command and be able to cancel a certain instance in discord.py

I have a simple discord.py bot that pretty much just counts to whatever number a user wants. So far, the command looks like this:
#bot.command()
async def count(ctx, startNum, endNum):
startNum = int(startNum)
endNum = int(endNum)
currentNum = startNum
if startNum > endNum:
await ctx.send("nice try, i'm not counting backwards")
while currentNum < (endNum + 1) or startNum < endNum:
await ctx.send(ctx.message.author.name + ": " + str(currentNum))
currentNum += 1
await ctx.send(ctx.message.author.mention + " I've finished counting to " + str(endNum))
Lets say you run count 10, it will display
username: 1
username: 2
username: 3
...
username: 10
I would like to create a command that pretty much allows a user to cancel one specific counter and not any others.
Preferably, each counter would display a seperate counter ID, which you can then cancel using a command like cancel ID. It would sort of look like:
> count 1 50
CounterID: 1
CounterID: 2
CounterID: 3
> cancel CounterID
CounterID has been cancelled
How could this be done?
In the while loop, you could add a wait_for event with a timeout of 1 second:
import asyncio # for the exception
#bot.command()
async def count(...):
# code
# make sure the person cancelling the timer is the original requester
# and they are cancelling it from the same channel
def check(msg):
return msg.author == ctx.author and msg.channel == ctx.channel and \
msg.content.lower().startswith("cancel")
while currentNum < endNum:
try:
msg = await bot.wait_for(message, check=check, timeout=1)
await ctx.send("Your counter has been cancelled!")
break # break the loop if they send a message starting with "cancel"
except asyncio.TimeoutError:
await ctx.send(ctx.author.name + ": " + str(currentNum))
currentNum += 1
Though that example won't allow for specific counters to be cancelled (apart from a user's own counter). If you had some database (such as json, sqlite, mongodb etc.), you could perhaps store current counter IDs for each user.
References:
Client.wait_for()
asyncio.TimeoutError

discord.py asyncio coroutine - function partially running

I am writing a discord bot with discord.py and am trying to implement a system to buy stocks in, on python 3.6 as python3.7+ has asyncio.run(func()).
import discord
import asyncio
import os
import random
import time
import math
client = discord.Client()
# contains coin information including balance, stock price, number of stocks, etc
with open('coins.conf', 'r') as f:
for line in f.readlines():
exec(line)
def save_all():
with open('coins.conf', 'w') as f:
f.write('coins = '+repr(coins))
random.seed(os.urandom(32))
searchusers = []
bank_cooldown = {}
bans['global'] = False
#client.event
async def on_ready():
'''Notification on ready.'''
print('Logged in! Bot running.')
await client.change_presence(activity=discord.Game(name='/help'))
#client.event
async def on_member_join(user):
'''Direct message the rules on member join.'''
await user.create_dm()
await user.dm_channel.send(f'Hi **{user.name}**, welcome to the server! Be sure to read the rules to stay out of trouble. Have a great time!')
def getcoins(uid):
'''Get the amount of coins, if nonexistent set to 0.'''
try:
return coins[uid][0]
except Exception:
coins[uid] = [0, time.time()+1, time.time()+1, 0, 320, time.time()+600, 0]
return 0
def mention_to_uid(mention):
'''Extract the UID from a mention (exclude first two char and last char)'''
uid = mention[2:-1]
if uid[0] == '!':
uid = uid[1:]
return uid
def setcoins(uid, value):
'''Set the amount of coins someone has.'''
try:
coins[uid][0] = value
except Exception:
coins[uid] = [value, time.time()+1, time.time()+1, 0, 320, time.time()+600, 0]
#client.event
async def on_message(message):
'''Main bot code running on message.'''
#############################
# some initialization stuff #
#############################
if message.content.startswith('/'):
user = message.author.id
name = message.author.display_name
text = message.content[1:].strip()
command = text.split(' ')[0]
subcommand = text.split(' ')[1:]
######################
# other bot commands #
######################
if command == 'stocks':
if len(subcommand) < 2:
await message.channel.send('Missing arguments! `/stocks [buy | sell] <amount>` or `/stocks help [info | price]`')
elif subcommand[0] == 'help':
if subcommand[1] == 'info':
msg = 'Invest in bot stocks! Use `/stocks help price` to find the current price per share.'
msg += '\nEvery 7-15 minutes, the stock price will change. It will decrease or increase by a little bit.'
msg += '\nStocks will more than likely output profit, but it is very random.'
msg += '\nEvery share bought increases the price by 1 and every share sold decreases the price by 1.'
await message.channel.send(msg)
elif subcommand[1] in ('price', 'amount', 'cost'):
await message.channel.send(f"The current stock price is **{getcoins('stock')}**. Get them while you can!")
elif subcommand[0] == 'buy':
amount = int(subcommand[1])
if amount < 0:
await message.channel.send('lol dummy. Positive number dude.')
else:
cost = (amount * getcoins('stock')) + (amount**2 - amount)//2 # price including increases
if getcoins(user) < cost:
await message.channel.send(f"You don't have enough coins to buy that many shares! Try `/balance`.\n[Debug] {cost}")
else:
coins[user][6] += amount # coins[user][6] => number of stocks user has
setcoins(user, getcoins(user)-cost)
setcoins('stock', getcoins('stock')+amount)
await message.channel.send(f'You bought {amount} shares for {cost} coins.')
elif subcommand[0] == 'sell':
amount = int(subcommand[1])
if amount < 0:
await message.channel.send('lol dummy. Positive number dude.')
else:
sell = (amount * getcoins('stock')) - (amount**2 - amount)//2 # price including decreases
if coins[user][6] < amount:
await message.channel.send(f"You don't have enough shares!")
else:
coins[user][6] -= amount
setcoins(user, getcoins(user)+sell)
setcoins('stock', getcoins('stock')-amount)
await message.channel.send(f'You sold {amount} shares for {sell} coins.')
else:
await message.channel.send('Invalid arguments! `/stocks [buy | sell] <amount>` or `/stocks help [info | price]`')
######################
# other bot commands #
######################
##################################
# this stuff is the main problem #
##################################
async def main():
'''Bot code.'''
await client.start('Nj*********************************************************')
while True:
await asyncio.sleep(random.randint(420,900))
## I'm certain anything below this doesn't run ##
change = random.choice([ *1*[-50], *4*[-8], *5*[-7], *6*[-6], *7*[-5], *8*[-4], *9*[-3], *10*[-2], *10*[-1],
*10*[0], *10*[1], *10*[2], *10*[3], *9*[4], *8*[5], *7*[6], *6*[7], *5*[8], *4*[9], *3*[10], *2*[12], *1*[15]
])
setcoins('stock', getcoins('stock')+change)
if getcoins('stock') < 15:
setcoins('stock', 15)
setcoins('jackpot', getcoins('jackpot')+random.randint(10, 20))
asyncio.get_event_loop().run_until_complete(main())
Only half of the function is running.
The line await client.start() is not supposed to be a blocking call, and anyway is awaited. Even after waiting 20 minutes (should maximum 15 minutes) the second part of the loop - the change = random.choice line and everything below it does not run.
I think the problem is the client.start call is blocking, but from the documentation (https://discordpy.readthedocs.io/en/latest/api.html#discord.Client.start)
await start(*args, **kwargs)
This function is a coroutine.
A shorthand coroutine for login() + connect().
Raises
TypeError – An unexpected keyword argument was received.
the start command is a coroutine.
I have tried lowering the asyncio.sleep time down to 10 seconds, and even removing it entirely. The stock price and ticket jackpot amount which is what is supposed to be modified does not change.

Categories