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
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)
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
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
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]