示例#1
0
文件: admin.py 项目: Zedd7/ZBot
    async def check_players_matching_name(context, members, app_id):
        """Check that all players have a name matching with a player in WoT."""
        nonmatching_members = []
        try:
            lower_case_matching_names = [
                name.lower() for name in wot_utils.get_players_info(
                    [member.display_name
                     for member in members], app_id).keys()
            ]
        except wot_utils.WargammingAPIError:
            await context.send(
                "L'API de Wargamming est incapable de vérifier les correspondances de pseudo pour le moment. 💩"
            )
        else:
            for member in members:
                # Parse member name as player name with optional clan tag
                result = utils.PLAYER_NAME_PATTERN.match(member.display_name)
                if result:  # Member name fits, check if it has a match
                    player_name = result.group(1)
                    if player_name.lower() not in lower_case_matching_names:
                        nonmatching_members.append(member)
                else:  # Member name malformed, reject
                    nonmatching_members.append(member)

            if nonmatching_members:
                for block in utils.make_message_blocks([
                        f"Le joueur {member.mention} n'a pas de correspondance de pseudo sur WoT."
                        for member in nonmatching_members
                ]):
                    await context.send(block)
            else:
                await context.send(
                    "Tous les joueurs ont une correspondance de pseudo sur WoT. 👌"
                )
        return nonmatching_members
示例#2
0
文件: admin.py 项目: Itstrike20/ZBot
 async def check_contacts_recruiting_permissions(context, contacts_by_clan,
                                                 app_id):
     """Check that all clan contacts still have recruiting permissions."""
     disbanded_members, demoted_members = [], []
     for clan_tag, contacts in contacts_by_clan.items():
         for member in contacts:
             if ' ' in member.display_name:  # Missing clan tag handled by Admin.check_contacts_clan_tag
                 player_name = member.display_name.split(' ')[0]
                 player_id, _ = await Stats.get_player_id(
                     player_name, app_id)
                 if player_id:  # Non-matching name handled by Admin.check_players_matching_name
                     clan_member_infos = await Stats.get_clan_member_infos(
                         player_id, app_id)
                     real_clan_tag = clan_member_infos and clan_member_infos[
                         'tag']
                     clan_position = clan_member_infos and clan_member_infos[
                         'position']
                     if not clan_member_infos or real_clan_tag != clan_tag.upper(
                     ):
                         disbanded_members.append((member, clan_tag))
                     elif clan_position not in [
                             "Commandant", "Commandant en second",
                             "Officier du personnel", "Recruteur"
                     ]:
                         demoted_members.append((member, real_clan_tag))
                         await context.send(
                             f"Le joueur {member.mention} n'a plus les permissions "
                             f"de recrutement au sein du clan [{real_clan_tag}]."
                         )
     if disbanded_members:
         for block in utils.make_message_blocks([
                 f"Le joueur {member.mention} a quitté le clan [{clan_tag}]."
                 for member, clan_tag in disbanded_members
         ]):
             await context.send(block)
     if demoted_members:
         for block in utils.make_message_blocks([
                 f"Le joueur {member.mention} n'a plus les permissions de recrutement au sein du clan [{real_clan_tag}]."
                 for member, real_clan_tag in demoted_members
         ]):
             await context.send(block)
     if not disbanded_members and not demoted_members:
         await context.send(
             "Tous les contacts de clan ont encore leurs permissions de recrutement. :ok_hand: "
         )
示例#3
0
 async def check_everyone_role(context, members):
     """Check that all members have at least one role."""
     # Ignore first role as it is @everyone
     if missing_role_members := list(filter(lambda m: len(m.roles) == 1, members)):
         for block in utils.make_message_blocks([
             f"Le joueur {member.mention} ne possède aucun rôle."
             for member in missing_role_members
         ]):
             await context.send(block)
示例#4
0
 async def check_clans_single_contact(context, contacts_by_clan):
     """Check that no clan has more than one contact."""
     if multiple_contact_clans := dict(filter(lambda i: len(i[1]) > 1, contacts_by_clan.items())):
         for block in utils.make_message_blocks([
             f"Le clan [{clan_tag}] est représenté par {len(contacts)} membres : "
             f"{', '.join([contact.mention for contact in contacts])}"
             for clan_tag, contacts in multiple_contact_clans.items()
         ]):
             await context.send(block)
示例#5
0
文件: admin.py 项目: Itstrike20/ZBot
 async def check_contacts_clan_tag(context, contacts):
     """Check that all contacts have a clan tag."""
     if missing_clan_tag_members := list(
             filter(lambda c: ' ' not in c.display_name, contacts)):
         for block in utils.make_message_blocks([
                 f"Le joueur {member.mention} n'arbore pas de tag de clan."
                 for member in missing_clan_tag_members
         ]):
             await context.send(block)
示例#6
0
文件: admin.py 项目: Zedd7/ZBot
 async def check_contacts_recruiting_permissions(context, contacts_by_clan,
                                                 app_id):
     """Check that all clan contacts still have recruiting permissions."""
     disbanded_members, demoted_members = [], []
     for clan_tag, contacts in contacts_by_clan.items():
         for member in contacts:
             result = utils.PLAYER_NAME_PATTERN.match(member.display_name)
             if result:  # Malformed member names handled by check_players_matching_name
                 player_name = result.group(1)
                 _, player_id = wot_utils.get_exact_player_info(
                     player_name, app_id)
                 if player_id:  # Non-matching name handled by Admin.check_players_matching_name
                     clan_member_infos = wot_utils.get_clan_member_infos(
                         player_id, app_id)
                     real_clan_tag = clan_member_infos and clan_member_infos[
                         'tag']
                     clan_position = clan_member_infos and clan_member_infos[
                         'position']
                     if not clan_member_infos or real_clan_tag != clan_tag.upper(
                     ):
                         disbanded_members.append((member, clan_tag))
                     elif clan_position not in [
                             "Commandant", "Commandant en second",
                             "Officier du personnel", "Recruteur"
                     ]:
                         demoted_members.append((member, real_clan_tag))
     if disbanded_members:
         for block in utils.make_message_blocks([
                 f"Le joueur {member.mention} a quitté le clan [{clan_tag}]."
                 for member, clan_tag in disbanded_members
         ]):
             await context.send(block)
     if demoted_members:
         for block in utils.make_message_blocks([
                 f"Le joueur {member.mention} n'a pas les permissions de recrutement au sein du "
                 f"clan [{real_clan_tag}]."
                 for member, real_clan_tag in demoted_members
         ]):
             await context.send(block)
     if not disbanded_members and not demoted_members:
         await context.send(
             "Tous les contacts de clan ont encore leurs permissions de recrutement. :ok_hand: "
         )
     return disbanded_members, demoted_members
示例#7
0
 async def check_authors_clan_contact_role(context, announces):
     """Check that all announce authors have the clan contact role."""
     if missing_clan_contact_role_announces := list(filter(
             lambda a: not checker.has_guild_role(context.guild, a.author, Stats.CLAN_CONTACT_ROLE_NAME), announces
     )):
         for block in utils.make_message_blocks([
             f"{announce.author.mention} ne possède pas le rôle @{Stats.CLAN_CONTACT_ROLE_NAME} nécessaire à la "
             f"publication d'une annonce : {announce.jump_url}" for announce in missing_clan_contact_role_announces
         ]):
             await context.send(block)
示例#8
0
 async def check_recruitment_announces_uniqueness(context, announces):
     """Check that no two recruitment announces have the same author."""
     announces_by_author = {}
     for announce in announces:
         announces_by_author.setdefault(announce.author, []).append(announce)
     if duplicate_announces_by_author := dict(filter(lambda i: len(i[1]) > 1, announces_by_author.items())):
         message_link_separator = "\n"
         for block in utils.make_message_blocks([
             f"Le joueur {author.mention} a publié {len(announces)} annonces : \n"
             f"{message_link_separator.join([announce.jump_url for announce in announces])}"
             for author, announces in duplicate_announces_by_author.items()
         ]):
             await context.send(block)
示例#9
0
文件: admin.py 项目: Itstrike20/ZBot
 async def check_players_unique_name(context, members):
     """Check that all players have a unique verified nickname."""
     members_by_name = {}
     for member in members:
         member_name = member.display_name.split(' ')[0]
         members_by_name.setdefault(member_name, []).append(member)
     if duplicate_name_members := dict(
             filter(lambda i: len(i[1]) > 1, members_by_name.items())):
         for block in utils.make_message_blocks([
                 f"Le pseudo vérifié **{member_name}** est utilisé par : "
                 f"{', '.join([member.mention for member in colliding_members])}"
                 for member_name, colliding_members in
                 duplicate_name_members.items()
         ]):
             await context.send(block)
示例#10
0
 async def check_players_unique_name(context, members):
     """Check that all players have a unique verified nickname."""
     members_by_name = {}
     for member in members:
         result = utils.PLAYER_NAME_PATTERN.match(member.display_name)
         if result:  # Malformed member names handled by check_players_matching_name
             member_name = result.group(1)
             members_by_name.setdefault(member_name, []).append(member)
     if duplicate_name_members := dict(filter(lambda i: len(i[1]) > 1, members_by_name.items())):
         for block in utils.make_message_blocks([
             f"Le pseudo vérifié **{member_name}** est utilisé par : "
             f"{', '.join([member.mention for member in colliding_members])}"
             for member_name, colliding_members in duplicate_name_members.items()
         ]):
             await context.send(block)
示例#11
0
 async def check_contacts_clan_tag(context, contacts):
     """Check that all contacts have a clan tag."""
     missing_clan_tag_members = []
     for contact in contacts:
         result = utils.PLAYER_NAME_PATTERN.match(contact.display_name)
         if not result or not result.group(3):
             missing_clan_tag_members.append(contact)
     if missing_clan_tag_members:
         for block in utils.make_message_blocks([
             f"Le joueur {member.mention} n'arbore pas de tag de clan."
             for member in missing_clan_tag_members
         ]):
             await context.send(block)
     else:
         await context.send("Tous les contacts de clan arborent un tag de clan. :ok_hand: ")
     return missing_clan_tag_members
示例#12
0
 async def check_everyone_clan_tag(context, members):
     """Check that no member has an unauthorized clan tag."""
     unauthorized_clan_tag_members = []
     for member in members:
         if re.search(r'[ ]*[\[{].{2,5}[\]}][ ]*', member.display_name) and \
                 not checker.has_role(member, Stats.CLAN_CONTACT_ROLE_NAME):
             unauthorized_clan_tag_members.append(member)
     if unauthorized_clan_tag_members:
         for block in utils.make_message_blocks([
             f"Le joueur {member.mention} arbore un tag de clan sans être contact de clan."
             for member in unauthorized_clan_tag_members
         ]):
             await context.send(block)
     else:
         await context.send("Aucun joueur n'arbore de tag de clan sans être contact de clan. :ok_hand: ")
     return unauthorized_clan_tag_members
示例#13
0
文件: admin.py 项目: Itstrike20/ZBot
    async def check_players_matching_name(context, members, app_id):
        """Check that all players have a matching player name on WoT."""
        def _batch(_array, _batch_size):
            """ Split an array into an iterable of constant-size batches. """
            for _i in range(0, len(_array), _batch_size):
                yield _array[_i:_i + _batch_size]

        unmatched_name_members = []
        for member_batch in _batch(members, Admin.BATCH_SIZE):
            # Replace forbidden characters in player names
            member_names = [
                re.sub(r'[^0-9a-zA-Z_]', r'',
                       member.display_name.split(' ')[0])
                for member in member_batch
            ]
            # Exclude fully non-matching (empty) names
            member_names = filter(lambda name: name != '', member_names)
            payload = {
                'application_id': app_id,
                'search': ','.join(member_names),
                'type': 'exact',
            }
            response = requests.get(
                'https://api.worldoftanks.eu/wot/account/list/',
                params=payload)
            response_content = response.json()
            matched_names = [
                player_data['nickname']
                for player_data in response_content['data']
            ] if response_content['status'] == 'ok' else []
            unmatched_name_members += list(
                filter(
                    lambda m: m.display_name.split(' ')[0].lower() not in
                    [matched_name.lower() for matched_name in matched_names],
                    member_batch))
        if unmatched_name_members:
            for block in utils.make_message_blocks([
                    f"Le joueur {member.mention} n'a pas de correspondance de pseudo sur WoT."
                    for member in unmatched_name_members
            ]):
                await context.send(block)
        else:
            await context.send(
                "Tous les joueurs ont une correspondance de pseudo sur WoT. :ok_hand: "
            )
示例#14
0
    async def check_recruitment_announces_timespan(context, channel, announces):
        """Check that no announce is re-posted before a given timespan."""
        zbot.db.update_recruitment_announces(await channel.history().flatten())

        # Get records of all deleted announces.
        # Still existing announce handled by Admin.check_recruitment_announces_uniqueness
        announces_data_by_author = {}
        for announce_data in zbot.db.load_recruitment_announces_data(
            query={'_id': {'$nin': list(map(lambda a: a.id, announces))}},
            order=[('time', -1)],
        ):
            announces_data_by_author.setdefault(announce_data['author'], []).append(
                {'time': announce_data['time'], 'message_id': announce_data['_id']}
            )

        # Find all existing announces that have the same author as a recent, but deleted, announce
        min_timespan = datetime.timedelta(
            # Apply a tolerance of 2 days for players interpreting the 30 days range as "one month"
            days=Admin.MIN_RECRUITMENT_ANNOUNCE_TIMESPAN - Admin.RECRUITMENT_ANNOUNCE_TIMESPAN_TOLERANCE
        )
        before_timespan_announces = []
        for announce in announces:
            for announce_data in announces_data_by_author.get(announce.author.id, []):
                previous_announce_time = announce_data['time']
                if previous_announce_time + min_timespan > announce.created_at:
                    before_timespan_announces.append((announce, previous_announce_time))
                    break  # Only check based on the most recent deleted announce
        if before_timespan_announces:
            for block in utils.make_message_blocks([
                f"L'annonce de {announce.author.mention} a été postée avant le délai minimum de "
                f"{Admin.MIN_RECRUITMENT_ANNOUNCE_TIMESPAN} jours (dernière publication le "
                f"{converter.humanize_datetime(previous_announce_time)}). : {announce.jump_url}"
                for announce, previous_announce_time in before_timespan_announces
            ]):
                await context.send(block)
        else:
            await context.send(
                f"Aucune annonce n'a été publiée avant le délai minimum de {Admin.MIN_RECRUITMENT_ANNOUNCE_TIMESPAN} "
                f"jours. :ok_hand: "
            )
        return before_timespan_announces
示例#15
0
 async def check_recruitment_announces_embeds(context, announces):
     """Check that no announce has an embed."""
     # Ignore line starting with code block statements
     discord_link_pattern = re.compile(r'discord(app)?\.(com|gg)')
     embedded_announces = []
     for announce in announces:
         # Include announces containing Discord links
         discord_link_count = len(discord_link_pattern.findall(announce.content))
         if announce.embeds or discord_link_count:
             embedded_announces.append((announce, len(announce.embeds) + discord_link_count))
     if embedded_announces:
         for block in utils.make_message_blocks([
             f"L'annonce de {announce.author.mention} contient {embed_count} embed(s) : {announce.jump_url}"
             for announce, embed_count in embedded_announces
         ]):
             await context.send(block)
     else:
         await context.send(
             f"Aucune annonce de recrutement ne contient d'embed. :ok_hand: "
         )
     return embedded_announces
示例#16
0
文件: admin.py 项目: Zedd7/ZBot
    async def check_recruitment_announces_length(context, announces):
        """Check that no recruitment announce is too long."""
        too_long_announces = []
        for announce in announces:
            if (apparent_length := Admin.compute_apparent_length(announce)
                ) > Admin.MAX_RECRUITMENT_ANNOUNCE_LENGTH:
                too_long_announces.append((announce, apparent_length))
        if too_long_announces:
            await context.send(
                f"Les critères suivants sont utilisés :\n"
                f"• Chaque ligne compte comme ayant au moins **{Admin.MIN_RECRUITMENT_LINE_LENGTH}** caractères.\n"
                f"• La longueur apparente maximale est de **{Admin.MAX_RECRUITMENT_ANNOUNCE_LENGTH}** caractères.\n_ _"
            )
            for block in utils.make_message_blocks([
                    f"L'annonce de {announce.author.mention} est d'une longueur apparente de **{apparent_length}** "
                    f"caractères (max {Admin.MAX_RECRUITMENT_ANNOUNCE_LENGTH}) : {announce.jump_url}"
                    for announce, apparent_length in too_long_announces
            ]):
                await context.send(block)
        else:
            await context.send(
                "Toutes les annonces de recrutement sont de longueur réglementaire. :ok_hand: "
            )
        return too_long_announces

    @staticmethod
    def compute_apparent_length(announce):
        return sum([
            max(len(line), Admin.MIN_RECRUITMENT_LINE_LENGTH)
            for line in announce.content.split('\n') if
            not re.match(r'^[^a-zA-Z0-9`]+```.*', line
示例#17
0
文件: admin.py 项目: Itstrike20/ZBot
                    max(len(line), Admin.MIN_RECRUITMENT_LINE_LENGTH)
                    for line in announce.content.split('\n')
                    if not code_block_pattern.match(
                        line
                    )  # Ignore line starting with code block statements
            ])) > Admin.MAX_RECRUITMENT_ANNOUNCE_LENGTH:
                too_long_announces.append((announce, apparent_length))
        if too_long_announces:
            await context.send(
                f"Les critères suivants sont utilisés :\n"
                f"• Chaque ligne compte comme ayant au moins **{Admin.MIN_RECRUITMENT_LINE_LENGTH}** caractères.\n"
                f"• La longueur apparente maximale est de **{Admin.MAX_RECRUITMENT_ANNOUNCE_LENGTH}** caractères.\n_ _"
            )
            for block in utils.make_message_blocks([
                    f"L'annonce de {announce.author.mention} est d'une longueur apparente de **{apparent_length}** "
                    f"caractères (max {Admin.MAX_RECRUITMENT_ANNOUNCE_LENGTH}) : {announce.jump_url}"
                    for announce, apparent_length in too_long_announces
            ]):
                await context.send(block)
        else:
            await context.send(
                "Toutes les annonces de recrutement sont de longueur réglementaire. :ok_hand: "
            )

    @staticmethod
    async def check_recruitment_announces_embeds(context, announces):
        """Check that no announce has an embed."""
        # Ignore line starting with code block statements
        discord_link_pattern = re.compile(r'discord(app)?\.(com|gg)')
        embedded_announces = []
        for announce in announces:
示例#18
0
文件: admin.py 项目: Zedd7/ZBot
    async def inspect_recruitment(self,
                                  context,
                                  member: typing.Union[discord.Member,
                                                       str] = None,
                                  *,
                                  options=''):
        """Post the status of the recruitment announces monitoring."""
        if isinstance(member,
                      str):  # Option mistakenly captured as member name
            options += f" {member}"
            member = None
        require_contact_role = not utils.is_option_enabled(options, 'all')
        recruitment_channel = self.guild.get_channel(
            self.RECRUITMENT_CHANNEL_ID)
        zbot.db.update_recruitment_announces(
            await recruitment_channel.history().flatten())

        # Get the record of each author's last announce (deleted or not)
        author_last_announce_data = {}
        for announce_data in zbot.db.load_recruitment_announces_data(
                query={'author': member.id} if member else {},
                order=[('time', -1)]):
            # Associate each author with his/her last announce data
            if announce_data['author'] not in author_last_announce_data:
                author_last_announce_data[announce_data['author']] = {
                    'last_announce_time': announce_data['time'],
                    'message_id': announce_data['_id']
                }

        # Enhance announces data with additional information
        min_timespan = datetime.timedelta(
            # Apply a tolerance of 2 days for players interpreting the 30 days range as "one month".
            # This is a subtraction because the resulting value is the number of days to wait before posting again.
            days=self.MIN_RECRUITMENT_ANNOUNCE_TIMESPAN -
            self.RECRUITMENT_ANNOUNCE_TIMESPAN_TOLERANCE)
        today = utils.bot_tz_now()
        for author_id, announce_data in author_last_announce_data.items():
            last_announce_time_localized = converter.to_utc(
                announce_data['last_announce_time'])
            next_announce_time_localized = last_announce_time_localized + min_timespan
            author_last_announce_data[author_id] = {
                'last_announce_time': last_announce_time_localized,
                'next_announce_time': next_announce_time_localized,
                'is_time_elapsed': next_announce_time_localized <= today
            }

        # Bind the member to the announce data, filter, and order by date asc
        member_announce_data = {
            self.guild.get_member(author_id): _
            for author_id, _ in author_last_announce_data.items()
        }
        filtered_member_announce_data = {
            author: _
            for author, _ in member_announce_data.items()
            if author is not None  # Still member of the server
            and
            not checker.has_any_mod_role(context, author, print_error=False
                                         )  # Ignore moderation messages
            and (not require_contact_role
                 or checker.has_role(author, Stats.CLAN_CONTACT_ROLE_NAME))
        }
        ordered_member_announce_data = sorted(
            filtered_member_announce_data.items(),
            key=lambda elem: elem[1]['last_announce_time'])

        # Post the status of announces data
        if ordered_member_announce_data:
            await context.send("Statut du suivi des annonces de recrutement :")
            for block in utils.make_message_blocks([
                    f"• {author.mention} : {converter.to_human_format(announce_data['last_announce_time'])} "
                    +
                ("✅" if announce_data['is_time_elapsed'] else
                 f"⏳ (→ {converter.to_human_format(announce_data['next_announce_time'])})"
                 ) for author, announce_data in ordered_member_announce_data
            ]):
                await context.send(block)
        else:
            await context.send("Aucun suivi enregistré.")
示例#19
0
文件: bot.py 项目: Zedd7/ZBot
            if changelog := self.get_changelog(current_version):
                date_iso, description, _ = changelog
                embed.add_field(name="Date",
                                value=converter.to_human_format(
                                    converter.to_datetime(date_iso)))
                embed.add_field(name="Description",
                                value=description,
                                inline=False)
            embed.set_footer(
                text="Utilisez +changelog <version> pour plus d'informations")
            await context.send(embed=embed)
        else:  # Display all versions
            versions_data = self.get_versions_data()
            for block in utils.make_message_blocks([
                    f"v**{version}** - {converter.to_human_format(versions_data[version]['date'])}\n"
                    f"> {versions_data[version]['description']}"
                    for version in sorted(versions_data, reverse=True)
            ]):
                await context.send(block)

    @commands.command(
        name='changelog',
        aliases=['patchnote'],
        usage="[version]",
        brief="Affiche les changements d'une version du bot",
        help=
        "Si aucune version n'est spécifiée (au format `a.b.c` avec `a`, `b` et `c` des valeurs entières), la "
        "version courante est utilisée. Les changements affichées ne concernent que les améliorations "
        "fonctionnelles et les corrections de bug.",
        ignore_extra=False,
    )