Возможно ли реализовать очередь из отложенных новостей в Discord?

Вопрос: есть ли возможность сделать несколько параллельно работающих отложенных сообщений, которые не будут блокировать остальной функционал и будут отправляться в заданные дату и время?

Условия таковы, что необходимо задавать сообщение и время не через слэшовую def функцию, а через лс.

Например, сначала присылается текст, записывается в переменную, потом картинки, кнопка и т.д.

Реально ли при таких условиях сделать их несколько? Или из-за наличия переменных, которые каждый раз перезаписываются, это нужно переделывать?

Подобной возможности нет в самом Discord. Например, как в том же VK или Telegram.

Но есть tasks и библиотека shedule, которая позволяет задавать время. Однако tasks делает не в задаваемое время, а в каждое указанное время (каждые 5-10-15 секунд-минут-часов).

shedule хоть и может отправлять в определенное время every().day.at("время").do(job), но не может указать дату и блокирует весь остальной функционал, то есть можно запустить только одну задачу, без параллельного выполнения. Да, можно запустить проверку в отдельном треде через thread.start(), но опять же только на одно сообщение. Не очень удобно.

Также пробовал вариант со сравнением текущего времени и нужного, но там опять блокируется функционал на одной задаче.

Получилось прийти к вот такому варианту, но только для одного сообщения:

        @tasks.loop(seconds=5)
        async def late():
            global need_date
            if datetime.datetime.now().replace(microsecond=0)==need_date:
                        
                await msg.publish()
                        
if need_date!=None:
          late.start()

Ответы (2 шт):

Автор решения: Unclear

Возможно. Можно использовать asyncio.run_coroutine_threadsafe, если я правильно понял ваш вопрос.

@self.tree.command(name="send_after", description="Отправляет запланированное сообщение", guild=self.guild_object)
@discord.app_commands.describe(message = "Планируемое сообщение", minutes = "Через сколько минут отправить")
async def send_after(interaction: discord.Interaction, message: str, minutes: int):
    async def send_mes(channel: discord.TextChannel, message: str, minutes: int):
        start: datetime = datetime.datetime.now()
        while True:
            if datetime.datetime.now()-start >= datetime.timedelta(minutes=minutes):
                await channel.send(message)
                break
            await asyncio.sleep(0.5)
    await interaction.response.send_message(f"Ваше сообщение отправится через {minutes} мин.", ephemeral=True)
    asyncio.run_coroutine_threadsafe(send_mes(channel=interaction.channel, message=message, minutes=minutes), bot.loop)

введите сюда описание изображения

→ Ссылка
Автор решения: Unclear
from asyncio import run_coroutine_threadsafe, sleep
from datetime import datetime, timedelta

from discord.app_commands import CommandTree, describe
from discord import Interaction, Object, Client, Intents, TextChannel, Embed, Colour, Message

class Bot(Client):
    def __init__(self) -> None:
        Client.__init__(self, intents=Intents.all())
        GUILD_ID: int = 969610478139629568
        self.tree: CommandTree = CommandTree(self)
        self.guild_object: Object = Object(id=969610478139629568)
        self.posts: dict = {}
        
        @self.tree.command(name="create_post", description="Создаёт образец поста", guild=self.guild_object)
        @describe(title="Заголовок поста", colour="Цвет боковой линии (hex значение или 'rgb(r, g, b)')")
        async def create_post(interaction: Interaction, title: str, colour: str):
            embed: Embed = Embed(title=title, colour=Colour.from_str(colour))
            embed.set_author(name=interaction.user.display_name, icon_url=interaction.user.avatar.url)
            self.posts[interaction.user.id] = {"embed": embed}
            self.posts[interaction.user.id]["sample_post"] = interaction
            self.posts[interaction.user.id]["sample_post_id"] = interaction
            await interaction.response.send_message(embed=self.posts[interaction.user.id]["embed"], ephemeral=True)

        # Ничего умного не придумал, что можно в посты запихать
        @self.tree.command(name="add_text_to_post", description="Добавляет текст посту с отступом", guild=self.guild_object)
        @describe(text="Текст")
        async def add_text_to_post(interaction: Interaction, text: str):
            try:
                embed: Embed = self.posts[interaction.user.id]["embed"]
                if embed.description:
                    embed.description += f"\n{text}"
                else:
                    embed.description = f"{text}"
                interact: Interaction = self.posts[interaction.user.id]["sample_post"]
                await interact.edit_original_response(embed=embed)
                await interaction.response.send_message("Готово", ephemeral=True, delete_after=1)
            except:
                await interaction.response.send_message("Для начала вам необходимо создать пост. Для этого используйте ```/create_post```", ephemeral=True)
        
        @self.tree.command(name="send_post_after", description="Отправляет пост через указанное время", guild=self.guild_object)
        @describe(minutes="Через сколько минут отправить")
        async def send_post_after(interaction: Interaction, minutes: int):
            async def send_mes(channel: TextChannel, embed: Embed, minutes: int, user_id: int):
                start: datetime = datetime.now()
                while True:
                    if datetime.now()-start >= timedelta(minutes=minutes):
                        await channel.send(embed=embed)
                        self.posts.pop(user_id)
                        break
                    await sleep(0.5)
            await interaction.response.send_message(f"Ваш пост отправится через {minutes} мин.", ephemeral=True)
            run_coroutine_threadsafe(send_mes(channel=interaction.channel, embed=self.posts[interaction.user.id]["embed"], minutes=minutes, user_id=interaction.user.id), bot.loop)

    async def on_ready(self) -> None:
        await self.tree.sync(guild=self.guild_object)

bot = Bot()
bot.run(token=token, reconnect=True)
→ Ссылка