python aiogram how to stop an asynchronous loop - python

Simple example
import asyncio
import logging
from aiogram import Bot, Dispatcher, types
logging.basicConfig(level=logging.INFO)
token = 'token'
bot = Bot(token=token)
dp = Dispatcher(bot=bot)
#dp.callback_query_handler(text='stoploop')
async def stop_loop(query: types.CallbackQuery):
# TODO how to stop test loop?
await query.message.edit_text('stop')
#dp.callback_query_handler(text='test')
async def start_loop(query: types.CallbackQuery):
a = 100
while True:
a -= 1
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton('<<<Stop And Back To Home', callback_data='stoploop'))
await query.message.edit_text(str(a),reply_markup=markup)
await asyncio.sleep(1)
#dp.message_handler(commands='start')
async def start_cmd_handler(message: types.Message):
markup = types.InlineKeyboardMarkup()
markup.add(
types.InlineKeyboardButton('start loop', callback_data='test')
)
await message.reply('test', reply_markup=markup)
async def main():
try:
await dp.start_polling()
finally:
await bot.close()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
When I click start_loop, the tg message box on my page starts to display a countdown. When I click stop, how can I stop the previous countdown?
I use id(query) to confirm that the query instance sent twice is not the same. After I execute the stop_loop function, start_loop will still execute and change the content of the message.
Can someone tell me how to stop it?

I used redis to solve it, but I don't know if this is the most appropriate way. If there is a more suitable way, please let me know

To manage your loop you should take it outside the handlers and just get in from any storage (dict is used for example).
Basic example of the loop
loops = {}
class Loop:
def __init__(self, user_id):
self.user_id = user_id
self._active = False
self._stopped = True
loops[self.user_id] = self
#classmethod
def get_loop(cls, user_id):
return loops.get(user_id, cls(user_id))
#property
def is_running(self):
return not self._stopped
async def start(self):
self._active = True
asyncio.create_task(self._run_loop())
async def _run_loop(self):
while self._active:
await bot.send_message(self.user_id, 'loop is running')
await asyncio.sleep(5)
self._stopped = True
async def stop(self):
self._active = False
while not self._stopped:
await asyncio.sleep(1)
So then:
#dp.callback_query_handler(text='start')
async def start_loop(query: CallbackQuery):
user = query.from_user
loop = Loop.get_loop(user.id)
if loop.is_running:
return await query.answer('Loop is already running')
loop.start()
await query.answer('Started!')
#dp.callback_query_handler(text='stop')
async def stop_loop(query: CallbackQuery):
user = query.from_user
loop = Loop.get_loop(user.id)
await query.answer('Stopping...')
await loop.stop()
await bot.send_message(user.id, 'Loop successfully stopped.')

Related

Aggregation of 2 RabbitMQ messages does not work properly (messages hanging unacked)

I need to listen tasks on 2 queues, so I wrote the code below, but it has a problem. Currently it behaves like this: if the code started when 2 queues were full, it works great. But if queues were empty one of them was, the code reads messages, but does not proccess them (does not send ack, does not do the logic). But the messages became unacked, until I stop the code. I do not see any reason to be them unacked and unprocessed.
I can't understand what is wrong with the code? May be there is another way to aggregate 2 or more queues like this?
# task_processor.py
from aio_pika import IncomingMessage
class TaskProcessor:
MAX_TASKS_PER_INSTANCE = 1
def __init__(self):
self._tasks = []
def can_accept_new_task(self) -> bool:
return len(self._tasks) < self.MAX_TASKS_PER_INSTANCE
async def process(self, message: IncomingMessage):
self._tasks.append(message)
print(message.body)
await message.ack()
self._tasks.pop()
# main.py
import asyncio
from asyncio import QueueEmpty
from typing import Callable
import aio_pika
from aio_pika import RobustQueue
from dotenv import load_dotenv
load_dotenv()
from core.logger.logger import logger
from core.services.rabbitmq.task_processor.task_processor import TaskProcessor
async def get_single_task(queue: RobustQueue):
while True:
try:
msg = await queue.get(timeout=3600)
return msg
except QueueEmpty:
await asyncio.sleep(3)
except asyncio.exceptions.TimeoutError:
logger.warning('queue timeout error')
pass
except Exception as ex:
logger.error(f"{queue} errored", exc_info=ex)
async def task_aggregator(queue1: RobustQueue, queue2: RobustQueue, should_take_new_task_cb: Callable):
while True:
if should_take_new_task_cb():
queue2, queue1 = queue1, queue2
gen1 = get_single_task(queue1)
gen2 = get_single_task(queue2)
done, _ = await asyncio.wait([gen1, gen2], return_when=asyncio.FIRST_COMPLETED)
for item in done:
result = item.result()
yield result
else:
await asyncio.sleep(1)
async def tasks(queue1: RobustQueue, queue2: RobustQueue, should_take_new_task_cb: Callable):
async for task in task_aggregator(queue1, queue2, should_take_new_task_cb):
yield task
async def main():
connection = await aio_pika.connect_robust(
f"amqp://user:password#host:port/vhost?heartbeat={180}"
)
channel1 = connection.channel()
channel2 = connection.channel()
await channel1.initialize()
await channel2.initialize()
queue1 = await channel1.get_queue('queue1')
queue2 = await channel2.get_queue('queue2')
task_processor = TaskProcessor()
task_generator = tasks(queue1, queue2, task_processor.can_accept_new_task)
while True:
if task_processor.can_accept_new_task():
task = await anext(task_generator)
await task_processor.process(task)
else:
await asyncio.sleep(1)
if __name__ == '__main__':
asyncio.run(main())

how to restart asyncio loop in python?

I'm wrting a python program to listen to NFT mints event by using websocket, but after running some time, the websocket will lose its connection, hence, I need to stop loop and restart it, but after searching many answers there still no solution for it, here is my code:
async def main():
await asyncio.gather(erc1155_listener(),erc721_listener())
async def erc721_listener():
task_set = set()
async with connect(alchemy_ws_url) as ws:
await ws.send(erc721_log_filter)
while True:
try:
message = await asyncio.wait_for(ws.recv(), timeout=60)
event = json.loads(message)
topics = event['params']['result']['topics']
transaction_hash = event['params']['result']['transactionHash']
contract_address = event['params']['result']['address']
if len(topics) == 4 and re.match(re.compile(r'0x[0]{0,}$'),topics[1]):
task = asyncio.create_task(free_mint_filter(transaction_hash,contract_address))
task_set.add(task)
task.add_done_callback(task_set.discard)
except Exception:
pass
async def erc1155_listener():
task_set = set()
async with connect(alchemy_ws_url) as ws:
await ws.send(erc1155_log_filter)
while True:
try:
message = await asyncio.wait_for(ws.recv(), timeout=60)
event = json.loads(message)
topics = event['params']['result']['topics']
transaction_hash = event['params']['result']['transactionHash']
contract_address = event['params']['result']['address']
if re.match( re.compile(r'0x[0]{0,}$'),topics[2]):
task = asyncio.create_task(free_mint_filter(transaction_hash,contract_address))
task_set.add(task)
task.add_done_callback(task_set.discard)
except Exception as e:
pass
async def free_mint_filter(transaction_hash,contract_address):
loop = asyncio.get_running_loop()
try:
transaction = await loop.run_in_executor(None, web3.eth.getTransaction, transaction_hash)
value = transaction['value']
if value == 0:
print(transaction['hash'].hex(),contract_address)
else:
print(transaction['hash'].hex(),'is not a free mint')
except Exception as e:
print(e)
asyncio.run(main())
if you have another better solution than restarting asyncio loop please tell me.

How to exit a while loop under the decorator command in discord?

I'm trying to command my bot on stopping the loop whenever the user will type 'stop'. What code do you think I must write?
#bot.command()
async def loop(ctx):
while True:
await ctx.send('It\'s Looping!')
#.......?
I'm new in this field that's y, Thanks : )
You can either store whether the loop should be running in a variable, like so:
should_loop = False
#bot.command()
async def loop(ctx):
global should_loop
should_loop = True
while should_loop:
await ctx.send("It's Looping!")
#bot.command()
async def stop(ctx):
global should_loop
should_loop = False
which works fine if you're only ever expecting one loop to be active at any time.
Alternatively, and what works better in my opinion, you can use the bot.wait_for coroutine:
#bot.command()
async def loop(ctx):
should_loop = True
async def looper():
while should_loop:
await ctx.send("It's Looping!")
async def listener():
nonlocal should_loop
try:
await bot.wait_for("message", check=lambda m: m.content == "stop", timeout=60)
except asyncio.TimeoutError:
pass
else:
should_loop = False
await asyncio.gather(looper(), listener())
This second approach allows you to have a separate should_loop for each loop command call, which could be what you want.

Discord Rewrite: Client Loop Create_Task is not running

I am currently working on a MusicPlayer that loops over a function inside the class MusicPlayer(). The problem is it doesn't seem to even be playing. Below is my class and the create_task is inside the __init__ but its like it never gets ran. I have ran loops before but never in a class, is there something I am missing?
MusicPlayer Class:
class MusicPlayer():
__slots__ = ("client", "_guild", "_ctxs", "_channel", "_cog", "np", "volume", "current", "colour")
queue = asyncio.Queue()
next = asyncio.Event()
def __init__(self, ctx, client):
self.client = client
self._guild = ctx.guild
self._ctxs = ctx
self._channel = ctx.channel
self._cog = ctx.cog
self.np = None
self.volume = defaultvolume
self.current = None
self.colour = self.client.defaultcolour
self.client.loop.create_task(self.player_loop())
async def player_loop(self):
print('player_loop ran')
await self.client.wait_until_ready()
while True:
self.next.clear()
try:
async with timeout(300):
self.current = await queue.get()
except asyncio.CancelledError:
return
except asyncio.TimeoutError:
guild = self._guild
vc = guild.voice_client
self.destroy(guild)
if not vc: return
await self._ctxs.send(":point_right: **I disconnected myself from the **`{}`** voice channel as I was not playing audio for 5 minutes!**".format(vc.channel.name))
return
except:
self.destroy(self._guild)
await self._ctxs.send(":thumbsdown: **Error: getting next song failed!** Please retry later!")
return
self._ctxs.voice_client.play(self.current, after=lambda: self.client.loop.call_soon_threadsafe(next.set))
self.current.volume = self.volume
thumbnail = self.current.thumbnail if self.current.thumbnail else self.client.user.avatar_url
self.colour = await self.client.get_average_colour(thumbnail)
embednps = discord.Embed(colour=self.colour)
embednps.add_field(name="Now Playing", value=f"```{self.current.title}```", inline=False)
embednps.add_field(name="Link", value=f"[URL]({self.current.web_url})", inline=True)
embednps.add_field(name="Duration", value=self.client.time_from_seconds(self.current.duration), inline=True)
embednps.add_field(name="Channel", value=f"{self.current.uploader}", inline=False)
embednps.set_thumbnail(url=f"{thumbnail}")
embednps.set_footer(text=f"Requested by {self.current.requester}", icon_url=self.current.requester.avatar_url)
self.np = await self._channel.send(embed=embednps)
await next.wait()
print("Terminated")
# Cleanup player
self.current.cleanup()
self.current = None
Looks like you're using an older revision of this: Basic music with playlist support on Rewrite
You should change the create_task from:
self.client.loop.create_task(self.player_loop())
To:
ctx.bot.loop.create_task(self.player_loop())

Missing 1 required positional argument: 'number'

Hi I'm having an issue running a asyncio loop it's asking for a missing 1 required positional argument: 'number'.
Here is what I'm working with:
async def purge_modlog(ctx, number):
tomorrow = datetime.now()+timedelta(days=1)
midnight = datetime(year=tomorrow.year, month=tomorrow.month,
day=tomorrow.day, hour=20, minute=35, second=0)
number = int(number)
server = before.server
db = fileIO(self.direct, "load")
if not server.id in db:
return
channel = db[server.id]["Channel"]
if number > 99 or number < 1:
await ctx.send("I can only delete messages within a range of 1 - 99", delete_after=10)
else:
author = ctx.message.author
authorID = author.id
mgs = []
number = int(number)
channel = modlog
async for x in bot.logs_from((channel), limit = int(number+1)):
mgs.append(x)
await asyncio.sleep((midnight - datetime.now()).seconds)
print("Deleting modlog messages 14 day or older")
await asyncio.sleep(5)
await delete_messages(mgs)
await ctx.send('Success!', delete_after=4)
await asyncio.sleep(86400) # Wait 24 hours
def check_folder():
if not os.path.exists('data/modlogset'):
print('Creating data/modlogset folder...')
os.makedirs('data/modlogset')
def check_file():
f = 'data/modlogset/settings.json'
if not fileIO(f, 'check'):
print('Creating default settings.json...')
fileIO(f, 'save', {})
def setup(bot):
check_folder()
check_file()
q = ModLog(bot)
loop = asyncio.get_event_loop()
loop.create_task(q.purge_modlog())
bot.add_cog(q)
To loop the event under def def setup(bot): You can see
loop = asyncio.get_event_loop()
loop.create_task(q.purge_modlog())
This is supposed to loop the event (q.purge_modlog()) I'm not sure what I'm doing wrong here. I have already tried the follow (q.purge_modlog(ctx, number))
line 686, in setup
loop.create_task(q.purge_modlog(ctx, number))
NameError: name 'ctx' is not defined
If anyone could help me that would be great. To add this is a module.
I have corrected some mistakes here. Try to read through the discord.py documentation on purging messages.
class ModLog:
def __init__(self, bot):
self.bot = bot
async def on_ready(self):
await self.bot.wait_until_ready()
for server in self.bot.servers:
channel = self.bot.get_channel("channel ID here")
if channel:
self.bot.loop.create_task(self.modlog_purge(channel))
async def modlog_purge(self, channel):
while True:
now = datetime.utcnow()
two_weeks_ago = now - timedelta(days=14)
await self.bot.purge_from(channel, before=two_weeks_ago)
await asyncio.sleep(86400)
def setup(bot):
q = ModLog(bot)
bot.add_cog(q)
class ModLog:
def __init__(self, bot):
self.bot = bot
async def on_ready(self):
await self.bot.wait_until_ready()
for guild in self.bot.guilds:
channel = await self.get_channel(guild)
if channel:
self.bot.loop.create_task(self.modlog_purge(channel))
async def get_channel(guild):
# Whatever your logic for getting the Channel for a given guild is
async def modlog_purge(self, channel):
while True:
now = datetime.utcnow()
two_weeks_ago = now - timedelta(days=14)
await channel.purge(before=two_weeks_ago)
await asyncio.sleep(86400)
def setup(bot):
q = ModLog(bot)
bot.add_cog(q)
Here's how I would structure this (I'm not on a computer with discord.py at the moment, so there may be some errors). We have an on_ready event that kicks off background tasks for each server that has a channel we want to maintain (this could instead loop through a list of channels, or something similar).
The actual purge is all taken care of by the TextChannel.purge coroutine. We just pass it a datetime object, and it deletes 100 messages from before that date (this is configurable). It then sleeps for a day and repeats.

Categories

Resources