I have developed a Teams bot that runs an infinite loop at startup in order to send proactive messages to users.
async def job():
i = 60
await asyncio.sleep(i)
await _create_file_of_the_day()
await _send_question_of_the_day()
await job()
if __name__ == "__main__":
try:
loop = asyncio.get_event_loop()
t1 = loop.create_task(job())
t2 = loop.create_task((web.run_app(APP, host="localhost", port=CONFIG.PORT)))
asyncio.gather(t1,t2)
loop.run_forever()
except Exception as error:
raise error
This work on local with python app.py but when I upload the bot to azure and test it online, the infinite loop is not started and so I find it impossible to proactively send messages to users.
The two methods called work. The first creates a file on azure and the second creates two questions using the contents of the file, which should be sent proactively to all members of the channel.
Does anyone know how to help me? I need to send messages to users based on a time delay not in response to their actions. This scheduling is not constant, for example I want to send messages only on working days and not on holidays.
Thanks to all
UPDATE
I have tried this second solution just on the comments, but the result is always the same. Locally the application behaves correctly, but on Azure cloud the routine that should loop seems not to be triggered.
async def job():
i = 60
await asyncio.sleep(i)
await _create_file_of_the_day()
await _send_question_of_the_day()
await job()
async def main():
runner = aiohttp.web.AppRunner(APP)
await runner.setup()
site = web.TCPSite(runner, host='localhost', port=CONFIG.PORT)
await site.start()
asyncio.create_task(job())
while True:
await asyncio.sleep(3600)
if __name__ == "__main__":
try:
asyncio.run(main())
except Exception as error:
raise error
Not being able to use the loop to be able to schedule messages, the problem was solved by using an Azure Function type timer trigger. This function calls an endpoint created inside the bot that each time it is called executes the job() method.
These two links may be useful to understand how to create an endpoint and how to query it. In the code samples, the query is given by a click on the link which can easily be replaced by a GET request within the code.
Proactive message sample with endpoint creation
Article with explanation of proactive messages
The sample code is in python, but by browsing the git folders you can find code in other programming languages.
For the development of Azure Function I found this series of three videos on YouTube very useful.
Useful video on development of timer trigger function in python
Related
I created a slack bot app using Django. In this app, the bot will ask some questions to the users within a given schedule(periodically) from the database. The bot will wait for the reply of the users.
This is how I am calling the slack API to ask the questions concurrently, at a time. I used async and await in the function.
async def post_async_message(channel, message):
""" broadcast message in the given channel asynchronously """
try:
response = await Async_Client.chat_postMessage(
channel=channel,
text=message
)
if response['ok']:
await asyncio.sleep(Wait_For_Answer)
except SlackApiError as e:
raise CustomError(f"Error occurred: {e}", e.response)
This is the function from where the post_async_message function has been called.
async def post_standup_message(standup_id):
participants = await models.get_participants(standup_id)
questions = await models.get_standup_questions(standup_id)
async def ask_question(user_id):
# send standup question to the user
async for question in questions:
try:
await views.post_async_message(user_id, question.Question)
except CustomError as e:
print(e)
tasks = [ask_question(participant.Slack_User_Id) async for participant in participants]
for completed_task in asyncio.as_completed(tasks):
await completed_task
asyncio.run(post_standup_message(49))
Everything is doing good. But one thing that I notice is that during the asking of the questions by the bot if I call any API at the same time, the server is unable to execute the API. But when the execution of the post_standup_message function is completed the API works perfectly. Which means the API and the bot are not running at a time. What is the reason behind that? Note that my bot and APIs are running on the same server.
Also, I have some questions.
Am I doing it right to execute the bot and APIs on the same server?
Is it good to have an external background scheduling tool to create a slack bot?
so I've been trying to make my discord bot send a message every day at 12:30 UCT but i cant seem to get my code to work I'm not sure if its not working because of incorrect code or because its on replit or whatever else the issue could be as i get no errors from this it just send the message once it loads online and that's all.
import datetime, asyncio
bot = commands.Bot(command_prefix="+")
Async def on_Ready():
await schedule_daily_message()
async def schedule_daily_message():
now = datetime.datetime.now()
then = now+datetime.timedelta(days=1)
then.replace(hour=12, minute=30)
wait_time = (then-now).total_seconds()
await asyncio.sleep(wait_time)
channel = bot.get_channel(Channel_id)
await channel.send("Enemies Spawned!")
client.run(os.getenv('TOKEN'))
await asyncio.sleep is non blocking. Your script will execute beyond that statement. You will need to use time.sleep, which that will block all execution of code until the timer has run out.
See here for a more in depth explanation and how to use this in functions:
asyncio.sleep() vs time.sleep()
A way to implement a function that posts a message to a channel after a delay could look like this:
async def send_after_delay(d, c, m):
time.sleep(d)
await c.send(m)
Calling this function asynchronously allows you to continue with code execution beyond it, while still waiting past the calling of the function to send the message.
Goal:
I'm developing a discord bot which scans a url every 5 seconds or so, checks for a specified change on that webpage, and will send a message in the discord channel if that change occurs. I've done this by sending the url to the bot using an if statement in on_message. The url is then passed to a tasks.loop() function, where it is scanned and processed in another function for the change.
Problem:
I'd like to be able to send a message in the discord channel which quickly ends the process taking place in the tasks.loop(), so that I can pass it a different url to scan using the on_message function. In its current form, it works-- just very slowly. From the time the cancel trigger is sent, it takes around 3 minutes to send the verification message that the process has been cancelled. I need to make this 5 seconds or less. For what its worth, the bot is kept running using replit and uptime robot, but I am sure that the long response time is not related to the frequency the repl is awoken by uptime robot.
Code:
My code is much more complex and riddled with obscurely named variables, so here is a much simpler snippet of code with the same general structure.
client = discord.Client()
channel = client.get_channel(CHANNEL_ID)
#tasks.loop()
async def myloop(website, dataframe):
channel = client.get_channel(CHANNEL_ID)
try:
# iteratively scrape data from a website for
# a predefined change in the dataframe
if change = True:
await channel.send(notification)
except:
pass
#client.event
async def on_message(message):
channel = client.get_channel(CHANNEL_ID)
msg = message.content
if msg.startswith('track'):
website = msg[6:]
await channel.send('Now tracking '+str(website))
myloop(website,df)
if msg.starswith('stop'):
myloop.cancel()
await channel.send('Done tracking, awaiting orders.')
Attempted Solutions:
I have tried using some forms of threading, which I am very new to, and I haven't found a way to make it work any faster. Any suggestions or solutions would be greatly appreciated! I've been combing the web for help for quite some time now.
Looks like you could use client.loop.create_task to create asyncio task objects, and their cancel method to immediately cancel those asyncio tasks at the right time, e.g.
import asyncio
from replit import db
_task = None
async def myloop():
website = db['website']
dataframe = db['dataframe']
channel = client.get_channel(CHANNEL_ID)
while not client.is_closed():
await asyncio.sleep(5)
try:
# iteratively scrape data from a website for
# a predefined change in the dataframe
if change:
await channel.send(notification)
except:
pass
#client.event
async def on_message(message):
global _task # This gives the function access to the variable that was already created above.
msg = message.content
if msg.startswith('track'):
website = msg[6:]
await message.channel.send('Now tracking '+str(website))
db['website'] = website
db['dataframe'] = df
if _task is not None:
_task.cancel()
_task = client.loop.create_task(myloop())
if msg.startswith('stop'):
if _task is not None:
_task.cancel()
_task = None
await message.channel.send('Done tracking, awaiting orders.')
The argument create_task takes is a coroutine that takes no arguments, so the website URL and dataframe need to be accessible to the function a different way (I'm not sure which way you would prefer or would be best; using replit's db is just an example).
With this approach, you should be able to use track again to change which website is being monitored without using stop in between.
More details in the docs:
discord.Client.loop
loop.create_task
Task.cancel
asyncio.sleep
discord.Client.is_closed
Replit db
I am having a hard time understanding asyncio and the await function. I have a script that uses the Telethon wrapper that requires me to understand / use asyncio.
The only goal of the script is to download an image from a Telegram group.
I have read a beginner asyncio tutorial but I am still having a very hard time understand the concept of it and how to await something.
# Start Client
client1 = TelegramClient('sesh1', api_id1, api_hash1)
client1.start('xxx')
time.sleep(0.1)
# ---------------------------------------
# Get last message
for message in client1.iter_messages(groupname, limit=1):
data = message.file
print(data)
# Download is last message is media
if data is not None:
print("Img found")
x = client1.download_media(message=message)
print(x)
The error I keep getting is:
sys:1: RuntimeWarning: coroutine 'DownloadMethods.download_media' was never awaited.
How do I await the download function?
I am working on a bot that streams post from the Steem Blockchain (using the synchronous beem library) and sends posts that fulfil certain criteria to a Discord channel (using the asynchronous Discord.py library). This is is my (simplified) code:
bot = commands.Bot(command_prefix="!")
async def send_discord(msg):
await bot.wait_until_ready()
await bot.send_message(bot.get_channel("mychannelid"), msg)
async def scan_post(post):
"""Scan queued Comment objects for defined patterns"""
post.refresh()
if post["author"] == "myusername":
await loop.create_task(send_discord("New post found"))
async def start_blockchain():
stream = map(blockchain.stream(opNames=["comment"]))
for post in stream:
await loop.create_task(scan_post(post))
if __name__ == '__main__':
while True:
loop.create_task(start_blockchain())
try:
loop.run_until_complete(bot.start(TOKEN))
except Exception as error:
bot.logout()
logger.warning("Bot restarting "+repr(error))
Before I implemented discord.py I would just call the synchronous function scan_post(post) and it worked just fine, but now with the asynchronous implementation the posts are not processed fast enough and the stream has a rapidly increasing delay. If I make scan_post(post) a synchronous function, the processing time is fine, but the Discord websocket closes (or does not even open) and the bot goes offline. How can I solve this in a simple way (without rewriting the beem library)?
I solved the problem: I run the beem stream in its own thread and the asynchronous functions in a second thread. With the janus library I can then add objects from the beam thread to a queue that is processed by the asynchronous thread.