Exemplo n.º 1
0
    async def close_draft_emoji_added(self, member, channel, message):
        announce_message_link = f'https://discord.com/channels/{member.guild.id}/{channel.id}/{message.id}'
        logger.debug(f'Draft close reaction added by {member.name} to draft announcement {announce_message_link}')
        grad_role = discord.utils.get(member.guild.roles, name=grad_role_name)
        novas_role = discord.utils.get(member.guild.roles, name=novas_role_name)

        try:
            await message.remove_reaction(self.emoji_draft_close, member)
            logger.debug(f'Removing {self.emoji_draft_close} reaction placed by {member.name} on message {message.id}')
        except discord.DiscordException as e:
            logger.warn(f'Unable to remove reaction in close_draft_emoji_added(): {e}')

        if not settings.is_mod(member):
            return

        draft_config = self.get_draft_config(member.guild.id)

        if draft_config['draft_open']:
            new_message = f'~~{message.content}~~\n{self.draft_closed_message}'
            log_message = f'Draft status closed by <@{member.id}>'
            draft_config['draft_open'] = False
        else:
            new_message = self.draft_open_format_str.format(grad_role.mention, novas_role.mention, draft_config['draft_message'])
            log_message = f'Draft status opened by <@{member.id}>'
            draft_config['draft_open'] = True

        self.save_draft_config(member.guild.id, draft_config)
        await self.send_to_log_channel(member.guild, log_message)
        try:
            await message.edit(content=new_message)
        except discord.DiscordException as e:
            return logger.error(f'Could not update message in close_draft_emoji_added: {e}')
Exemplo n.º 2
0
    async def conclude_draft_emoji_added(self, member, channel, message):
        announce_message_link = f'https://discord.com/channels/{member.guild.id}/{channel.id}/{message.id}'
        logger.debug(f'Conclude close reaction added by {member.name} to draft announcement {announce_message_link}')

        try:
            await message.remove_reaction(self.emoji_draft_conclude, member)
            logger.debug(f'Removing {self.emoji_draft_conclude} reaction placed by {member.name} on message {message.id}')
        except discord.DiscordException as e:
            logger.warn(f'Unable to remove reaction in conclude_draft_emoji_added(): {e}')

        if not settings.is_mod(member):
            return

        free_agent_role = discord.utils.get(member.guild.roles, name=free_agent_role_name)
        draftable_role = discord.utils.get(member.guild.roles, name=draftable_role_name)

        confirm_message = await channel.send(f'<@{member.id}>, react below to confirm the conclusion of the current draft. '
            f'{len(free_agent_role.members)} members will lose the **{free_agent_role_name}** role and {len(draftable_role.members)} members with the **{draftable_role_name}** role will lose that role and become the current crop with the **{free_agent_role_name}** role.\n'
            '*If you do not react within 30 seconds the draft will remain open.*', delete_after=35)
        await confirm_message.add_reaction('✅')

        logger.debug('waiting for reaction confirmation')

        def check(reaction, user):
            e = str(reaction.emoji)
            return ((user == member) and (reaction.message.id == confirm_message.id) and e == '✅')

        try:
            reaction, user = await self.bot.wait_for('reaction_add', check=check, timeout=33)

        except asyncio.TimeoutError:
            logger.debug(f'No reaction to confirmation message.')
            return

        result_message_list = [f'Draft successfully closed by <@{member.id}>']
        self.announcement_message = None
        await message.delete()

        async with channel.typing():
            for old_free_agent in free_agent_role.members:
                await old_free_agent.remove_roles(free_agent_role, reason='Purging old free agents')
                logger.debug(f'Removing free agent role from {old_free_agent.name}')

                result_message_list.append(f'Removing free agent role from {old_free_agent.name} <@{old_free_agent.id}>')

            for new_free_agent in draftable_role.members:
                await new_free_agent.add_roles(free_agent_role, reason='New crop of free agents')
                logger.debug(f'Adding free agent role to {new_free_agent.name}')

                await new_free_agent.remove_roles(draftable_role, reason='Purging old free agents')
                logger.debug(f'Removing draftable role from {new_free_agent.name}')

                result_message_list.append(f'Removing draftable role from and applying free agent role to {new_free_agent.name} <@{new_free_agent.id}>')

        await self.send_to_log_channel(member.guild, '\n'.join(result_message_list))
Exemplo n.º 3
0
    async def ping(self, ctx, *, args=None):
        """ Ping everyone in one of your games with a message

         **Examples**
        `[p]ping 100 I won't be able to take my turn today` - Send a message to everyone in game 100
        `[p]ping This game is amazing!` - You can omit the game ID if you send the command from a game-specific channel

        See `[p]help pingall` for a command to ping ALL incomplete games simultaneously.

        """

        usage = (f'**Example usage:** `{ctx.prefix}ping 100 Here\'s a nice note for everyone in game 100.`\n'
                    'You can also omit the game ID if you use the command from a game-specific channel.')
        if not args:
            ctx.command.reset_cooldown(ctx)
            return await ctx.send(usage)

        if settings.is_mod(ctx):
            ctx.command.reset_cooldown(ctx)

        args = args.split()
        try:
            game_id = int(args[0])
            message = ' '.join(args[1:])
        except ValueError:
            game_id = None
            message = ' '.join(args)

        inferred_game = None
        if not game_id:
            try:
                inferred_game = models.Game.by_channel_id(chan_id=ctx.message.channel.id)
            except exceptions.TooManyMatches:
                logger.error(f'More than one game with matching channel {ctx.message.channel.id}')
                return await ctx.send('Error looking up game based on current channel - please contact the bot owner.')
            except exceptions.NoMatches:
                ctx.command.reset_cooldown(ctx)
                return await ctx.send(f'Game ID was not included. {usage}')
            logger.debug(f'Inferring game {inferred_game.id} from ping command used in channel {ctx.message.channel.id}')

        if not message:
            ctx.command.reset_cooldown(ctx)
            return await ctx.send(f'Message was not included. {usage}')

        if ctx.message.attachments:
            attachment_urls = '\n'.join([attachment.url for attachment in ctx.message.attachments])
            message += f'\n{attachment_urls}'

        message = utilities.escape_role_mentions(message)

        if inferred_game:
            game = inferred_game
        else:
            game = await PolyGame().convert(ctx, int(game_id), allow_cross_guild=True)

        if not game.player(discord_id=ctx.author.id) and not settings.is_staff(ctx):
            ctx.command.reset_cooldown(ctx)
            return await ctx.send(f'You are not a player in game {game.id}')

        permitted_channels = settings.guild_setting(game.guild_id, 'bot_channels')
        permitted_channels_private = []
        if settings.guild_setting(game.guild_id, 'game_channel_categories'):
            if game.game_chan:
                permitted_channels = [game.game_chan] + permitted_channels
            if game.smallest_team() > 1:
                permitted_channels_private = [gs.team_chan for gs in game.gamesides]
                permitted_channels = permitted_channels_private + permitted_channels
                # allows ping command to be used in private team channels - only if there is no solo squad in the game which would mean they cant see the message
                # this also adjusts where the @Mention is placed (sent to all team channels instead of simply in the ctx.channel)
            elif ctx.channel.id in [gs.team_chan for gs in game.gamesides]:
                channel_tags = [f'<#{chan_id}>' for chan_id in permitted_channels]
                ctx.command.reset_cooldown(ctx)
                return await ctx.send(f'This command cannot be used in this channel because there is at least one solo player without access to a team channel.\n'
                    f'Permitted channels: {" ".join(channel_tags)}')

        if ctx.channel.id not in permitted_channels and ctx.channel.id not in settings.guild_setting(game.guild_id, 'bot_channels_private'):
            channel_tags = [f'<#{chan_id}>' for chan_id in permitted_channels]
            ctx.command.reset_cooldown(ctx)
            return await ctx.send(f'This command can not be used in this channel. Permitted channels: {" ".join(channel_tags)}')

        player_mentions = [f'<@{l.player.discord_member.discord_id}>' for l in game.lineup]
        full_message = f'Message from {ctx.author.mention} (**{ctx.author.name}**) regarding game {game.id} **{game.name}**:\n*{message}*'

        if ctx.channel.id in permitted_channels_private:
            logger.debug(f'Ping triggered in private channel {ctx.channel.id}')
            await game.update_squad_channels(self.bot.guilds, game.guild_id, message=f'{full_message}\n{" ".join(player_mentions)}')
        else:
            logger.debug(f'Ping triggered in non-private channel {ctx.channel.id}')
            await game.update_squad_channels(self.bot.guilds, ctx.guild.id, message=full_message)
            await ctx.send(f'{full_message}\n{" ".join(player_mentions)}')
Exemplo n.º 4
0
    async def ping(self, ctx, *, args=''):
        """ Ping everyone in one of your games with a message

         **Examples**
        `[p]ping 100 I won't be able to take my turn today` - Send a message to everyone in game 100
        `[p]ping This game is amazing!` - You can omit the game ID if you send the command from a game-specific channel

        See `[p]help pingall` for a command to ping ALL incomplete games simultaneously.

        """

        usage = (
            f'**Example usage:** `{ctx.prefix}ping 100 Here\'s a nice note for everyone in game 100.`\n'
            'You can also omit the game ID if you use the command from a game-specific channel.'
        )

        if ctx.message.attachments:
            attachment_urls = '\n'.join(
                [attachment.url for attachment in ctx.message.attachments])
            args += f'\n{attachment_urls}'

        if not args:
            ctx.command.reset_cooldown(ctx)
            return await ctx.send(usage)

        if settings.is_mod(ctx.author):
            ctx.command.reset_cooldown(ctx)

        args = args.split()
        try:
            game_id = int(args[0])
            message = ' '.join(args[1:])
        except ValueError:
            game_id = None
            message = ' '.join(args)

        # TODO:  should prioritize inferred game above an integer. currently something like '$ping 1 city island plz restart'
        # will try to ping game ID #1 even if done within a game channel

        inferred_game = None
        if not game_id:
            try:
                inferred_game = models.Game.by_channel_id(
                    chan_id=ctx.message.channel.id)
            except exceptions.TooManyMatches:
                logger.error(
                    f'More than one game with matching channel {ctx.message.channel.id}'
                )
                return await ctx.send(
                    'Error looking up game based on current channel - please contact the bot owner.'
                )
            except exceptions.NoMatches:
                ctx.command.reset_cooldown(ctx)
                logger.debug('Could not infer game from current channel.')
                return await ctx.send(f'Game ID was not included. {usage}')
            logger.debug(
                f'Inferring game {inferred_game.id} from ping command used in channel {ctx.message.channel.id}'
            )

        if not message:
            ctx.command.reset_cooldown(ctx)
            return await ctx.send(f'Message was not included. {usage}')

        message = utilities.escape_role_mentions(message)

        if inferred_game:
            game = inferred_game
        else:
            game = await PolyGame().convert(ctx,
                                            int(game_id),
                                            allow_cross_guild=True)

        if not game.player(discord_id=ctx.author.id) and not settings.is_staff(
                ctx.author):
            ctx.command.reset_cooldown(ctx)
            return await ctx.send(f'You are not a player in game {game.id}')

        permitted_channels = settings.guild_setting(game.guild_id,
                                                    'bot_channels').copy()
        if game.game_chan:
            permitted_channels.append(game.game_chan)

        game_player_ids = [
            l.player.discord_member.discord_id for l in game.lineup
        ]
        game_members = [ctx.guild.get_member(p_id) for p_id in game_player_ids]
        player_mentions = [f'<@{p_id}>' for p_id in game_player_ids]

        game_channels = [gs.team_chan for gs in game.gamesides]
        game_channels = [chan for chan in game_channels
                         if chan]  # remove Nones

        mention_players_in_current_channel = True  # False when done from game channel, True otherwise

        if ctx.channel.id in game_channels and len(game_channels) >= len(
                game.gamesides):
            logger.debug(
                'Allowing ping since it is within a game channel, and all sides have a game channel'
            )
            mention_players_in_current_channel = False
        elif settings.is_mod(
                ctx.author) and len(game_channels) >= len(game.gamesides):
            logger.debug(
                'Allowing ping since it is from a mod and all sides have a game channel'
            )
            mention_players_in_current_channel = False
        elif None not in game_members and all(
                ctx.channel.permissions_for(member).read_messages
                for member in game_members):
            logger.debug(
                'Allowing ping since all members have read access to current channel'
            )
            mention_players_in_current_channel = True
        elif ctx.channel.id in permitted_channels:
            logger.debug(
                'Allowing ping since it is a bot channel or central game channel'
            )
            mention_players_in_current_channel = True
        else:
            logger.debug(f'Not allowing ping in {ctx.channel.id}')
            if len(game_channels) >= len(game.gamesides):
                permitted_channels = game_channels + permitted_channels

            channel_tags = [f'<#{chan_id}>' for chan_id in permitted_channels]
            ctx.command.reset_cooldown(ctx)

            if len(game_channels) < len(game.gamesides):
                error_str = 'Not all sides have access to a private channel. '
            else:
                error_str = ''

            return await ctx.send(
                f'This command can not be used in this channel. {error_str}Permitted channels: {" ".join(channel_tags)}'
            )

        full_message = f'Message from **{ctx.author.display_name}** regarding game {game.id} **{game.name}**:\n*{message}*'
        models.GameLog.write(
            game_id=game,
            guild_id=game.guild_id,
            message=
            f'{models.GameLog.member_string(ctx.author)} pinged the game with message: *{discord.utils.escape_markdown(message)}*'
        )

        try:
            if mention_players_in_current_channel:
                logger.debug(
                    f'Ping triggered in non-private channel {ctx.channel.id}')
                await game.update_squad_channels(self.bot.guilds,
                                                 ctx.guild.id,
                                                 message=full_message,
                                                 suppress_errors=True)
                await ctx.send(f'{full_message}\n{" ".join(player_mentions)}')
            else:
                logger.debug(
                    f'Ping triggered in private channel {ctx.channel.id}')
                await game.update_squad_channels(self.bot.guilds,
                                                 game.guild_id,
                                                 message=f'{full_message}',
                                                 suppress_errors=False,
                                                 include_message_mentions=True)
                if ctx.channel.id not in game_channels:
                    await ctx.send(
                        f'Sending ping to game channels:\n{full_message}')
        except exceptions.CheckFailedError as e:
            channel_tags = [f'<#{chan_id}>' for chan_id in permitted_channels]
            return await ctx.send(
                f'{e}\nTry sending `{ctx.prefix}ping` from a public channel that all members can view: {" ".join(channel_tags)}'
            )
Exemplo n.º 5
0
    async def gamelog(self, ctx, *, search_term: str = None):
        """ *Staff*: Lists log entries related to a particular game

         **Examples**
        `[p]gamelog 1234`
        `[p]gamelog Nelluk`

        `[p]gamelogs` - *Mod only*: List last 50 log messages, regardless of game.
        """

        # TODO: Might have issue with log entries leaking across servers. Could add a guild_id field to the log table and limit
        # searches to ctx.guild.id. Would need a one-time command to populate guild_id on old entries
        if ctx.invoked_with == 'gamelog':
            # look up history of one game

            if search_term:
                try:
                    game_id = int(search_term)
                except (ValueError):
                    game_id = None  # search_term is string, used for text search of log contents
                else:
                    # Numeric search term passed, if its <= 7 chars assume its a game ID and search that way
                    if len(search_term) > 7:
                        # if numeric string > 7 chars passed, assuming its not a game ID but will be used for a text search
                        game_id = None
            else:
                return await ctx.send(f'No search term was entered')

            if game_id:
                message_list = [f'Listing all entries for game # {game_id}...']
                entries = models.GameLog.select().where(
                    (models.GameLog.game_id == game_id)
                    & (models.GameLog.guild_id == ctx.guild.id)).order_by(
                        -models.GameLog.message_ts)
                for entry in entries:
                    message_list.append(
                        f'`{entry.message_ts.strftime("%Y-%m-%d %H:%M:%S")}` - {entry.message}'
                    )
            else:
                message_list = [
                    f'Listing the 50 most recent entries matching **{search_term}**...'
                ]
                entries = models.GameLog.select().where(
                    (models.GameLog.message.contains(search_term))
                    & (models.GameLog.guild_id == ctx.guild.id)).order_by(
                        -models.GameLog.message_ts).limit(50)
                for entry in entries:
                    message_list.append(
                        f'`{entry.message_ts.strftime("%Y-%m-%d %H:%M:%S")}` - {entry.game_id} - {entry.message}'
                    )

        elif ctx.invoked_with == 'gamelogs' and settings.is_mod(ctx.author):
            # List 50 more recent logged actions
            if search_term and search_term.upper() == 'ALL':
                message_list = [
                    f'Listing the 50 most recent log items (across all guilds)...'
                ]
                entries = models.GameLog.select().order_by(
                    -models.GameLog.message_ts).limit(50)
            else:
                message_list = [f'Listing the 50 most recent log items...']
                entries = models.GameLog.select().where(
                    models.GameLog.guild_id == ctx.guild.id).order_by(
                        -models.GameLog.message_ts).limit(50)

            for entry in entries:
                message_list.append(
                    f'`{entry.message_ts.strftime("%Y-%m-%d %H:%M:%S")}` - {entry.game_id} - {entry.message}'
                )

        await utilities.buffered_send(destination=ctx,
                                      content='\n'.join(message_list))