Esempio n. 1
0
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
Esempio n. 2
0
    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
Esempio n. 3
0
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]
Esempio n. 4
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
Esempio n. 7
0
    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
Esempio n. 8
0
    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)