pythonpython-3.xdiscordpycordyt-dlp

How to create search options


I'm currently working on having my Discord music bot create a keyword search menu, similar to the one depicted in the image. I'm utilizing the ytsearch from the ytdlp library to retrieve the top five results for a keyword search. The search process often takes longer than 3 seconds, resulting in unsuccessful responses. I'm curious if any of you have effective strategies to reduce this search time. enter image description here

I've attempted to reference this article for guidance. Although the code runs well, the issue of timeouts and failed responses persists, and I'm unsure how to address it.

Code snippet:

from discord.commands import slash_command
import yt_dlp as youtube_dl

youtube_dl.utils.bug_reports_message = lambda: ''

ytdlopts = {
    'format': 'bestaudio/best',
    'extractaudio': True,
    'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
    'restrictfilenames': True,
    'noplaylist': True,
    'nocheckcertificate': True,
    'ignoreerrors': False,
    'logtostderr': False,
    'quiet': True,
    'no_warnings': True,
    'dump_single_json': True,
    'default_search': 'auto',
    'postprocessors': [{"key" : "FFmpegExtractAudio", "preferredcodec" : "mp3", "preferredquality" : "256"}],
    'buffersize': 16777216,
    'source_address': '0.0.0.0'  # ipv6 addresses cause issues sometimes
}

ytdl = youtube_dl.YoutubeDL(ytdlopts)
async def song_search(self, ctx):
    options = []
    if ctx.value:
        to_run = partial(ytdl.extract_info, f"ytsearch5:{ctx.value}", download=False)
        info_dict = await asyncio.get_event_loop().run_in_executor(None, to_run)

        if 'entries' in info_dict:
            # Extract up to 5 items from the list of entries
            entries = info_dict['entries'][:5]
            for entry in entries:
                if 'title' in entry:
                    options.append(entry['title'])
            print(options)

    return options

@slash_command(name="play", description="play")
@option("url",description="url", autocomplete = song_search)
async def play_(self, ctx, *, url: str):
    await ctx.trigger_typing()

    vc = ctx.voice_client

    if not vc:
        await self.join_channel(ctx)

    await self.load_source_defer(ctx)

    player = self.get_player(ctx)

    source = await self.get_music_source(ctx, url)

    await player.queue.put(source)

The error that appears:

['Create Your Own Discord Bot in Python 3.10 Tutorial (2022 Edition)', 'The EASIEST Discord Chat Bot Tutorial On The Internet (Python 3.10) 2023', 'Code a Discord Bot with Python - Host for Free in the Cloud', 'Making a Discord Bot In Python (Part 1: Setup)', 'All you need to know about Buttons in Discord.py & Pycord | Ultimate Python Guide']
Task exception was never retrieved
future: <Task finished name='Task-44' coro=<ApplicationCommandMixin.on_application_command_auto_complete.<locals>.callback() done, defined at C:\Users\user\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\bot.py:853> exception=NotFound('404 Not Found (error code: 10062): Unknown interaction')>
Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\bot.py", line 856, in callback
    return await command.invoke_autocomplete_callback(ctx)
  File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\commands\core.py", line 1011, in invoke_autocomplete_callback
    return await ctx.interaction.response.send_autocomplete_result(
  File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\interactions.py", line 1017, in send_autocomplete_result
    await self._locked_response(
  File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\interactions.py", line 1090, in _locked_response
    await coro
  File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\webhook\async_.py", line 219, in request
    raise NotFound(response, data)
discord.errors.NotFound: 404 Not Found (error code: 10062): Unknown interaction

Solution

  • How to create options in pycord

    Here's an example that I have below:

    breed_types = ['German Shepherd', 'Bulldog', 'etc']  # list of breed types to autocomplete   
    dog_breeds = discord.Option(str, autocomplete=discord.utils.basic_autocomplete(breed_types),
                                required=False)  # create options using autocomplete util
    @bot.slash_command(name="dog", description="Random dog picture", )
    async def dog(ctx, breed: dog_breeds):
        await ctx.respond(breed)
    

    This is what it outputs:

    command lookup output

    Here's what happens when you input something:

    output when input


    So here's how it works.

    breed_types is a list of different options. Technically, it is a global variable because it's where it's placed scope-wise, but I just put it there for explanation purposes (you could use a function to generate it). Then that gets put into dog_breeds which is then put into autocomplete with the discord utils handler (I cover why I use this later). This allows for autocomplete input of anything in the list. However, it can let the user input anything outside of the list breed_types which can lead to errors.


    How to make the options non optional

    If we edit the following lines:

    def convert_list_to_options(input_list):
        return [discord.OptionChoice(name=option) for option in input_list]
    
    
    dog_breeds = discord.Option(str, choices=convert_list_to_options(breed_types),
                                required=False)  # create options using helper function
    

    I've created the above helper function. It converts a string list into discord.OptionChoice() for the attribute choices.

    The list of available choices for this option. Can be a list of values or OptionChoice objects (which represent a name:value pair). If provided, the input from the user must match one of the choices in the list.

    Documentation


    However, if you have a large number of options, the above method may not work. So, instead, you can add the following (to your slash command) and use the first method

    if breed is not None:
        if breed not in breed_types:
            return await ctx.respond('Option does not exist', ephemeral=True)
        else:
            (insert code here)
    

    Current documentation issues

    Currently, the documentation says the following:

    The autocomplete handler for the option. Accepts an iterable of str, a callable (sync or async) that takes a single argument of AutocompleteContext, or a coroutine. Must resolve to an iterable of str.

    Documentation

    The: "Must resolve to an iterable of str." is not true. This is currently a code or documentation issue that hasn't been resolved yet. In the meanwhile, use the discord.utils helper function provided above.


    In context with your problem

    I've attempted to reference this article for guidance. Although the code runs well, the issue of timeouts and failed responses persists, and I'm unsure how to address it.

    If your slash command itself is taking time you could use:

    await ctx.defer()
    

    Using the above at the beginning of a slash command will not time the command out.

    This is typically used when the interaction is acknowledged and a secondary action will be done later.

    Documentation


    However, if the slash option itself is timing out, you could try this in your song_search function:

    await ctx.interaction.response.defer()
    

    Looking through the documentation, interaction.response (looked into source too) is of class discord.InteractionResponse