Example #1
0
async def search_and_select(ctx, list_to_search: list, query, key, cutoff=5, return_key=False, pm=False, message=None,
                            list_filter=None, selectkey=None, search_func=search, return_metadata=False):
    """
    Searches a list for an object matching the key, and prompts user to select on multiple matches.
    :param ctx: The context of the search.
    :param list_to_search: The list of objects to search.
    :param query: The value to search for.
    :param key: How to search - compares key(obj) to value
    :param cutoff: The cutoff percentage of fuzzy searches.
    :param return_key: Whether to return key(match) or match.
    :param pm: Whether to PM the user the select prompt.
    :param message: A message to add to the select prompt.
    :param list_filter: A filter to filter the list to search by.
    :param selectkey: If supplied, each option will display as selectkey(opt) in the select prompt.
    :param search_func: The function to use to search.
    :param return_metadata Whether to return a metadata object {num_options, chosen_index}.
    :return:
    """
    if list_filter:
        list_to_search = list(filter(list_filter, list_to_search))

    if search_func is None:
        search_func = search

    if asyncio.iscoroutinefunction(search_func):
        result = await search_func(list_to_search, query, key, cutoff, return_key)
    else:
        result = search_func(list_to_search, query, key, cutoff, return_key)

    if result is None:
        raise NoSelectionElements("No matches found.")
    strict = result[1]
    results = result[0]

    if strict:
        result = results
    else:
        if len(results) == 0:
            raise NoSelectionElements()

        first_result = results[0]
        confidence = fuzz.partial_ratio(key(first_result).lower(), query.lower())
        if len(results) == 1 and confidence > 75:
            result = first_result
        else:
            if selectkey:
                options = [(selectkey(r), r) for r in results]
            elif return_key:
                options = [(r, r) for r in results]
            else:
                options = [(key(r), r) for r in results]
            result = await get_selection(ctx, options, pm=pm, message=message, force_select=True)
    if not return_metadata:
        return result
    metadata = {
        "num_options": 1 if strict else len(results),
        "chosen_index": 0 if strict else results.index(result)
    }
    return result, metadata
Example #2
0
    async def select_bestiary(self, ctx, name):
        user_bestiaries = await self.bot.mdb.bestiaries.find({
            "owner":
            ctx.message.author.id
        }).to_list(None)

        if not user_bestiaries:
            raise NoBestiary()
        choices = []
        for bestiary in user_bestiaries:
            url = bestiary['critterdb_id']
            if bestiary['name'].lower() == name.lower():
                choices.append((bestiary, url))
            elif name.lower() in bestiary['name'].lower():
                choices.append((bestiary, url))

        if len(choices) > 1:
            choiceList = [(f"{c[0]['name']} (`{c[1]})`", c) for c in choices]

            result = await get_selection(ctx, choiceList, delete=True)
            if result is None:
                raise SelectionCancelled()

            bestiary = result[0]
            bestiary_url = result[1]
        elif len(choices) == 0:
            raise NoSelectionElements()
        else:
            bestiary = choices[0][0]
            bestiary_url = choices[0][1]
        return Bestiary.from_raw(bestiary_url, bestiary)
Example #3
0
    async def select_bestiary(self, ctx, name):
        user_bestiaries = self.bot.db.jget(
            ctx.message.author.id + '.bestiaries', None)

        if user_bestiaries is None:
            raise NoBestiary()
        choices = []
        for url, bestiary in user_bestiaries.items():
            if bestiary['name'].lower() == name.lower():
                choices.append((bestiary, url))
            elif name.lower() in bestiary['name'].lower():
                choices.append((bestiary, url))

        if len(choices) > 1:
            choiceList = [(f"{c[0]['name']} (`{c[1]})`", c) for c in choices]

            result = await get_selection(ctx, choiceList, delete=True)
            if result is None:
                raise SelectionCancelled()

            bestiary = result[0]
            bestiary_url = result[1]
        elif len(choices) == 0:
            raise NoSelectionElements()
        else:
            bestiary = choices[0][0]
            bestiary_url = choices[0][1]
        return Bestiary.from_raw(bestiary_url, bestiary)
async def search_and_select(ctx, list_to_search: list, value, key, cutoff=5, return_key=False, pm=False, message=None,
                            list_filter=None, selectkey=None, search_func=search, return_metadata=False):
    """
    Searches a list for an object matching the key, and prompts user to select on multiple matches.
    :param ctx: The context of the search.
    :param list_to_search: The list of objects to search.
    :param value: The value to search for.
    :param key: How to search - compares key(obj) to value
    :param cutoff: The cutoff percentage of fuzzy searches.
    :param return_key: Whether to return key(match) or match.
    :param pm: Whether to PM the user the select prompt.
    :param message: A message to add to the select prompt.
    :param list_filter: A filter to filter the list to search by.
    :param selectkey: If supplied, each option will display as selectkey(opt) in the select prompt.
    :param search_func: The function to use to search.
    :return:
    """
    if message:
        message = f"{message}\nOnly results from the 5e SRD are included."
    else:
        message = "Only results from the 5e SRD are included."
    if list_filter:
        list_to_search = list(filter(list_filter, list_to_search))

    if search_func is None:
        search_func = search

    if asyncio.iscoroutinefunction(search_func):
        result = await search_func(list_to_search, value, key, cutoff, return_key)
    else:
        result = search_func(list_to_search, value, key, cutoff, return_key)

    if result is None:
        raise NoSelectionElements("No matches found.")
    strict = result[1]
    results = result[0]

    if strict:
        result = results
    else:
        if len(results) == 1:
            result = results[0]
        else:
            if selectkey:
                result = await get_selection(ctx, [(selectkey(r), r) for r in results], pm=pm, message=message)
            elif return_key:
                result = await get_selection(ctx, [(r, r) for r in results], pm=pm, message=message)
            else:
                result = await get_selection(ctx, [(key(r), r) for r in results], pm=pm, message=message)
    if not return_metadata:
        return result
    metadata = {
        "num_options": 1 if strict else len(results),
        "chosen_index": 0 if strict else results.index(result)
    }
    return result, metadata
Example #5
0
async def search_and_select(ctx, list_to_search: list, value, key, cutoff=5, return_key=False, pm=False,
                            message=None, list_filter=None, srd=False, selectkey=None, search_func=search):
    """
    Searches a list for an object matching the key, and prompts user to select on multiple matches.
    :param ctx: The context of the search.
    :param list_to_search: The list of objects to search.
    :param value: The value to search for.
    :param key: How to search - compares key(obj) to value
    :param cutoff: The cutoff percentage of fuzzy searches.
    :param return_key: Whether to return key(match) or match.
    :param pm: Whether to PM the user the select prompt.
    :param message: A message to add to the select prompt.
    :param list_filter: A filter to filter the list to search by.
    :param srd: Whether to only search items that have a property ['srd'] set to true, or a search function.
    :param selectkey: If supplied, each option will display as selectkey(opt) in the select prompt.
    :param search_func: The function to use to search.
    :return:
    """
    if srd:
        if isinstance(srd, bool):
            srd = lambda e: e.get('srd')
        if list_filter:
            old = list_filter
            list_filter = lambda e: old(e) and srd(e)
        else:
            list_filter = srd
        message = "Only results from the 5e SRD are included."
    if list_filter:
        list_to_search = list(filter(list_filter, list_to_search))

    if search_func is None:
        search_func = search

    if asyncio.iscoroutinefunction(search_func):
        result = await search_func(list_to_search, value, key, cutoff, return_key)
    else:
        result = search_func(list_to_search, value, key, cutoff, return_key)

    if result is None:
        raise NoSelectionElements("No matches found.")
    strict = result[1]
    results = result[0]

    if strict:
        result = results
    else:
        if len(results) == 1:
            result = results[0]
        else:
            if selectkey:
                result = await get_selection(ctx, [(selectkey(r), r) for r in results], pm=pm, message=message)
            elif return_key:
                result = await get_selection(ctx, [(r, r) for r in results], pm=pm, message=message)
            else:
                result = await get_selection(ctx, [(key(r), r) for r in results], pm=pm, message=message)
    return result
Example #6
0
async def get_selection(ctx,
                        choices,
                        delete=True,
                        pm=False,
                        message=None,
                        force_select=False):
    """Returns the selected choice, or None. Choices should be a list of two-tuples of (name, choice).
    If delete is True, will delete the selection message and the response.
    If length of choices is 1, will return the only choice unless force_select is True.
    :raises NoSelectionElements if len(choices) is 0.
    :raises SelectionCancelled if selection is cancelled."""
    if len(choices) == 0:
        raise NoSelectionElements()
    elif len(choices) == 1 and not force_select:
        return choices[0][1]

    page = 0
    pages = paginate(choices, 10)
    m = None
    selectMsg = None

    def chk(msg):
        valid = [str(v) for v in range(1, len(choices) + 1)] + ["c", "n", "p"]
        return msg.author == ctx.author and msg.channel == ctx.channel and msg.content.lower(
        ) in valid

    for n in range(200):
        _choices = pages[page]
        names = [o[0] for o in _choices if o]
        embed = discord.Embed()
        embed.title = "Multiple Matches Found"
        selectStr = "Which one were you looking for? (Type the number or \"c\" to cancel)\n"
        if len(pages) > 1:
            selectStr += "`n` to go to the next page, or `p` for previous\n"
            embed.set_footer(text=f"Page {page + 1}/{len(pages)}")
        for i, r in enumerate(names):
            selectStr += f"**[{i + 1 + page * 10}]** - {r}\n"
        embed.description = selectStr
        embed.colour = random.randint(0, 0xffffff)
        if message:
            embed.add_field(name="Note", value=message, inline=False)
        if selectMsg:
            try:
                await selectMsg.delete()
            except:
                pass
        if not pm:
            selectMsg = await ctx.channel.send(embed=embed)
        else:
            embed.add_field(
                name="Instructions",
                value=
                "Type your response in the channel you called the command. This message was PMed to "
                "you to hide the monster name.",
                inline=False)
            selectMsg = await ctx.author.send(embed=embed)

        try:
            m = await ctx.bot.wait_for('message', timeout=30, check=chk)
        except asyncio.TimeoutError:
            m = None

        if m is None:
            break
        if m.content.lower() == 'n':
            if page + 1 < len(pages):
                page += 1
            else:
                await ctx.channel.send("You are already on the last page.")
        elif m.content.lower() == 'p':
            if page - 1 >= 0:
                page -= 1
            else:
                await ctx.channel.send("You are already on the first page.")
        else:
            break

    if delete and not pm:
        try:
            await selectMsg.delete()
            await m.delete()
        except:
            pass
    if m is None or m.content.lower() == "c":
        raise SelectionCancelled()
    return choices[int(m.content) - 1][1]