async def findMonster1(dgcog, query): query = rmdiacritics(query) nm, err, debug_info = await _findMonster(dgcog, query) monster_no = nm.monster_id if nm else -1 historic_lookups[query] = monster_no json.dump(historic_lookups, open(historic_lookups_file_path, "w+")) m = dgcog.get_monster(nm.monster_id) if nm else None return m, err, debug_info
async def findMonster2(self, query, server_filter=ServerFilter.any): query = rmdiacritics(query) nm, err, debug_info = await self._findMonster2(query, server_filter) monster_no = nm.monster_id if nm else -1 self.historic_lookups_id2[query] = monster_no json.dump(self.historic_lookups_id2, open(self.historic_lookups_file_path_id2, "w+")) m = self.get_monster_by_id(nm.monster_id) if nm else None return m, err, debug_info
async def _findMonster3(dgcog, query) -> Optional["MonsterModel"]: await dgcog.wait_until_ready() query = rmdiacritics(query).lower().replace(",", "") tokenized_query = query.split() mw_tokenized_query = find_monster.merge_multi_word_tokens(tokenized_query, dgcog.index2.multi_word_tokens) return max( await find_monster_search(tokenized_query, dgcog), await find_monster_search(mw_tokenized_query, dgcog) if tokenized_query != mw_tokenized_query else (None, {}), key=lambda t: t[1].get(t[0], MonsterMatch()).score )[0]
async def find_monster_debug(self, query: str) -> \ Tuple[Optional[MonsterModel], MatchMap, Set[MonsterModel], int]: """Get debug info from a search. This gives info that isn't necessary for non-debug functions. Consider using findmonster or findmonsters instead.""" await self.dgcog.wait_until_ready() query = rmdiacritics(query).lower().replace(",", "") tokenized_query = query.split() mw_tokenized_query = self._merge_multi_word_tokens(tokenized_query) best_monster, matches_dict, valid_monsters = max( await self._find_monster_search(tokenized_query), await self._find_monster_search(mw_tokenized_query) if tokenized_query != mw_tokenized_query else (None, {}, set()), key=lambda t: t[1].get(t[0], MonsterMatch()).score) self.dgcog.historic_lookups[ query] = best_monster.monster_id if best_monster else -1 json.dump(self.dgcog.historic_lookups, open(self.dgcog.historic_lookups_file_path, "w+")) return best_monster, matches_dict, valid_monsters, 0
def find_monster2(self, query): """Search with alternative method for resolving prefixes. Implements the lookup for id2, where you are allowed to specify multiple prefixes for a card. All prefixes are required to be exactly matched by the card. Follows a similar logic to the regular id but after each check, will remove any potential match that doesn't contain every single specified prefix. """ query = tsutils.rmdiacritics(query).lower().strip() # id search if query.isdigit(): m = self.monster_no_na_to_named_monster.get(int(query)) if m is None: return None, 'Looks like a monster ID but was not found', None else: return m, None, "ID lookup" # handle exact nickname match if query in self.all_entries: return self.all_entries[query], None, "Exact nickname" contains_ja = tsutils.contains_ja(query) if len(query) < 2 and contains_ja: return None, 'Japanese queries must be at least 2 characters', None elif len(query) < 4 and not contains_ja: return None, 'Your query must be at least 4 letters', None # we want to look up only the main part of the query, and then verify that each result has the prefixes # so break up the query into an array of prefixes, and a string (new_query) that will be the lookup query_prefixes = [] parts_of_query = query.split() new_query = '' for i, part in enumerate(parts_of_query): if part in self.all_prefixes: query_prefixes.append(part) else: new_query = ' '.join(parts_of_query[i:]) break # if we don't have any prefixes, then default to using the regular id lookup if len(query_prefixes) < 1: return self.find_monster(query) matches = PotentialMatches() # prefix search for ids, take max id for nickname, m in self.all_entries.items(): if query.endswith("base {}".format(m.monster_id)): matches.add( find_first(lambda mo: m.base_monster_no == mo.monster_id, self.all_entries.values())) matches.update_list(query_prefixes) # first try to get matches from nicknames for nickname, m in self.all_entries.items(): if new_query in nickname: matches.add(m) matches.update_list(query_prefixes) # if we don't have any candidates yet, pick a new method if not matches.length(): # try matching on exact names next for nickname, m in self.all_en_name_to_monsters.items(): if new_query in m.name_en.lower( ) or new_query in m.name_ja.lower(): matches.add(m) matches.update_list(query_prefixes) # check for exact match on pantheon name but only if needed if not matches.length(): for pantheon in self.all_pantheon_nicknames: if new_query == pantheon.lower(): matches.get_monsters_from_potential_pantheon_match( pantheon, self.pantheon_nick_to_name, self.pantheons) matches.update_list(query_prefixes) # check for any match on pantheon name, again but only if needed if not matches.length(): for pantheon in self.all_pantheon_nicknames: if new_query in pantheon.lower(): matches.get_monsters_from_potential_pantheon_match( pantheon, self.pantheon_nick_to_name, self.pantheons) matches.update_list(query_prefixes) if matches.length(): return matches.pick_best_monster(), None, None return None, "Could not find a match for: " + query, None
def find_monster(self, query): query = tsutils.rmdiacritics(query).lower().strip() # id search if query.isdigit(): m = self.monster_no_na_to_named_monster.get(int(query)) if m is None: return None, 'Looks like a monster ID but was not found', None else: return m, None, "ID lookup" # special handling for na/jp # TODO: need to handle na_only? # handle exact nickname match if query in self.all_entries: return self.all_entries[query], None, "Exact nickname" contains_ja = tsutils.contains_ja(query) if len(query) < 2 and contains_ja: return None, 'Japanese queries must be at least 2 characters', None elif len(query) < 4 and not contains_ja: return None, 'Your query must be at least 4 letters', None # TODO: this should be a length-limited priority queue matches = set() # prefix search for ids, take max id for nickname, m in self.all_entries.items(): if query.endswith("base {}".format(m.monster_id)): matches.add( find_first(lambda mo: m.base_monster_no == mo.monster_id, self.all_entries.values())) if len(matches): return self.pick_best_monster( matches), None, "Base ID match, max of 1".format() # prefix search for nicknames, space-preceeded, take max id for nickname, m in self.all_entries.items(): if nickname.startswith(query + ' '): matches.add(m) if len(matches): return self.pick_best_monster( matches), None, "Space nickname prefix, max of {}".format( len(matches)) # prefix search for nicknames, take max id for nickname, m in self.all_entries.items(): if nickname.startswith(query): matches.add(m) if len(matches): all_names = ",".join(map(lambda x: x.name_en, matches)) return self.pick_best_monster( matches ), None, "Nickname prefix, max of {}, matches=({})".format( len(matches), all_names) # prefix search for full name, take max id for nickname, m in self.all_entries.items(): if m.name_en.lower().startswith( query) or m.name_ja.lower().startswith(query): matches.add(m) if len(matches): return self.pick_best_monster( matches), None, "Full name, max of {}".format(len(matches)) # for nicknames with 2 names, prefix search 2nd word, take max id if query in self.two_word_entries: return self.two_word_entries[ query], None, "Second-word nickname prefix, max of {}".format( len(matches)) # TODO: refactor 2nd search characteristcs for 2nd word # full name contains on nickname, take max id for nickname, m in self.all_entries.items(): if query in m.name_en.lower() or query in m.name_ja.lower(): matches.add(m) if len(matches): return self.pick_best_monster( matches), None, 'Nickname contains nickname match ({})'.format( len(matches)) # No decent matches. Try near hits on nickname instead matches = difflib.get_close_matches(query, self.all_entries.keys(), n=1, cutoff=.8) if len(matches): match = matches[0] return self.all_entries[ match], None, 'Close nickname match ({})'.format(match) # Still no decent matches. Try near hits on full name instead matches = difflib.get_close_matches( query, self.all_en_name_to_monsters.keys(), n=1, cutoff=.9) if len(matches): match = matches[0] return self.all_en_name_to_monsters[ match], None, 'Close name match ({})'.format(match) # About to give up, try matching all words matches = set() for nickname, m in self.all_entries.items(): if (all(map(lambda x: x in m.name_en.lower(), query.split())) or all(map(lambda x: x in m.name_ja.lower(), query.split()))): matches.add(m) if len(matches): return self.pick_best_monster( matches ), None, 'All word match on full name, max of {}'.format( len(matches)) # couldn't find anything return None, "Could not find a match for: " + query, None
def find_monster(self, query): query = tsutils.rmdiacritics(query).lower().strip() # id search if query.isdigit(): m = self.monster_no_na_to_named_monster.get(int(query)) if m is None: return None, 'Looks like a monster ID but was not found', None else: return m, None, "ID lookup" # special handling for na/jp # TODO: need to handle na_only? err = None if query in self.bad_entries: err = ( "It looks like this query won't be supported soon!" f" Please start using `^id {self.bad_entries[query]}` instead, with a space." " For more information, check out:" " <https://github.com/TsubakiBotPad/pad-cogs/wiki/%5Eid-user-guide>" " or join the Tsubaki server (<https://discord.gg/QCRxNtC>)." " To start using the beta now, type `^idset beta y`!") # handle exact nickname match if query in self.all_entries: return self.all_entries[query], err, "Exact nickname" contains_ja = tsutils.contains_ja(query) if len(query) < 2 and contains_ja: return None, 'Japanese queries must be at least 2 characters', None elif len(query) < 4 and not contains_ja: return None, 'Your query must be at least 4 letters', None # TODO: this should be a length-limited priority queue matches = set() # prefix search for ids, take max id for nickname, m in self.all_entries.items(): if query.endswith("base {}".format(m.monster_id)): matches.add( find_first(lambda mo: m.base_monster_no == mo.monster_id, self.all_entries.values())) if len(matches): return self.pick_best_monster( matches), None, "Base ID match, max of 1".format() # prefix search for nicknames, space-preceeded, take max id for nickname, m in self.all_entries.items(): if nickname.startswith(query + ' '): matches.add(m) if len(matches): return self.pick_best_monster( matches), err, "Space nickname prefix, max of {}".format( len(matches)) # prefix search for nicknames, take max id for nickname, m in self.all_entries.items(): if nickname.startswith(query): matches.add(m) if len(matches): all_names = ",".join(map(lambda x: x.name_en, matches)) return self.pick_best_monster( matches ), err, "Nickname prefix, max of {}, matches=({})".format( len(matches), all_names) # prefix search for full name, take max id for nickname, m in self.all_entries.items(): if m.name_en.lower().startswith( query) or m.name_ja.lower().startswith(query): matches.add(m) if len(matches): return self.pick_best_monster( matches), err, "Full name, max of {}".format(len(matches)) # for nicknames with 2 names, prefix search 2nd word, take max id if query in self.two_word_entries: return self.two_word_entries[ query], err, "Second-word nickname prefix, max of {}".format( len(matches)) # TODO: refactor 2nd search characteristcs for 2nd word # full name contains on nickname, take max id for nickname, m in self.all_entries.items(): if query in m.name_en.lower() or query in m.name_ja.lower(): matches.add(m) if len(matches): return self.pick_best_monster( matches), err, 'Nickname contains nickname match ({})'.format( len(matches)) # No decent matches. Try near hits on nickname instead matches = difflib.get_close_matches(query, self.all_entries.keys(), n=1, cutoff=.8) if len(matches): match = matches[0] return self.all_entries[ match], err, 'Close nickname match ({})'.format(match) # Still no decent matches. Try near hits on full name instead matches = difflib.get_close_matches( query, self.all_en_name_to_monsters.keys(), n=1, cutoff=.9) if len(matches): match = matches[0] return self.all_en_name_to_monsters[ match], err, 'Close name match ({})'.format(match) # About to give up, try matching all words matches = set() for nickname, m in self.all_entries.items(): if (all(map(lambda x: x in m.name_en.lower(), query.split())) or all(map(lambda x: x in m.name_ja.lower(), query.split()))): matches.add(m) if len(matches): return self.pick_best_monster( matches), err, 'All word match on full name, max of {}'.format( len(matches)) # couldn't find anything return None, "Could not find a match for: " + query, None
def __init__(self, **m): self.monster_id = m['monster_id'] self.monster_no = self.monster_id self.monster_no_jp = m['monster_no_jp'] self.monster_no_na = m['monster_no_na'] self.monster_no_kr = m['monster_no_kr'] # these things are literally named backwards atm self.awakenings = sorted(m['awakenings'], key=lambda a: a.order_idx) self.superawakening_count = sum(int(a.is_super) for a in self.awakenings) self.leader_skill: LeaderSkillModel = m['leader_skill'] self.leader_skill_id = self.leader_skill.leader_skill_id if self.leader_skill else None self.active_skill: ActiveSkillModel = m['active_skill'] self.active_skill_id = self.active_skill.active_skill_id if self.active_skill else None self.series = m['series'] self.series_id = m['series_id'] self.name_ja = m['name_ja'] self.name_ko = m['name_ko'] self.name_en = m['name_en'] self.roma_subname = None if self.name_en == self.name_ja: self.roma_subname = self.make_roma_subname(self.name_ja) else: # Remove annoying stuff from NA names, like Jörmungandr self.name_en = tsutils.rmdiacritics(self.name_en) self.name_en_override = m['name_en_override'] self.name_en = self.name_en_override or self.name_en self.type1 = enum_or_none(MonsterType, m['type_1_id']) self.type2 = enum_or_none(MonsterType, m['type_2_id']) self.type3 = enum_or_none(MonsterType, m['type_3_id']) self.types = list(filter(None, [self.type1, self.type2, self.type3])) self.rarity = m['rarity'] self.is_farmable = m['is_farmable'] self.in_rem = m['in_rem'] self.in_pem = m['in_pem'] self.in_mpshop = m['buy_mp'] is not None self.buy_mp = m['buy_mp'] self.sell_gold = m['sell_gold'] self.sell_mp = m['sell_mp'] self.reg_date = m['reg_date'] self.on_jp = m['on_jp'] self.on_na = m['on_na'] self.on_kr = m['on_kr'] self.attr1 = enum_or_none(Attribute, m['attribute_1_id'], Attribute.Nil) self.attr2 = enum_or_none(Attribute, m['attribute_2_id'], Attribute.Nil) self.is_equip = any([x.awoken_skill_id == 49 for x in self.awakenings]) self.is_inheritable = m['is_inheritable'] self.evo_gem_id = m['evo_gem_id'] self.orb_skin_id = m['orb_skin_id'] self.cost = m['cost'] self.exp = m['exp'] self.fodder_exp = m['fodder_exp'] self.level = m['level'] self.limit_mult = m['limit_mult'] self.latent_slots = m['latent_slots'] self.hp_max = m['hp_max'] self.hp_min = m['hp_min'] self.hp_scale = m['hp_scale'] self.atk_max = m['atk_max'] self.atk_min = m['atk_min'] self.atk_scale = m['atk_scale'] self.rcv_max = m['rcv_max'] self.rcv_min = m['rcv_min'] self.rcv_scale = m['rcv_scale'] self.stat_values = { 'hp': {'min': self.hp_min, 'max': self.hp_max, 'scale': self.hp_scale}, 'atk': {'min': self.atk_min, 'max': self.atk_max, 'scale': self.atk_scale}, 'rcv': {'min': self.rcv_min, 'max': self.rcv_max, 'scale': self.rcv_scale} } self.voice_id_jp = m['voice_id_jp'] self.voice_id_na = m['voice_id_na'] self.pronunciation_ja = m['pronunciation_ja'] self.has_animation = m['has_animation'] self.has_hqimage = m['has_hqimage'] self.search = MonsterSearchHelper(self)