I made a discord economy bot. I want to include a loan function, where the user can ask for a loan. There would be a command cooldown. However, if they don't pay after the command cooldown has ended, the bot should automatically take the money.
#bot.command()
#commands.cooldown(1, 60*60*24*7, commands.BucketType.user)
async def loan(ctx, amount : int):
loan_available = int(client.get_user_bal(Devada, testbot)['cash'])
if int(amount) <= loan_available:
time.sleep(1)
await ctx.channel.send('You have been given ' + ''.join(str(amount) + ". You will have to pay " + str((int(amount)+int(amount)*0.1)) +" baguttes within 2 weeks."))
client.change_user_bal(str(ctx.guild.id), str(ctx.author.id), cash=0, bank=amount, reason='loan')
client.change_user_bal(str(ctx.guild.id), testbot, cash=-amount, bank=0, reason='loan')
must_pay.update({ctx.author.name:str(amount)})
else:
time.sleep(2)
await ctx.channel.send("You Can only request a loan within "+str(loan_available))
Is there any way to detect when the cooldown is over?
The #commands.cooldown attribute is used to add a cooldown to a command, so users can't spam that same command. Instead, they need to wait for a certain amount of time ( 60*60*24*7 seconds, in this case ) before being able to re-use the command.
However, if you want the bot to wait for 604800 seconds and then take the money back, you should use the asyncio module to wait for that amount of time without disturbing or stopping the other commands of the program. Here is how you can re-adjust your code:
import asyncio
#bot.command()
#commands.cooldown(1, 60*60*24*7, commands.BucketType.user)
async def loan(ctx, amount : int):
loan_available = int(client.get_user_bal(Devada, testbot)['cash'])
if int(amount) <= loan_available:
time.sleep(1)
await ctx.channel.send('You have been given ' + ''.join(str(amount) + ". You will have to pay " + str((int(amount)+int(amount)*0.1)) +" baguttes within 2 weeks."))
client.change_user_bal(str(ctx.guild.id), str(ctx.author.id), cash=0, bank=amount, reason='loan')
client.change_user_bal(str(ctx.guild.id), testbot, cash=-amount, bank=0, reason='loan')
must_pay.update({ctx.author.name:str(amount)})
else:
time.sleep(2)
await ctx.channel.send("You Can only request a loan within "+str(loan_available))
# New asyncio code
await asyncio.sleep(60*60*24*7) # Wait for 60*60*24*7 seconds
# Here, just add the code to take the money away after 60*60*24*7 seconds
Note that if you restart your bot between the time, the bot won't execute the code after
60*60*24*7 seconds. So you must keep the bot online without restarting it.
The correct way of checking if a command's cooldown has finished is either checking if Command.is_on_cooldown is false or even checking the time left on the command by using Command.get_cooldown_retry_after. You would need to check this every certain amount of time, so you could create a Task that would call any of those functions and then evaluate the result to do whatever you want.
Related
I have a program that changes what channel members can see at certain times of the day. To do this, I can either change the roles that every member has, or change the permissions of each channel. However, I have looked all over the web and all of the ways for either method require a message to be sent so that the data from that message can be read and put into the function, such as:
#client.command()
async def perm(ctx):
await ctx.channel.set_permissions(ctx.guild.default_role, send_messages=False
Change the permissions of a discord text channel with discord.py
or
async def addrole(ctx):
member = ctx.message.author
role = get(member.server.roles, name="Test")
await bot.add_roles(member, role)
Discord.py | add role to someone
My current program looks like this:
import discord
client = discord.Client()
import datetime
async def live_day(schedule):
current_place = "the void"
while True:
current_time = str((datetime.datetime.now() -datetime.timedelta(hours=7)).time())
int_time = int(current_time[:2] + current_time[3:5])
for x in range(len(schedule)):
try:
start_time = schedule[x][1]
end_time = schedule[x + 1][1]
except IndexError:
end_time = 2400
if current_place != schedule[x][0] and int_time >= start_time and int_time < end_time:
current_place = schedule[x][0]
#Change the channel permissions of the current place to allow viewing
#Change the channel permissions of the last channel to disallow viewing
#client.event
async def on_ready():
print("{0.user} has arrived for duty".format(client))
client.loop.create_task(live_day([
("Home", 0),
("Work", 900),
("Home", 1700),
("Nightclub", 2000),
("Home", 2200),
]))
client.run(my_secret)
Never mind the badly written code, how would I do this or where should I go to figure this out? Any help is appreciated. Thanks!
Edit: I could get the channels individually by using this,
channel1=client.get_channel(channelid)
discord.py, send message by channel id without on_message() event?
but then I can't use this for more than one server. How can I get channels by name?
Note on on_ready():
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.
Using this function may crash the bot.
Instead create background tasks and use wait_until_ready().
You can find examples in https://github.com/Rapptz/discord.py/blob/v1.7.3/examples/background_task.py
If you want to change only one channel's permissions (per guild) this might fit your needs:
#changing "Home" channel visibility, every day at 5 pm.
#client.event
async def on_ready():
while True:
await asyncio.sleep(1)
current_time = datetime.datetime.now()
five_pm = current_time.replace(hour=17, minute=0, second=0)
if current_time == five_pm:
for guild in client.guilds: #looping through all the guilds your bot is in
channel = discord.utils.get(client.get_all_channels(), name="Home") #getting channel by name by using "discord.utils"
await channel.set_permissions(guild.default_role, view_channel=True) #changing permissions
By using the on_ready() event you can loop through the time check without sending any message.
Remember to import discord.utils.
so basically I am trying to make a spam command for my discord bot, which takes in a custom message to spam. Here's the code:
#client.command(name='spam')
async def spam(ctx):
global stop
stop = 0
content = ctx.message.content[11:]
if ctx.author.guild_permissions.administrator or ctx.author.id in admins:
if lock == 1:
await ctx.send('Jesus bot is currently locked.')
elif lock == 0:
await ctx.send('Beginning spam..')
while not stop:
await ctx.send(content)
else:
await ctx.send('Sorry, but you do not have admin permissions in this server, or you are not a verified admin.')
For some reason, whenever I try to use this command, the bot doesn't respond. I'm not sure why this happens, and could use some help please.
Picture of bot not responding:
I have a spam command, but I only use it to mess around with my friends. I would not recommend using this as a public command, as you may get rate limited or banned for abuse or something like that. Anyway here is the code I have used for it.
#commands.command()
#commands.is_owner()
# If you want to use admin only, use this below
# #commands.has_permissions(administrator=True)
async def spam(self, ctx, amount, *, word):
int(amount)
await asyncio.sleep(2)
print(f"Starting to spam {word} in {ctx.guild.name}")
await ctx.message.delete()
await ctx.send(f"{ctx.author.mention}\nPlease note that this will clog up the bot's reaction time")
await asyncio.sleep(3)
count = 0
counting=True
while counting:
await ctx.send(word)
count = count + 1
if count == amount:
await asyncio.sleep(2)
await ctx.send("Spam complete")
print(Fore.GREEN + "Spam complete")
counting = False
At the top of your code, make sure you import asyncio as time.sleep will cause the whole bot to pause. Also the Fore.GREEN stuff is just colorama (import colorama).
Try using tasks instead of asyncio. It is made for such repetetive operations and it is easier and nicer because it is made by discord and is included in discord.ext. Something like this:
from discord.ext import tasks
#client.command(name='spam')
async def spam(ctx):
#get message and do all the ifs that you have there
spamLoop.start()
#client.command(name='stopSpam')
async def spamStop(ctx):
# stop the loop
spamLoop.cancel()
#tasks.loop(seconds=1)
async def spamLoop():
print("The message")
Actually quite a simple way of adding spam
import asyncio
#bot.command(name='spam', help= "Spam to your heart's delight")
async def spam(ctx, thing, amount):
count = 0
while count < int(amount):
await ctx.send(thing)
count += 1
if count < amount:
await asyncio.sleep(1)
I've put together a command that it takes a decent length of time for discord to execute:
#bot.command()
async def repeat_timer(ctx, *lines):
for line in lines:
await ctx.send(line)
time = "5s"
await ctx.send(time)
you could send $repeat_timer 1 2 3 4 5 6 7 8 9 10 then it will send each numeral back as a seperate message.
I would like to know the time between the loops begining and the final iteration completing executing. i.e. in the above example the message being 10 being posted to the channel.
The above shows the code i have working so far- but i can't see how you could set the timer to know when the task was complete
There's a ton of ways of doing this, probably the easiest and fastest is using the time.perf_counter function:
import time
#bot.command()
async def repeat_timer(ctx, *lines):
start = time.perf_counter()
for line in lines:
await ctx.send(line)
end = time.perf_counter()
total_time = end - start # In seconds
await ctx.send(total_time)
so far what I've got is:
def restart_program():
python = sys.executable
os.execl(python, python, * sys.argv)
#bot.command()
async def restart(ctx):
message = await ctx.send("Restarting... Allow up to 5 seconds")
restart_program()
This works, however I'm wondering how I can make it edit the "Restarting... Allow up to 5 seconds" message to say something like "Bot is back up" after it's restarted. Is this possible, and if so, how could I do this?
I would use Client.logout() and Client.login() to restart a bot instead of doing it with os.execl, but if you really need to do it this way, here is a how you can send a message every time it is started:
#client.event
async def on_ready():
# First get the channel where the message should be sent
channel = discord.utils.get(client.get_all_channels(), name='general')
await channel.send("Bot is back up!")
Using Discord.py-rewrite, How can we diagnose my_background_task to find the reason why its print statement is not printing every 3 seconds?
Details:
The problem that I am observing is that "print('inside loop')" is printed once in my logs, but not the expected 'every three seconds'. Could there be an exception somewhere that I am not catching?
Note: I do see print(f'Logged in as {bot.user.name} - {bot.user.id}') in the logs so on_ready seems to work, so that method cannot be to blame.
I tried following this example: https://github.com/Rapptz/discord.py/blob/async/examples/background_task.py
however I did not use its client = discord.Client() statement because I think I can achieve the same using "bot" similar to as explained here https://stackoverflow.com/a/53136140/6200445
import asyncio
import discord
from discord.ext import commands
token = open("token.txt", "r").read()
def get_prefix(client, message):
prefixes = ['=', '==']
if not message.guild:
prefixes = ['=='] # Only allow '==' as a prefix when in DMs, this is optional
# Allow users to #mention the bot instead of using a prefix when using a command. Also optional
# Do `return prefixes` if u don't want to allow mentions instead of prefix.
return commands.when_mentioned_or(*prefixes)(client, message)
bot = commands.Bot( # Create a new bot
command_prefix=get_prefix, # Set the prefix
description='A bot for doing cool things. Commands list:', # description for the bot
case_insensitive=True # Make the commands case insensitive
)
# case_insensitive=True is used as the commands are case sensitive by default
cogs = ['cogs.basic','cogs.embed']
#bot.event
async def on_ready(): # Do this when the bot is logged in
print(f'Logged in as {bot.user.name} - {bot.user.id}') # Print the name and ID of the bot logged in.
for cog in cogs:
bot.load_extension(cog)
return
async def my_background_task():
await bot.wait_until_ready()
print('inside loop') # This prints one time. How to make it print every 3 seconds?
counter = 0
while not bot.is_closed:
counter += 1
await bot.send_message(channel, counter)
await channel.send(counter)
await asyncio.sleep(3) # task runs every 3 seconds
bot.loop.create_task(my_background_task())
bot.run(token)
[]
From a cursory inspection, it would seem your problem is that you are only calling it once. Your method my_background_task is not called once every three seconds. It is instead your send_message method that is called once every three seconds. For intended behavior, place the print statement inside your while loop.
Although I am using rewrite, I found both of these resources helpful.
https://github.com/Rapptz/discord.py/blob/async/examples/background_task.py
https://github.com/Rapptz/discord.py/blob/rewrite/examples/background_task.py