При изменении переменной реакции из другой асинхронной функции

#python #discord.py #python-asyncio

Вопрос:

Я работаю над ботом discord для создания опросов, мой код здесь:

 @bot.command(name="newpoll")
async def new_poll(ctx, question, *options):
    if len(options) > 12:
        await ctx.send("You can have a maximum of 12 choices in your poll")

    else:
        embed = discord.Embed(title = "Poll",
                              description = question,
                              colour = discord.Colour.red())

        fields = [("Options", "n".join([f"{emotes[idx]} {option}" for idx, option in enumerate(options)]), False),
                  ("Instructions", "Please react in order to vote!", False)]

        for name, value, inline in fields:
            embed.add_field(name = name, value = value, inline = inline)

        embed = embed.add_field(name = "Total votes", value = 0, inline = False)
        message = await ctx.send(embed = embed)

        for emoji in emotes[:len(options)]:
            await message.add_reaction(emoji)

        message_win = await bot.get_channel(message.channel.id).fetch_message(message.id)
        total_votes = sum(reaction.count for reaction in message_win.reactions) - len(options)

        tv_embed = embed.set_field_at(2, name = "Total votes", value = total_votes, inline = False)
        await message.edit(embed = tv_embed)


 

Общее количество голосов хранится в total_votes переменной, которую я планирую отобразить, отредактировав встроенное сообщение, как в последних двух строках. Я хочу, чтобы это произошло в случае реакции. На этой ноте я попытался получить доступ к tv_embed коду, аналогичному этому:

 @bot.event
async def on_reaction_add(reaction, user):
    print("planning to ember.set_field_at(...) here")
    from_new_poll = await new_poll.tv_embed
    from_new_polll = await new_poll.message
    await from_new_polll.edit(embed = from_new_poll)
 

Но я получаю ошибку, подобную этой:

 planning to ember.set_field_at(...) here
Ignoring exception in on_reaction_add
Traceback (most recent call last):
  File "/home/vale/.virtualenvs/discord_env/lib/python3.9/site-packages/discord/client.py", line 343, in _run_event
    await coro(*args, **kwargs)
  File "/home/vale/Documents/arcane/e_project-kindling/Discord-Bot/main.py", line 108, in on_reaction_add
    from_new_poll = await new_poll.tv_embed
AttributeError: 'Command' object has no attribute 'tv_embed'

 

Не мог бы кто-нибудь, пожалуйста, любезно объяснить, что здесь происходит, заранее спасибо!

Ответ №1:

Простым методом может быть использование словаря или другой структуры данных, доступной, например, из любого места:

 # after your bot instantiation (or at __init__)
bot = commands.Bot(...)
bot.polls = {}

# In same file commands:
@bot.command()
async def test(ctx):
  print(bot.polls)

#In cogs:
self.bot.polls
ctx.bot.polls
 

Теперь в вашей команде опроса

 msg = await ctx.send(...)
author_polls = bot.polls.setdefault(author, [])
author_polls.append(msg)
 

И, наконец, в вашем обработчике событий

 msg = reaction.message
polls = bot.polls.get(msg.author)
if polls and msg in polls:
  ... # we know this reaction happened in a message that has a poll
 

В этом примере я просто сохранил исходное сообщение, но вы можете хранить там все, что вам нужно.

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

Ответ №2:

Вы не можете получить доступ к переменным, определенным в другой функции, они видны только внутри этой функции, поэтому нигде больше. new_poll.tv_embed по сути , это то же any_command.tv_embed самое, что и то, что tv_embed переменная не видна внешнему миру. Если вы об этом не знаете, я предлагаю вам scopes немного разобраться.

Если вы хотите получить к нему доступ, вы должны сохранить его где-то за пределами функции (например, в качестве переменной в вашем Cog файле или где-то еще в вашем .py файле).

Я должен отметить, что если вы пытаетесь сделать что-то подобное, есть более серьезная проблема, и общий дизайн вашего кода просто не работает. Так почти никогда не бывает. В вашем случае вы можете получить доступ к исходному встраиванию из reaction параметра и сразу же отредактировать его, не сохраняя его вообще нигде.

 @bot.event
async def on_reaction_add(reaction, user):
    # "reaction" has a "message" field that contains a reference to the message that the reaction was placed on
    message = reaction.message
    # A "Message" has a list of all embeds attached to this message, in your case I assume there's only one
    tv_embed = message.embeds[0]
 

Обратите внимание , что вышеприведенное ^ приведет к сбою для сообщений, в которых их нет embeds , это просто пример, чтобы дать вам представление здесь, а не полноценное решение. Вам понадобится кое if-statements -что здесь и там, чтобы проверить, следует ли редактировать это встраивание или нет, поэтому проверьте, есть ли вообще встраивание для начала. Этого должно быть достаточно, чтобы заставить вас двигаться дальше, и эти чеки должны быть выписаны вами.

Для получения дополнительной информации см. Документы API для on_reaction_add, discord.Реакция и разногласия.Сообщение.

Комментарии:

1. Большое спасибо, сэр, за подробный ответ, изученный scopes и cog , как вы предложили, и теперь, похоже, было бы лучше изучить и использовать cog вместо моего оригинального подхода.

2. Если это решило вашу проблему, не забудьте принять ответ.