如何在不重新启动的情况下更改选择。不和谐.py

问题描述 投票:0回答:1

我的机器人基于经常更新的数据库。 其中一个团队的机器人提供了课程主题的选择(取自表格)。它似乎有效,但如果在机器人运行时对表进行更改,那么在不重新启动它的情况下,将不会出现新的选择选项

命令:

@app_commands.command()
@app_commands.default_permissions(administrator=True)
@app_commands.choices(lesson=lesson_choices())
async def create_or_update_mark(self, interaction: Interaction,
                                student: discord.Member,
                                lesson: Choice[str],
                                logiks: app_commands.Range[int, 0, 8]):
    with MarkRepository(student_id=student.id, lesson_title=lesson.value) as lr:
        lr.create_or_update(mark_dto=MarkCreateDTO(logiks=logiks))

    await interaction.response.send_message("logiks has been changed", ephemeral=True)

选择列表取自函数

lesson_choices

def lesson_choices() -> list[Choice[str]]:
    return [
        Choice(name=lesson.title, value=lesson.title)
        for lesson in LessonRepository.get_all_lessons()
    ]

看起来很明显装饰器决定了运行代码时的选择,但这不适合我。有什么方法可以帮助更改选择选项而不重新启动机器人吗?

我想使用

autocomplete
,但我什至没有基本的选项。也许我没有正确实现它

自动完成命令

@app_commands.command()
@app_commands.default_permissions(administrator=True)
@app_commands.autocomplete(lesson=lesson_autocomplete)
async def create_or_update_mark(self, interaction: Interaction,
                                student: discord.Member,
                                lesson: str,
                                logiks: app_commands.Range[int, 0, 8]):
    with MarkRepository(student_id=student.id, lesson_title=lesson) as lr:
        lr.create_or_update(mark_dto=MarkCreateDTO(logiks=logiks))

    await interaction.response.send_message("logiks has been changed", ephemeral=True)

lesson_autocomplete

async def lesson_autocomplete(interaction: Interaction, current: str) -> list[app_commands.Choice[str]]:
    lessons = [lesson_dto.title for lesson_dto in LessonRepository.get_all_lessons()]
    print(lessons)
    return [
        app_commands.Choice(name=lesson, value=lesson)
        for lesson in lessons if current.lower() in lesson.lower()
    ]
python discord discord.py
1个回答
0
投票

我发现您想要显示一个取决于数据库当前状态的动态选项列表,并且您在显示它们时遇到了一些问题。这是我注意到的,也是我建议您这样做的:

  1. 我发现联系您的数据库是一个阻塞功能(这意味着您正在使用非异步的数据库库)。为了解决这个问题,我强烈建议您切换到异步库,以便在您联系数据库获取信息/更新时不会阻塞事件循环。
  2. 我看到,在您尝试的自动完成中,您联系数据库来获取当前的课程标题 - 但您的列表理解逻辑有点偏离,您在自动完成回调中获取当前的课程列表,这很不幸,原因有以下几个:
    1. for lesson in lessons if current.lower() in lesson.lower()
      -> 这意味着自动完成功能仅在用户完整输入课程标题时才会显示某些内容,而不会根据他们当前输入的内容建议课程。
    2. LessonRepository.get_all_lessons()
      -> 每次用户键入新字符(或删除一个字符)时都会调用自动完成方法,因此该方法每秒被调用多次。您希望将其移至缓存,以便不需要进行任何此类调用。这很重要,因为您只有 3 秒钟的时间来响应自动完成的交互。

在将我们的逻辑移至Transformer(处理用于转换参数并提供自动完成支持的复杂逻辑)时进行这些建议的更改,将导致我们的代码发生以下更改:

from typing import TYPE_CHECKING, Dict, List, Any, Optional, TypeAlias, Union
import discord
from discord.ext import commands
from discord import app_commands
import difflib

GUILD_ID: TypeAlias = int
MEMBER_ID: TypeAlias = int
LESSON_ID: TypeAlias = int


# Some class definition that encapsulates the information from your database.
class Lesson:
    id: int
    title: str
    # ... some other data here


class MyBot(commands.Bot):
    if TYPE_CHECKING:
        some_function_for_loading_the_lessons_cache: Any

    def __init__(self, *args: Any, **kwargs: Any):
        super().__init__(*args, **kwargs)

        # We want to keep track of all the lessons your bot is 
        # holding here for each student in each guild. To achieve this, let's store a nested dictionary of 
        # {guild_id: {student_id: {lesson_name: lesson_information}}}. Of course, you can change this to your needs!
        self.lessons_cache: Dict[GUILD_ID, Dict[MEMBER_ID, Dict[LESSON_ID, Lesson]]] = {}

    async def setup_hook(self):
        # This async function is called once during your bots 
        # startup, and can be used to load your cache and sync your application commands.

        await self.tree.sync()
        await self.some_function_for_loading_the_lessons_cache()


class LessonTransformer(app_commands.Transformer):
    async def find_similar_lesson_titles(self, lessons: Dict[LESSON_ID, Lesson], title: str) -> Dict[LESSON_ID, Lesson]:
        # We can use difflib to find similar lesson titles, a standard python library utility!
        similar = difflib.get_close_matches(title, map(lambda l: l.title, lessons.values()), n=15, cutoff=1)
        return {lesson.id: lesson for lesson in lessons.values() if lesson.title in similar}

    async def autocomplete(self, interaction: discord.Interaction[MyBot], value: str, /) -> List[app_commands.Choice[str]]:
        # Prerequisite: We know that this command can ONLY be invoked in a server (guild)
        assert interaction.guild is not None

        # Check if the invoker of this command has already filled out the "student" argument of this command,
        # if they have, then we can use that information to filter to all lessons from only that student
        student: Optional[discord.Member] = interaction.namespace.get('student')
        if student is None:
            # This person skipped the student argument before going to the lesson argument, so we can only show them
            # so much.
            lessons: Dict[MEMBER_ID, Dict[LESSON_ID, Lesson]] = interaction.client.lessons_cache.get(
                interaction.guild.id, {}
            )

            # Flatten this dict into just a list of lessons
            flat_lessons: Dict[LESSON_ID, Lesson] = {
                s_lesson.id: s_lesson for student_lessons in lessons.values() for s_lesson in student_lessons.values()
            }

            similar_lessons = await self.find_similar_lesson_titles(flat_lessons, value)
        else:
            # We can get more specific!
            student_lessons: Dict[LESSON_ID, Lesson] = interaction.client.lessons_cache.get(interaction.guild.id, {}).get(
                student.id, {}
            )

            similar_lessons = await self.find_similar_lesson_titles(student_lessons, value)

        return [
            app_commands.Choice(name=lesson.title, value=str(lesson_id)) for lesson_id, lesson in similar_lessons.items()
        ]

    async def transform(self, interaction: discord.Interaction[MyBot], value: str, /) -> Union[Lesson, LESSON_ID]:
        # Prerequisite: We know that this command can ONLY be invoked in a server (guild)
        assert interaction.guild is not None

        # It should be noted that autocompletes are mere SUGGESTIONS, we need to make sure the user entered okay data.
        # Find the lesson from the cache, where value should be a lesson id
        if not value.isdigit():
            raise Exception("Invalid lesson id")  # Can raise better exception here

        lesson_id = int(value)
        student: Optional[discord.Member] = interaction.namespace.get('student')
        if student is None:
            return lesson_id  # We will check that this lesson ID is to the actual student later

        # We can get more specific!
        lesson = interaction.client.lessons_cache.get(interaction.guild.id, {}).get(student.id, {}).get(lesson_id)
        if lesson is None:
            raise Exception("Invalid lesson id")

        return lesson


class SomeCog(commands.Cog):
    @app_commands.command()
    @app_commands.guild_only()
    @app_commands.default_permissions(administrator=True)
    async def create_or_update_mark(
        self,
        interaction: discord.Interaction[MyBot],
        student: discord.Member,
        lesson: app_commands.Transform[Union[Lesson, LESSON_ID], LessonTransformer],
        logiks: app_commands.Range[int, 0, 8],
    ):
        # Prerequisite: We know that this command can ONLY be invoked in a server (guild)
        assert interaction.guild is not None

        reveal_type(lesson)  # Type of "lesson" is "Lesson | int"

        if isinstance(lesson, int):
            # This is a lesson ID, but we need to check that it belongs to the student (or complain)
            potential_lesson = interaction.client.lessons_cache.get(interaction.guild.id, {}).get(student.id, {}).get(lesson)
            if potential_lesson is None:
                raise Exception("Invalid lesson id")

            lesson = potential_lesson

        reveal_type(lesson)  # Type of "lesson" is "Lesson"

        # ... perform our operations with the lesson object here

所以,回顾一下这个潜在的例子:

  1. 将所有自动完成和转换逻辑移至 discord.py Transformer
  2. 不再依赖如此多的数据库调用,而是依赖缓存来进行自动完成/验证操作。
  3. 提供了一个面向前端的
    Lesson
    类型,表示存储在我们数据库中的数据。
  4. 更新了如何根据用户已输入的内容以及 if 他们已指定学生作为参数来显示自动完成建议。

通过这些更改,您的问题将得到解决。请注意,我尚未测试此代码,它只是“如何”重构您的设置的示例。如果您有任何疑问,请随时提问。另外,我强烈建议您查看 discord.py Discord Server,它有帮助渠道以加快响应速度。

© www.soinside.com 2019 - 2024. All rights reserved.