How to add a cooldown timer for commands - python-3.x

I am very new to coding and want to just add a command timer to the code i got off the internet. I don't know how to do this and all the other codes I find are too much for me to know. I just want to be able to add around a 10 second cooldown timer to each command.
import discord
import asyncio
from discord.ext import commands
import datetime as DT
import discord.utils
class MyClient (discord.Client):
async def on_ready(self):
print('Logged in as')
print(self.user.name)
print(self.user.id)
print('------')
async def on_message(self, message):
# we do not want the bot to reply to itself
if message.author.id == self.user.id:
return
if message.content.startswith('!discord'): # I want to add a timer
channel = client.get_user(message.author.id)
await channel.send(''.format(message))
async def on_member_join(self, member):
guild = member.guild
if guild.system_channel is not None:
to_send = 'Welcome {0.mention} to {1.name}!'.format(member, guild)
await guild.system_channel.send(to_send)
client = MyClient()
client.run('')

The time.sleep() function, which another user suggested is a blocking call. Which means it will block the entire thread from being ran. I am not sure exactly how the Discord framework works, but I imagine thread blocking might be a problem.
A better solution I think would be to use asyncio, especially since you've already imported it. It will allow the program to do other stuff, while waiting the 10 seconds. Another Stackoverflow thread if you're interested
if message.content.startswith('!discord'):
channel = client.get_user(message.author.id)
await asyncio.sleep(10)
await channel.send(''.format(message))
Edit
You can save the last time the function was called, and check with an IF-statement if 10 seconds have passed since the last call.
class MyClient (discord.Client):
last_called = None
async def on_ready(self):
# other stuff
async def on_message(self, message):
# check if 10 seconds have passed since the last call
if MyClient.last_called and DT.datetime.now() < MyClient.last_called + DT.timedelta(seconds=10):
return
# we do not want the bot to reply to itself
if message.author.id == self.user.id:
return
if message.content.startswith('!discord'):
channel = client.get_user(message.author.id)
await channel.send(''.format(message))
MyClient.last_called = DT.datetime.now() # save the last call time

If you add this to the imports at the top of the file:
from time import sleep
then you can use the sleep() function to pause the script for a set number of seconds when execution reaches that line. So for instance, adding this:
sleep(10)
somewhere inside the functions would sleep for 10 seconds whenever the script reaches that line.

Related

Cannot run a While Loop in AWS Python Lambda

import asyncio
from aiogram import Bot, Dispatcher, types
# Handlers
async def echo_hello(message: types.Message):
while True:
await message.reply("Hello!")
await asyncio.sleep(20)
# AWS Lambda funcs
async def register_handlers(dp: Dispatcher):
dp.register_message_handler(echo_hello, commands=['sayhello'])
async def process_event(event, dp: Dispatcher):
Bot.set_current(dp.bot)
update = types.Update.to_object(event)
await dp.process_update(update)
async def main(event):
bot = Bot(token=TOKEN)
dp = Dispatcher(bot)
await register_handlers(dp)
await process_event(event, dp)
return 'ok'
def lambda_handler(event, context):
return asyncio.get_event_loop().run_until_complete(main(event))
I can't seem to get my telegram bot in AWS Python Lambda to loop the Hello message. Provided I got no idea how else to get the lambda_handler function, I only saw this as an example from https://github.com/DavisDmitry/aiogram-aws-serverless-example
EDIT: NVM the default timeout for functions is 15 minutes at best so any loop will only work for 15 minutes at the most.
As #luk2302 mentions in the comment above, indeed Lambdas only have an upper limit of at most 15 minutes customizable by the user in the settings.
If set to 15 minutes, any loop will execute for that amount of time given the conditions make it so. At the time I asked this question I was unaware of the fact and by default the timeout value was set to 5 seconds, while my code was putting the while loop to sleep for 20 seconds. Hence I was unable to get any repeated messages. Once I set it to 2 minutes (for example), it repeated the message about 6 times after which it stopped.
Lambda functions do run almost forever but these are not suitable for functions which might have long execution process (including loops). For that, EC2 instances can be used.

Real time stdout redirect from a python function call to an async method

So I have a heavy time-consuming function call my_heavy_function and I need to redirect that output to a web interface that is calling it, I have a method to send messages to the web interface, let's called that method async push_message_to_user().
basically, it's something like
import time
def my_heavy_function():
time_on = 0
for i in range(20):
time.sleep(1)
print(f'time spend {time_on}')
time_on = time_on+1
async def push_message_to_user(message:str):
# some lib implementation
pass
if __name__ == "__main__":
my_heavy_function() # how push prints to the UI ?
maybe there is a way giving my_heavy_function(stdout_obj) and use that "std_object"(StringIO) to do something like stdout_object.push(f'time spend {time_on}'). I can do that, but what I can't change the my_heavy_function() by an async version, to add push_message_to_user() directly instead of the print (it's used by other non-ascyn routines)
what I would want it's something like (pseudocode)
with contextlib.redirect_output(my_prints):
my_heavy_function()
while my_prints.readable():
# realtime push
await push_message_to_user(my_prints)
Thanks!
Thanks for the comment of #HTF I finally managed to solve the problem with janus. I copy the example of the repo, and I modified in order to receive a variable number of messages (because I don't know how many iterations my_heavy_function() will use)
import asyncio
import janus
import time
def my_heavy_function(sync_q):
for i in range(10):
sync_q.put(i)
time.sleep(1)
sync_q.put('end') # is there a more elegant way to do this ?
sync_q.join()
async def async_coro(async_q):
while True:
val = await async_q.get()
print(f'arrived {val}')
# send val to UI
# await push_message_to_user(val)
async_q.task_done()
if val == 'end':
break
async def main():
queue = janus.Queue()
loop = asyncio.get_running_loop()
fut = loop.run_in_executor(None, my_heavy_function, queue.sync_q)
await async_coro(queue.async_q)
await fut
queue.close()
await queue.wait_closed()
asyncio.run(main())

using Subprocess to avoid long-running task from disconnecting discord.py bot?

I created a bot for my Discord server, that goes to the Reddit API for a given subreddit, and posts the Top 10 results for the Day in the Discord chat, based on the subreddit(s) that you input. It disregards self posts, and really only posts pictures and GIFs. The Discord message command would look something like this: =get funny awww news programming, posting the results for each subreddit as it gets them from the Reddit API (PRAW). THIS WORKS WITH NO PROBLEM. I know that the bot's ability to hit the API and post to discord works.
I added another command =getshuffled which puts all of the results from the subreddits in a large list, and then shuffles them before posting. This works really well with a request of up to ~50 subreddits.
This is what I need help with:
Because it can be such a large list of results, 1000+ results from 100+ subreddits, the bot is crashing on really big requests. Based on what help I got from my question yesterday, I understand what is going wrong. The bot is starting, it is talking to my Discord server, and when I pass it a long request, it stops talking to the server for too long while the Reddit API call is done, and it the Discord connection fails.
So, what I think I need to do, is have a subprocess for the code that goes to the Reddit API and pulls the results, (which I think will let the discord connection stay running), and then pass those results BACK to the bot when it is finished....
Or... this is something that Asyncio can handle on its own...
I'm having a hard time with the subprocess call, as I knew I would.
Basically, I either need help with this subprocess trickery, or need to know if I'm being an idiot and Asyncio can handle all of this for me. I think this is just one of those "I don't know what I don't know" instances.
So to recap: The bot worked fine with smaller amounts of subreddits being shuffled. It goes through the args sent (which are subreddits), grabbing info for each post, and then shuffling before posting the links to discord. The problem is when it is a larger set of subreddits of ~ 50+. In order to get it to work with the larger amount, I need to have the Reddit call NOT block the main discord connection, and that's why I'm trying to make a subprocess.
Python version is 3.6 and Discord.py version is 0.16.12
This bot is hosted and running on PythonAnywhere
Code:
from redditBot_auth import reddit
import discord
import asyncio
from discord.ext.commands import Bot
#from discord.ext import commands
import platform
import subprocess
import ast
client = Bot(description="Pulls posts from Reddit", command_prefix="=", pm_help = False)
#client.event
async def on_ready():
return await client.change_presence(game=discord.Game(name='Getting The Dank Memes'))
def is_number(s):
try:
int(s)
return True
except:
pass
def show_title(s):
try:
if s == 'TITLES':
return True
except:
pass
async def main_loop(*args, shuffled=False):
print(type(args))
q=10
#This takes a integer value argument from the input string.
#It sets the number variable,
#Then deletes the number from the arguments list.
title = False
for item in args:
if is_number(item):
q = item
q = int(q)
if q > 15:
q=15
args = [x for x in args if not is_number(x)]
if show_title(item):
title = True
args = [x for x in args if not show_title(x)]
number_of_posts = q * len(args)
results=[]
TESTING = False #If this is turned to True, the subreddit of each post will be posted. Will use defined list of results
if shuffled == False: #If they don't want it shuffled
for item in args:
#get subreddit results
#post links into Discord as it gets them
#The code for this works
else: #if they do want it shuffled
output = subprocess.run(["python3.6", "get_reddit.py", "*args"])
results = ast.literal_eval(output.decode("ascii"))
# ^^ this is me trying to get the results back from the other process.
. This is my get_reddit.py file:
#THIS CODE WORKS, JUST NEED TO CALL THE FUNCTION AND RETURN RESULTS
#TO THE MAIN_LOOP FUNCTION
from redditBot_auth import reddit
import random
def is_number(s):
try:
int(s)
return True
except:
pass
def show_title(s):
try:
if s == 'TITLES':
return True
except:
pass
async def get_results(*args, shuffled=False):
q=10
#This takes a integer value argument from the input string.
#It sets the number variable,
#Then deletes the number from the arguments list.
title = False
for item in args:
if is_number(item):
q = item
q = int(q)
if q > 15:
q=15
args = [x for x in args if not is_number(x)]
if show_title(item):
title = True
args = [x for x in args if not show_title(x)]
results=[]
TESTING = False #If this is turned to True, the subreddit of each post will be posted. Will use defined list of results.
NoGrabResults = False
#This pulls the data and creates a list of links for the bot to post
if NoGrabResults == False:
for item in args:
try:
#get the posts
#put them in results list
except Exception as e:
#handle error
pass
try:
#print('____SHUFFLED___')
random.shuffle(results)
random.shuffle(results)
random.shuffle(results)
except:
#error stuff
print(results)
#I should be able to read that print statement for the results,
#and then use that in the main bot function to post the results.
.
#client.command()
async def get(*args, brief="say '=get' followed by a list of subreddits", description="To get the 10 Top posts from a subreddit, say '=get' followed by a list of subreddits:\n'=get funny news pubg'\n would get the top 10 posts for today for each subreddit and post to the chat."):
#sr = '+'.join(args)
await main_loop(*args)
#THIS POSTS THE POSTS RANDOMLY
#client.command()
async def getshuffled(*args, brief="say '=getshuffled' followed by a list of subreddits", description="Does the same thing as =get, but grabs ALL of the posts and shuffles them, before posting."):
await main_loop(*args, shuffled=True)
client.run('my ID')
UPDATE: Following advice, I had the command passed through a ThreadPoolExecutor as shown:
async def main(*args, shuffled):
if shuffled==True:
with concurrent.futures.ThreadPoolExecutor() as pool:
results = await asyncio.AbstractEventLoop().run_in_executor(
executor=pool, func=await main_loop(*args, shuffled=True))
print('custom thread pool', results)
but this still results in errors when the script tries to talk to Discord:
ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<Client._run_event() running at /home/GageBrk/.local/lib/python3.6/site-packages/discord/client.py:307> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f28acd8db28>()]>>
Event loop is closed
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
...
It is sending the results correctly, but discord is still losing connection.
praw relies on the requests library, which is synchronous meaning that the code is blocking. This can cause your bot to freeze if the blocking code takes too long to execute.
To get around this, a separate thread can be created that handles the blocking code. Below is an example of this. Note how blocking_function will use time.sleep to block for 10 minutes (600 seconds). This should be more than enough to freeze and eventually crash the bot. However, since the function is in it's own thread using run_in_executor, the bot continues to operate as normal.
New versions
import time
import asyncio
from discord.ext import commands
from concurrent.futures import ThreadPoolExecutor
def blocking_function():
print('entering blocking function')
time.sleep(600)
print('sleep has been completed')
return 'Pong'
client = commands.Bot(command_prefix='!')
#client.event
async def on_ready():
print('client ready')
#client.command()
async def ping(ctx):
loop = asyncio.get_event_loop()
block_return = await loop.run_in_executor(ThreadPoolExecutor(), blocking_function)
await ctx.send(block_return)
client.run('token')
Older async version
import time
import asyncio
from discord.ext import commands
from concurrent.futures import ThreadPoolExecutor
def blocking_function():
print('entering blocking function')
time.sleep(600)
print('sleep has been completed')
return 'Pong'
client = commands.Bot(command_prefix='!')
#client.event
async def on_ready():
print('client ready')
#client.command()
async def ping():
loop = asyncio.get_event_loop()
block_return = await loop.run_in_executor(ThreadPoolExecutor(), blocking_function)
await client.say(block_return)
client.run('token')

how to make this code Non-Blocking with Asyncio?

I'm trying to create a code that is non-blocking and that allows me to create multiple clients to do some request on a server. However, I can't create more than 1 client simultaneously!
CLIENT.PY
import asyncio
PYTHONASYNCIODEBUG = 1
#ECHO CLIENT PROTOCOL
async def tcp_echo_client(message, loop):
# Send request to server
reader, writer = await asyncio.open_connection('127.0.0.1', 8888, loop=loop)
print('Send: %r' % message)
writer.write(message.encode())
# Receive the information
if message == '1':
await asyncio.Task(read_position(reader))
else:
await asyncio.ensure_future(read_server(reader))
# Close the connection
print('Close the socket')
writer.close()
#ASYNCIO COROUTINES TO REQUEST INFORMATION
async def read_server(reader):
server_message = await reader.read()
print(type(server_message))
print('Received: %r' % server_message.decode())
async def read_position(reader):
while True:
print("I'm Here")
server_message = await reader.read(50)
position = server_message.split()
print(position)
print(type(position))
print('Received: %r' % server_message.decode())
#FUNCTION THAT CREATES THE CLIENT
def main(message):
'''This function creates the client'''
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(tcp_echo_client(message, loop))
finally:
pass
# This is how I create a new client
if __name__ == '__main__':
message = '2'
main(message)
message = '3'
main(message)
I want to create multiples clients, however, the code is blocking in the first main when I send the message('1'). I don't know why the code is blocking if I'm using asyncio. My server accepts multiples connections, because if I run this code seperatly I can do everything. The propose of this is to create clients every time I click a button at my Kivy app to send a request to the server.
This problems exists because I want to control a Robot and do a lot of things simultaneously, however with a blocking code I can't do it because I'm get stuck
Maybe it's a stupid question but I've only started coded 2 months ago and I haven't any help.
Your main function doesn't "create the client", as its docstring claims. It creates the client and runs it to completion. This is why multiple invocations of main() result in serial execution. main() being a regular function, that's exactly what you'd expect, asyncio doesn't change that. It's useful to remember that asyncio is single-threaded, so it can't do some "run in the background" magic, unless you cooperate.
To cooperate, you need to tell aysncio to start both clients, and then await them in parallel:
async def main(messages):
loop = asyncio.get_event_loop()
# launch the coroutines in parallel
tasks = [loop.create_task(tcp_echo_client(msg, loop)) for msg in messages]
# don't exit until all of them are done
await asyncio.gather(*tasks)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main(['2', '3']))
Note that when awaiting your coroutines, you don't need to wrap them in asyncio.ensure_future() or asyncio.Task() - asyncio will handle that automatically. await read_position(reader) and await read_server(reader) would work just fine and have the same meaning as the longer versions.

Why can I not send a message via Discord.py from a function?

I have created a script which takes in a message formatted as !notice [MM/DD/YY HH:mm], message, target which then calls a function using threading.Timer to call it at the time given in the message in UTC.
Where I am having trouble is sending a message from this function, I can't seem to get a message to send from the function regardless of the input of the message.
See below:
import discord
import asyncio
from datetime import *
import threading
client = discord.Client()
#client.event
async def on_message(message):
if message.content[:7].lower() == "!notice".lower():
try:
notice = [datetime.strptime(message.content[message.content.find("[")+1:message.content.find("]")], "%m/%d/%y %H:%M"), message.content.split(", ")[1], message.content.split(", ")[2]]
await client.send_message(message.channel, 'Created notice "'+notice[1]+'" to be sent to '+notice[2]+' at '+str(notice[0])+' UTC.')
threading.Timer((notice[0] - datetime.utcnow()).total_seconds(), lambda a=notice[1], b=notice[2]: func(a, b)).start()
print(str((notice[0] - datetime.utcnow()).total_seconds())+" seconds until message is sent")
except (ValueError, IndexError):
await client.send_message(message.channel, 'Incorrect Notice Format.\nMust be "!notice [MM/DD/YY HH:mm], Notice contents, Target".\nEG: "!notice [01/01/2017 12:00], This is a notice, Siren Raid Team".')
def func(message, target):
print("Func called")
for i in client.servers:
for c in i.channels:
client.send_message(c, target+message)
client.run(MY_SESSION_KEY)
This returns "Func called" so I know the function is being called, but no exceptions are raised and no message is posted in my chat.
I also tried substituting func with:
async def func(message, target):
print("Func called")
for i in client.servers:
for c in i.channels:
await client.send_message(c, target+message)
However this throws up an exception:
RuntimeWarning: coroutine 'func' was never awaited
Frankly, I'm out of my depth here. Is there any reason why this won't work?
I saw online that asyncio is not thread-safe. But, unless I'm misunderstanding, my first example didn't use that library in the function. Could is still be causing problems?
discord.py's discord.Client.send_message is a coroutine and must be awaited, like you did in your second code snippet. However, threading.Timer does not support coroutines.
What you're looking for is create_task, which enables you to run a coroutine on the event loop. Since most of what your coroutine does is sleeping (mimicking threading.Timer), your on_message will proceed to run, given that you use asyncio.sleep and not time.sleep - the latter blocks the event loop. Here's an example, including passing arguments to functions:
import asyncio
loop = asyncio.get_event_loop()
async def sleep_and_add(a, b):
await asyncio.sleep(3)
print(a, '+', b, 'is', a + b)
async def on_message():
# prepare arguments to your function
loop.create_task(sleep_and_add(2, 3))
# continue doing other things
In case anyone needs help with just sending messages to a server from inside of a flask function. I spent hours with this problem myself after going down the rabbit hole of asyncio and threading. Turned out to be way easier than I thought. Webhooks, unlike a regular Discord bot, are entirely synchronous, which means they can run in a flask function no problem.
If your goal is to just simply send a message to a channel from a flask endpoint and not use any other functionality, try webhooks.
from discord import Webhook, RequestsWebhookAdapter
webhook = Webhook.partial(WEB_HOOK_ID, 'WEB_HOOK_TOKEN', adapter=RequestsWebhookAdapter())
and then post the message
webhook.send('MESSAGE', username='WEBHOOK_BOT')
Creating Webhook tutorial
Discord.py Webhook Info

Resources