Beispiel #1
0
def create_ability_queries(add_query: typing.Callable):
    abilities = get_name_map(data.Ability)
    generic_ability_group_map = collections.defaultdict(list)
    for name, ab in abilities.items():
        # this will need to be addressed if different abilities have the same name
        add_query(name, ab, True)
        generic_ability_group_map[ab.generic_name].append(ab)

    generic_ability_sources_map = collections.defaultdict(dict)
    entities = [
        item
        for sublist in map(lambda c: c.get_all(), (data.Adventurer,
                                                   data.Dragon, data.Wyrmprint,
                                                   data.Weapon))
        for item in sublist
    ]

    for ent in entities:
        # assumes that abilities unique to an entity won't be present on that entity in multiple slots
        ent_ability_groups = ent.get_abilities()
        for ab_group in ent_ability_groups:
            for ab in ab_group:
                generic_ability_sources_map[ab.generic_name][
                    ent.get_key()] = ab

    generic_descriptions = config.get_global("ability_disambiguation")
    for gen_name, source_map in generic_ability_sources_map.items():
        if len(source_map) == 1:
            ab_highest = list(source_map.values())[0]
            add_query(gen_name, ab_highest, True)
            if gen_name in generic_descriptions:
                logger.warning(
                    f"Disambiguation specified for unique generic ability {gen_name}"
                )
        else:
            ab_list = generic_ability_group_map[gen_name]
            if len(ab_list) == 1:
                add_query(gen_name, ab_list[0], True)
            else:
                if gen_name in generic_descriptions:
                    desc = f"*{generic_descriptions[gen_name]}*"
                else:
                    desc = ""
                    logger.warning(
                        f"No description for common generic ability {gen_name}"
                    )

                names = natsort.natsorted(set(ab.name for ab in ab_list),
                                          reverse=True)
                if len(names) > 15:
                    names = names[:15] + ["..."]

                name_list = "\n".join(names)
                embed = discord.Embed(
                    title=f"{gen_name} (Disambiguation)",
                    description=f"{desc}\n\n{name_list}".strip(),
                    color=0xFF7000)

                add_query(gen_name, embed)
Beispiel #2
0
async def on_init(discord_client):
    global query_config
    query_config = config.get_global("custom_query")

    build_matcher()

    hook.Hook.get("on_message").attach(scan_for_query)
    hook.Hook.get("data_downloaded").attach(build_matcher)
Beispiel #3
0
def get_emote(name) -> str:
    """
    Gets the emote string for the given emote name. Emote names are case-insensitive.
    An object may be passed in as an emote, where str(name) will be used as the name of the emote.
    :param name: name of the emote
    :return: emote string for the given name
    """
    name = str(name).lower()
    emote_map = config.get_global("emotes")
    return emote_map[name] if name in emote_map else ""
Beispiel #4
0
def check_command_permissions(message, level) -> bool:
    """
    Determines whether a command of the given level can be used, given the context of a sent message.
    :param message: context message used to determine whether the command can be used
    :param level: level of command to use, may be one of "public", "admin", or "owner"
    :return: True if the command can be used, False otherwise
    """
    if level == "public":
        return True
    elif level == "admin":
        return isinstance(message.channel, discord.abc.GuildChannel) and message.author.guild_permissions.manage_guild
    elif level == "owner":
        return message.author.id == config.get_global("general")["owner_id"]
Beispiel #5
0
def configure_discord(client: discord.Client):
    channel = client.get_channel(
        config.get_global("general")["logging_channel"])
    if channel is None:
        raise ValueError("Logging channel not found")

    discord_handler = DiscordHandler(channel)
    discord_handler.setLevel(logging.ERROR)
    discord_handler.setFormatter(
        logging.Formatter(
            fmt="Date: %(asctime)s\nLevel: %(levelname)s\n%(message)s",
            datefmt="%Y-%m-%d %H:%M:%S"))
    logging.getLogger().addHandler(discord_handler)
Beispiel #6
0
def generate_queries():
    queries = {}
    hdt_data = config.get_global("hdt_data")
    alias_lists = config.get_global("hdt_alias")

    for hdt, dragon_info in hdt_data.items():
        dragon_name = dragon_info["dragon_name"]
        dragon_element = data.Element(dragon_info["dragon_element"])
        resist_wyrmprint_name = dragon_info["wyrmprint"]
        aliases = alias_lists[hdt] + [hdt]
        for difficulty, difficulty_info in dragon_info["fight_info"].items():
            embed = discord.Embed(
                title=f"High {dragon_name} {difficulty.title()} HP Requirement",
                description=generate_description(resist_wyrmprint_name,
                                                 difficulty_info),
                color=dragon_element.get_colour())

            for alias in aliases:
                queries[f"{difficulty} {alias}"] = embed
                queries[f"{difficulty[0]}{alias}"] = embed
                queries[f"{alias} {difficulty}"] = embed

    return queries
Beispiel #7
0
    def update_data(cls):
        default_showcase = data.Showcase()
        default_showcase.name = "none"
        cls.default_showcase = NormalSS(default_showcase)

        new_cache = {}
        showcase_blacklist = config.get_global(
            "general")["summonable_showcase_blacklist"]
        for sc in data.Showcase.get_all():
            if sc.name not in showcase_blacklist:
                if sc.type == "Regular" and not sc.name.startswith(
                        "Dragon Special"):
                    new_cache[
                        sc.get_key()] = SimShowcaseFactory.create_showcase(sc)

        matcher_additions = new_cache.copy()
        aliases = config.get_global(f"query_alias/showcase")
        for alias, expanded in aliases.items():
            try:
                matcher_additions[alias] = new_cache[expanded]
            except KeyError:
                continue

        # add fuzzy matching names
        name_replacements = {
            "part one": "part 1",
            "part two": "part 2",
        }
        matcher = fuzzy_match.Matcher(lambda s: 1 + 0.5 * len(s))
        for sc_name, sim_sc in matcher_additions.items():
            matcher.add(sc_name, sim_sc)
            for old, new in name_replacements.items():
                if old in sc_name:
                    matcher.add(sc_name.replace(old, new), sim_sc)

        cls.showcases = new_cache
        cls.showcase_matcher = matcher
Beispiel #8
0
async def on_message(message: discord.Message):
    if message.author.bot or message.author.id in config.get_global(
            "user_blacklist"):
        return

    if (isinstance(message.channel, discord.DMChannel)
            or isinstance(message.channel, discord.GroupChannel) or
            message.channel.permissions_for(message.guild.me).send_messages):
        prefix = config.get_prefix(message.guild)
        if message.content.startswith(prefix):
            if not initialised:
                await message.channel.send(
                    "I've only just woken up, give me a second please!")
                return
            command = message.content[len(prefix):].split(
                " ")[0].lower()  # just command text
            args = message.content[len(prefix) + len(command) + 1:]
            if Hook.exists("public!" +
                           command) and util.check_command_permissions(
                               message, "public"):
                await Hook.get("public!" + command)(message, args)
            elif Hook.exists("admin!" +
                             command) and util.check_command_permissions(
                                 message, "admin"):
                await Hook.get("admin!" + command)(message, args)
            elif Hook.exists("owner!" +
                             command) and util.check_command_permissions(
                                 message, "owner"):
                await Hook.get("owner!" + command)(message, args)
            else:
                await message.channel.send(
                    "I don't know that command, sorry! Use the `help` command for a list of commands."
                )
        else:
            if not initialised:
                return
            await Hook.get("on_message")(message)
            if discord.utils.find(lambda m: m.id == client.user.id,
                                  message.mentions) is not None:
                await Hook.get("on_mention")(message)
            if isinstance(message.channel, discord.abc.PrivateChannel):
                await Hook.get("on_message_private")(message)
Beispiel #9
0
def get_name_map(entity_type: typing.Type[data.abc.Entity]):
    name_map = {str(e).lower(): e for e in entity_type.get_all()}
    entity_type_name = entity_type.__name__.lower()
    try:
        aliases = config.get_global(f"query_alias/{entity_type_name}")
    except FileNotFoundError:
        return name_map

    # add all aliases for this entity type
    for alias, expanded in aliases.items():
        try:
            resolved_entity = name_map[expanded]
        except KeyError:
            logger.warning(
                f"Alias '{alias}' = '{expanded}' doesn't resolve to any {entity_type_name}"
            )
            continue

        if alias in name_map:
            logger.warning(
                f"Alias '{alias}' already exists as {entity_type_name} name")
        name_map[alias] = resolved_entity

    return name_map
Beispiel #10
0
async def resist_search(message, args):
    """
    Searches for combinations of elemental affinity and affliction resistance.
    **Affliction keywords:** *poison, burning/burn, freezing/freeze, paralysis, blind, stun, curses/curse, bog, sleep*
    **Element keywords:** *flame/fire, water, wind, light, shadow/dark*
    If no keywords of a category are used, all keywords of that category are included. (E.g. if no element is specified, adventurers of all elements will be included)
    If you use more than one keyword from a single category, or omit a category, all other categories must have *exactly* one keyword specified.
    You may also add a number as the minimum percentage resist value to display. If multiple are provided, only the first will be used.

    Shortcut keywords are available for Advanced Dragon Trials:
    **hms** (High Midgardsormr) = *flame*, *stun*, *100*
    **hbh** (High Brunhilda) = *water*, *burning*, *100*
    **hmc** (High Mercury) = *wind*, *bog*, *100*
    **hjp** (High Jupiter) = *dark*, *paralysis*, *100*
    **hzd** (High Zodiark) = *light*, *curse*, *100*

    """
    args = args.lower()
    alias_lists = config.get_global("hdt_alias")
    for hdt, aliases in alias_lists.items():
        for alias in aliases:
            args = args.replace(alias, hdt)

    arg_list = list(map(str.strip, args.split(" ")))

    shortcuts = {
        "hbh": (data.Element.WATER, data.Resistance.BURN, 100),
        "hmc": (data.Element.WIND, data.Resistance.BOG, 100),
        "hms": (data.Element.FIRE, data.Resistance.STUN, 100),
        "hjp": (data.Element.DARK, data.Resistance.PARALYSIS, 100),
        "hzd": (data.Element.LIGHT, data.Resistance.CURSE, 100),
    }

    specified_elements = set()
    specified_resists = set()
    specified_threshold = -1

    # collect criteria
    for arg in arg_list:
        if arg == "":
            continue

        if arg in shortcuts:
            specified_elements.add(shortcuts[arg][0])
            specified_resists.add(shortcuts[arg][1])
            if specified_threshold == -1:
                specified_threshold = shortcuts[arg][2]
            continue

        try:
            specified_elements.add(data.Element(arg.capitalize()))
            continue
        except ValueError:
            pass

        try:
            specified_resists.add(data.Resistance(arg.capitalize()))
            continue
        except ValueError:
            pass

        # find threshold
        if specified_threshold == -1:
            threshold_match = re.findall(r"^(\d+)%?$", arg)
            if len(threshold_match) > 0 and 0 <= int(
                    threshold_match[0]) <= 100:
                specified_threshold = int(threshold_match[0])

    # if one is empty, use all
    if len(specified_elements) == 0:
        element_list = list(data.Element)
    else:
        element_list = specified_elements
    if len(specified_resists) == 0:
        resist_list = list(data.Resistance)
    else:
        resist_list = specified_resists

    # too broad or no keywords
    if len(specified_elements) == 0 and len(specified_resists) == 0:
        await message.channel.send(
            "I need something to work with, give me an element or resistance!")
        return
    elif len(resist_list) > 1 and len(element_list) > 1:
        await message.channel.send("Too much! Try narrowing down your search.")
        return

    result_lines = []
    emote = util.get_emote
    for res in resist_list:
        match_list = []
        for el in element_list:
            match_list.extend(
                list(
                    filter(None, map(data.Adventurer.find,
                                     resist_data[res][el]))))

        if len(
                match_list
        ) > 0 or res in specified_resists:  # include resists specifically searched for
            result_lines.append(f"{emote(res)} **{res} Resistance**")
        else:
            continue

        if len(match_list) == 0:
            result_lines.append(f"{emote('blank')*2} *No results.* ")
            continue

        # sorting
        match_list.sort(key=lambda a: a.full_name)  # by name
        match_list.sort(key=lambda a: a.element.value)  # by element
        match_list.sort(key=lambda a: a.rarity, reverse=True)  # by rarity
        match_list.sort(key=lambda a: resist_data[res][a.element][a.get_key()],
                        reverse=True)  # by percent

        # formatting
        current_resist = 101
        for adv in match_list:
            adv_res_percent = resist_data[res][adv.element][adv.get_key()]
            if adv_res_percent < specified_threshold:
                break

            if adv_res_percent < current_resist:
                current_resist = adv_res_percent
                result_lines.append(
                    f"{emote('blank')*2} **{adv_res_percent}%**")

            result_lines.append(
                f"{emote('rarity' + str(adv.rarity))}{emote(adv.element)} {adv.full_name}"
            )

    if len(result_lines) == 0:
        await message.channel.send(
            "I didn't find anything! Maybe there's nobody that matches your search?"
        )

    await util.send_long_message_in_sections(message.channel, result_lines)
Beispiel #11
0
    "VII": 7,
    "VIII": 8,
    "IX": 9,
    "X": 10,
}

asyncio.get_event_loop().run_until_complete(data.update_repositories())
abilities = {str(e).lower(): e for e in data.Ability.get_all()}
generic_ability_map = collections.defaultdict(list)
for name, ab in abilities.items():
    generic_ability_map[ab.generic_name].append(ab)

numeric_ex = re.compile(r"(.+) \+(\d+)%?")
roman_ex = re.compile(r"(.+) ([IVX]+)")

generic_descriptions = config.get_global("ability_disambiguation")
non_matching = []
for gen_name, ab_list in generic_ability_map.items():
    if len(ab_list) > 1:
        if gen_name not in generic_descriptions:
            if any(numeric_ex.match(ab.name) for ab in ab_list):
                ab_max = None
                ab_max_value = 0
                for ab in ab_list:
                    m = numeric_ex.match(ab.name)
                    if m and int(m.group(2)) > ab_max_value:
                        ab_max = ab
                        ab_max_value = int(m.group(2))
                print(description_line(gen_name, ab_max.description))
            elif any(roman_ex.match(ab.name) for ab in ab_list):
                ab_max = None
Beispiel #12
0
async def on_init(discord_client):
    global client, statuses
    client = discord_client
    statuses = list(zip(*(config.get_global("status"))))
    change_status("my favourites!")
Beispiel #13
0
async def check_news(reschedule):
    if reschedule:
        now = datetime.datetime.utcnow()
        next_check = now.replace(minute=5 * math.floor(now.minute / 5),
                                 second=1,
                                 microsecond=0) + datetime.timedelta(minutes=5)
        time_delta = (next_check - now).total_seconds()
        asyncio.get_event_loop().call_later(
            time_delta, lambda: asyncio.ensure_future(check_news(True)))
        if time_delta < 240:
            return

    async with aiohttp.ClientSession() as session:
        list_base_url = "https://dragalialost.com/api/index.php?" \
                        "format=json&type=information&action=information_list&lang=en_us&priority_lower_than="

        response_json = await get_api_json_response(session, list_base_url)
        if not response_json:
            logger.error("Could not retrieve article list")
            return

        query_result = response_json["data"]

        wc = config.get_writeable()
        stored_ids = wc.news_ids
        stored_time = wc.news_update_time
        if not stored_ids and not stored_time:
            wc.news_ids = query_result["new_article_list"]
            wc.news_update_time = math.ceil(time.time())
            logger.info(
                f"Regenerated article history, time = {wc.news_update_time}, IDs = {wc.news_ids}"
            )
            await config.set_writeable(wc)
            return

        # new posts
        new_article_ids = list(reversed(query_result["new_article_list"]))
        new_article_ids = [i for i in new_article_ids if i not in stored_ids]
        new_stored_ids = query_result["new_article_list"].copy() or stored_ids

        # updated posts
        updated_articles = sorted(query_result["update_article_list"],
                                  key=lambda d: d["update_time"])
        updated_article_ids = [
            d["id"] for d in updated_articles if d["update_time"] > stored_time
        ]
        updated_article_ids = [
            i for i in updated_article_ids if i not in new_article_ids
        ]
        new_stored_time = updated_articles[-1][
            "update_time"] if updated_articles else stored_time

        # filter blacklisted articles
        article_blacklist = config.get_global(
            "general")["news_article_blacklist"]
        news_articles = [
            i for i in new_article_ids + updated_article_ids
            if i not in article_blacklist
        ]

        if len(news_articles) >= 10:
            # too many news items, post a generic notification
            embeds = [
                discord.Embed(
                    title="New news posts are available",
                    url="https://dragalialost.com/en/news/",
                    description=
                    f"{len(news_articles)} new news posts are available! Click the link above to read them.",
                    color=get_news_colour()).set_author(
                        name="Dragalia Lost News", icon_url=news_icon)
            ]
        else:
            # generate embeds from articles
            embeds = []
            for article_id in news_articles:
                article_embed = await get_article_embed(
                    session, article_id, article_id in updated_article_ids)
                if article_embed:
                    embeds.append(article_embed)

    # update config
    wc.news_ids = new_stored_ids
    wc.news_update_time = new_stored_time
    if wc.news_ids != stored_ids or wc.news_update_time != stored_time:
        await config.set_writeable(wc)

    # post articles
    if embeds:
        for guild in client.guilds:
            active_channel = config.get_guild(guild).active_channel
            channel = guild.get_channel(active_channel)
            if channel is not None and channel.permissions_for(
                    guild.me).send_messages:
                asyncio.ensure_future(
                    exec_in_order([channel.send(embed=e) for e in embeds]))