Example #1
0
class ConnectFour(Cog):
    """Play a game of Connect Four

    See `[p]help c4` manual for individual
    commands for more information.

    The classic game of Connect Four.
    Use these commands to play a game
    of Connect Four with another user.
    You can have multiple concurrent
    games, one per channel."""
    def __init__(self, bot: Bot):
        self.bot = bot
        self.config = SubRedis(bot.db, "c4")

        self.sessions = dict()
        self.timeout = 120
        self.timeout_incr = 1

        # Valid reactions
        self._game_reactions = [
            str(Emoji.one),
            str(Emoji.two),
            str(Emoji.three),
            str(Emoji.four),
            str(Emoji.five),
            str(Emoji.six),
            str(Emoji.seven),
            str(Emoji.x),
            str(Emoji.refresh)
        ]

        # Default chips
        self.empty_chip = AWBW_EMOJIS["ne"]
        self.p1_chip = AWBW_EMOJIS["os"]
        self.p2_chip = AWBW_EMOJIS["bm"]

    def session(self, channel: TextChannel) -> Optional[ConnectFourSession]:
        """Returns an active ConnectFourSession if there is a running game in a channel"""
        return self.sessions.get(channel.id, None)

    def channel_check(self, channel: TextChannel) -> bool:
        """Returns true if C4 is enabled in channel"""
        return str(channel.id) in self.config.smembers("allowed_channels")

    @staticmethod
    async def member_check(ctx: Context, member: str) -> Optional[Member]:
        """Attempt to convert an argument an argument to :class:`Member`"""
        try:
            return await MemberConverter().convert(ctx, member)
        except (BadArgument, NoPrivateMessage, TypeError):
            return None

    @staticmethod
    def get_member_chip(roles: List[Role],
                        skip: str = None) -> Tuple[str, str]:
        for ctry, role_id in AWBW_ROLES.items():
            if role_id in [r.id for r in roles] and ctry != skip:
                return ctry, AWBW_EMOJIS[ctry]
        else:
            ctry = choice(
                [ctry for ctry in AWBW_EMOJIS.keys() if ctry != skip])
            return ctry, AWBW_EMOJIS[ctry]

    @staticmethod
    async def send_message(channel: TextChannel,
                           msg: str = "Error",
                           level: int = MsgLevel.info) -> None:
        """Formats a message as embed"""

        em = Embed(description=f"{MSG_ICON[level.value]}  {msg}",
                   color=MSG_COLOR[level.value])
        msg = await channel.send(embed=em)
        await sleep(5)
        await msg.delete()

    async def init_game_message(self, channel: TextChannel,
                                session: ConnectFourSession,
                                board: Embed) -> Message:
        """Sends game board to channel and sets message on session"""

        msg = await channel.send(embed=board)

        for react in self._game_reactions:
            await msg.add_reaction(str(react))
            await sleep(0.1)

        session.msg = msg

        return msg

    async def send_board(self, channel: TextChannel) -> Message:
        """Prepare game Embed and update message"""

        session = self.session(channel)

        em = Embed(
            title=
            f"{session.p1.chip}{session.p1.name} {Emoji.vs} {session.p2.name}{session.p2.chip}",
            description=f"\n"
            f"{Emoji.one}{Emoji.two}{Emoji.three}{Emoji.four}{Emoji.five}{Emoji.six}{Emoji.seven}\n"
            f"{session.draw_board}")

        if session.state == State.init:
            em.description = f"New game! Turn: 1\n" \
                             f"{em.description}\n" \
                             f"\n" \
                             f"{session.current_player.mention}'s turn: {session.current_player.chip}"
            em.colour = session.colour

            return await self.init_game_message(channel, session, em)

        elif session.state == State.active:
            em.description = f"Turn: {(session.turn + 2) // 2}\n" \
                             f"{em.description}\n" \
                             f"\n" \
                             f"{session.current_player.mention}'s turn: {session.current_player.chip}"
            em.colour = session.colour

        elif session.state == State.draw:
            self.sessions.pop(channel.id)
            em.description = f"Game ended in a Draw.\n" \
                             f"{em.description}"
            em.colour = Colour.dark_grey()
            await session.msg.clear_reactions()

        elif session.state == State.forfeit:
            self.sessions.pop(channel.id)
            em.description = f"Game Over. {session.current_player.name} Forfeits.\n" \
                             f"{em.description}"
            em.colour = Colour.dark_grey()
            await session.msg.clear_reactions()

        elif session.state == State.timeout:
            self.sessions.pop(channel.id)
            em.description = f"Time Out. {session.current_player.name} Forfeits.\n" \
                             f"{em.description}"
            em.colour = Colour.dark_grey()
            await session.msg.clear_reactions()

        elif session.state == State.won:
            self.sessions.pop(channel.id)
            em.description = f"Game Over!\n{session.current_player.name} wins! {Emoji.tada}\n" \
                             f"{em.description}"
            em.colour = 0xFDFF00
            await session.msg.clear_reactions()

        # TODO: check I can edit the message (retrievable), if not, init message
        return await session.msg.edit(embed=em)

    @group(name="c4", invoke_without_command=True)
    async def c4(self, ctx: Context, *, member=None):
        """Connect Four

        `[p]c4 @user` to start a game with
        another user in the current channel."""

        if not member:
            return await self.bot.help_command.send_help_for(
                ctx, ctx.command, "You need another player to start")

        try:
            member = await self.member_check(ctx, member)
        except BadArgument:
            return await self.member_check(ctx, member)

        if not self.channel_check(ctx.channel):
            return await self.send_message(
                ctx.channel,
                msg=
                f"{ctx.author.mention}: Connect Four is not enabled in this channel",
                level=MsgLevel.error)

        elif self.session(ctx.channel):
            return await self.send_message(
                ctx.channel,
                msg=
                f"{ctx.author.mention}: There is already an active session in this channel",
                level=MsgLevel.warning)

        elif member.id == ctx.author.id:
            return await self.send_message(
                ctx.channel,
                msg=
                f"{ctx.author.mention}: You cannot start a game with yourself.",
                level=MsgLevel.warning)

        elif member.bot:
            return await self.send_message(
                ctx.channel,
                msg=f"{ctx.author.mention}: You cannot play against bots.",
                level=MsgLevel.warning)

        elif member.id == ctx.author.id:
            return await self.send_message(
                ctx.channel,
                msg=
                f"{ctx.author.mention}: You cannot start a game with yourself.",
                level=MsgLevel.warning)

        else:
            p2_ctry, p2_chip = self.get_member_chip(ctx.author.roles)
            _, p1_chip = self.get_member_chip(member.roles, p2_ctry)

            self.sessions[ctx.channel.id] = ConnectFourSession(
                p1=member,
                p1_chip=p1_chip,
                p2=ctx.author,
                p2_chip=p2_chip,
                empty=self.empty_chip)
            await self.send_board(ctx.channel)

    @c4.command(name="help", hidden=True)
    async def c4_help(self, ctx):
        """Shortcut to send help manual for ConnectFour"""
        await self.bot.help_command.send_help_for(
            ctx, self.bot.get_cog("ConnectFour"))

    @c4.command(name="board")
    async def c4_board(self, ctx: Context):
        """Resend the current game board"""
        session = self.session(ctx.channel)
        if not session:
            return await self.send_message(
                ctx.channel,
                msg=
                f"{ctx.author.mention} There is no active game in this channel.",
                level=MsgLevel.warning)
        elif ctx.author.id not in session.players.keys():
            return
        else:
            await self.init_game_message(ctx.channel, session,
                                         session.msg.embeds[0])

    @sudo()
    @c4.command(name="enable", hidden=True)
    async def c4_enable(self, ctx: Context, *, chan: TextChannel = None):
        """Enable Connect Four on a channel

        Run without an argument to enable on the current channel
        Pass a channel as an argument to enable on that channel"""

        if not chan:
            chan = ctx.channel

        if self.config.sadd("allowed_channels", chan.id):
            await self.send_message(
                ctx.channel,
                msg="Connect Four successfully enabled on channel.",
                level=MsgLevel.info)

        else:
            await self.send_message(
                ctx.channel,
                msg="Connect Four already enabled on channel.",
                level=MsgLevel.warning)

    @sudo()
    @c4.command(name="disable", hidden=True)
    async def c4_disable(self, ctx: Context, *, chan: TextChannel = None):
        """Disable Connect Four on a channel

        Run without an argument to disabled on the current channel
        Pass a channel as an argument to disable on that channel"""

        if not chan:
            chan = ctx.channel

        if self.config.srem("allowed_channels", chan.id):
            await self.send_message(
                ctx.channel,
                msg="Connect Four successfully disabled on channel.",
                level=MsgLevel.info)

        else:
            await self.send_message(
                ctx.channel,
                msg="Connect Four already disabled on channel.",
                level=MsgLevel.warning)

    @sudo()
    @c4.command(name="games", hidden=True)
    async def c4_games(self, ctx: Context):
        """Shows the number of running sessions"""
        sessions = '\n'.join([str(s) for s in self.sessions.values()])
        await self.send_message(
            ctx.channel,
            msg=f"Total running games: {len(self.sessions.keys())}\n"
            f"\n"
            f"{sessions}",
            level=MsgLevel.info)

    @sudo()
    @c4.command(name="kill", hidden=True)
    async def c4_kill(self, ctx: Context, *, _all: bool = False):
        """Administrative kill command

        This will kill a running game in the current channel"""
        if not _all:
            if self.sessions.pop(ctx.channel.id, None):
                await self.send_message(
                    ctx.channel,
                    msg="Current game in this channel has been terminated.",
                    level=MsgLevel.info)
            else:
                await self.send_message(ctx.channel,
                                        msg="No active game in this channel.",
                                        level=MsgLevel.warning)
        else:
            await self.send_message(
                ctx.channel,
                msg=
                f"All running games have been terminated. (Total: {len(self.sessions.keys())})",
                level=MsgLevel.info)
            self.sessions = dict()

    @sudo()
    @c4.command(name="killall", hidden=True)
    async def c4_killall(self, ctx: Context):
        """Administrative kill command

        This will kill all running games in all channels"""

        ctx.message.content = f"{self.bot.command_prefix}kill True"
        await self.bot.process_commands(ctx.message)

    @Cog.listener(name="on_reaction_add")
    async def on_reaction_add(self, reaction: Reaction, user: Union[User,
                                                                    Member]):

        await sleep(0.1)

        if not self.channel_check(reaction.message.channel):
            # Channel is not watched for Connect Four
            return

        if user.id == self.bot.user.id:
            # Ignore the bot's own reactions
            return

        # It's a watched channel, so try to get an active session
        session = self.session(reaction.message.channel)

        if not session:
            # No session in channel
            return

        if reaction.message.id != session.msg.id:
            # Message is not an active session
            return

        if reaction.emoji not in self._game_reactions:
            # Not a valid game reaction
            return

        # It is a valid game reaction on an active session in a watched channel
        # Remove reaction, then act based on reaction
        await reaction.remove(user)

        if user.id not in session.players:
            # Not a player
            return

        # Currently the other player's turn
        if user.id != session.current_player.id:

            # Still allow quitting
            if reaction.emoji == str(Emoji.x):
                session.turn -= 1
                session.forfeit()
                return await self.send_board(reaction.message.channel)

            # And resending the board
            elif reaction.emoji == str(Emoji.refresh):
                await session.msg.delete()
                return await self.init_game_message(reaction.message.channel,
                                                    session,
                                                    session.msg.embeds[0])

            # Otherwise needs to be player's turn
            else:
                return await self.send_message(
                    reaction.message.channel,
                    msg=f"{user.mention}: It is not your turn.",
                    level=MsgLevel.warning)

        # Resend the current game board
        if reaction.emoji == str(Emoji.refresh):
            await session.msg.delete()
            return await self.init_game_message(reaction.message.channel,
                                                session, session.msg.embeds[0])

        if reaction.emoji == str(Emoji.one):
            try:
                session.play(user, 1)
            except ValueError:
                await self.send_message(
                    reaction.message.channel,
                    msg="That column is full. Select another.",
                    level=MsgLevel.error)
            return await self.send_board(reaction.message.channel)

        if reaction.emoji == str(Emoji.two):
            try:
                session.play(user, 2)
            except ValueError:
                await self.send_message(
                    reaction.message.channel,
                    msg="That column is full. Select another.",
                    level=MsgLevel.error)
            return await self.send_board(reaction.message.channel)

        if reaction.emoji == str(Emoji.three):
            try:
                session.play(user, 3)
            except ValueError:
                await self.send_message(
                    reaction.message.channel,
                    msg="That column is full. Select another.",
                    level=MsgLevel.error)
            return await self.send_board(reaction.message.channel)

        if reaction.emoji == str(Emoji.four):
            try:
                session.play(user, 4)
            except ValueError:
                await self.send_message(
                    reaction.message.channel,
                    msg="That column is full. Select another.",
                    level=MsgLevel.error)
            return await self.send_board(reaction.message.channel)

        if reaction.emoji == str(Emoji.five):
            try:
                session.play(user, 5)
            except ValueError:
                await self.send_message(
                    reaction.message.channel,
                    msg="That column is full. Select another.",
                    level=MsgLevel.error)
            return await self.send_board(reaction.message.channel)

        if reaction.emoji == str(Emoji.six):
            try:
                session.play(user, 6)
            except ValueError:
                await self.send_message(
                    reaction.message.channel,
                    msg="That column is full. Select another.",
                    level=MsgLevel.error)
            return await self.send_board(reaction.message.channel)

        if reaction.emoji == str(Emoji.seven):
            try:
                session.play(user, 7)
            except ValueError:
                await self.send_message(
                    reaction.message.channel,
                    msg="That column is full. Select another.",
                    level=MsgLevel.error)
            return await self.send_board(reaction.message.channel)

        if reaction.emoji == str(Emoji.x):
            session.forfeit()
            return await self.send_board(reaction.message.channel)

    @Cog.listener(name="on_timer_update")
    async def on_timer_update(self, sec: int):
        """Timer event that triggers every 1 second"""

        # Only check statuses every `self.timeout_incr` seconds
        if sec % self.timeout_incr == 0:

            # Get all sessions and start a list of ones that timed out
            sessions = self.sessions.values()
            to_expire = list()

            # Increment the time on all
            for session in sessions:
                session.timeout += self.timeout_incr

                # Timeout reached
                # Add to list of expired sessions
                if session.timeout == self.timeout:
                    to_expire.append(session)

            # Set game over condition on all expired sessions to timeout and send
            for session in to_expire:
                session.expire()
                await self.send_board(session.msg.channel)
Example #2
0
class SelfRoles(Cog):
    """Commands for managing and assigning selfroles"""
    def __init__(self, bot: Bot):
        self.bot = bot
        self.config = SubRedis(bot.db, "selfroles")

        self.errorlog = bot.errorlog

        self._selfroles = {g.id: [] for g in bot.guilds}

        for key in self.config.scan_iter(match=f"{self.config}:*"):
            guild = bot.get_guild(int(key.split(":")[-1]))
            if not guild:
                continue
            self._selfroles[guild.id] = []
            r_ids = [int(r_id) for r_id in self.config.smembers(key)]
            for role in guild.roles:
                if role.id in r_ids:
                    self._selfroles[guild.id].append(role.id)

    @guild_only()
    @command(name="iam")
    async def iam(self, ctx: Context, *, role) -> None:
        """Add a self-assignable role

        If the `role` is configured as an assignable
        selfrole, you can use this command to assign
        the role to yourself.

        `[p]iam role`
        `[p]iam @role`
        `[p]iam role id`

        You can also use a country's short code.
        e.g. `[p]iam os`"""

        if role.lower() in ROLE_SHORTNAME.keys():
            role = str(ROLE_SHORTNAME[role.lower()])

        try:
            role = await RoleConverter().convert(ctx, role)

        except BadArgument:
            await ctx.send(
                embed=Embed(color=ctx.guild.me.colour,
                            title="⚠ Selfroles",
                            description=f"Could not recognize role."))

        else:
            if role in ctx.author.roles:
                await ctx.send(embed=Embed(
                    color=ctx.guild.me.colour,
                    title="⚠ Selfroles",
                    description="You already have this role assigned."))
            elif role.id in self._selfroles.get(ctx.guild.id):
                for author_role in ctx.author.roles:
                    if author_role.id in self._selfroles[ctx.guild.id]:
                        await ctx.author.remove_roles(
                            author_role,
                            reason=ctx.message.content,
                            atomic=True)
                        await sleep(0.5)
                await ctx.author.add_roles(role,
                                           reason=ctx.message.content,
                                           atomic=True)
                await ctx.send(embed=Embed(
                    color=role.colour,
                    title="Role Assigned",
                    description=f"Congratulations, {ctx.author.mention}!"
                    f" You now have the **{role.mention}** "
                    f"role."))
            else:
                await ctx.send(embed=Embed(
                    color=0xFF0000,
                    title="⚠ Selfroles",
                    description="That role is not self-assignable."))

    @guild_only()
    @group(name="selfroles", aliases=["selfrole"], invoke_without_command=True)
    async def selfroles(self, ctx: Context):
        """View all selfroles"""

        r_ids = self._selfroles.get(ctx.guild.id)

        if r_ids:
            roles = []
            for role in ctx.guild.roles:
                if role.id in r_ids:
                    roles.append(role.mention)

            roles = "\n".join(roles)

            await ctx.send(embed=Embed(
                color=ctx.guild.me.colour,
                title="Selfroles",
                description=f"The following roles are self-assignable:\n"
                f"{roles}"
                f"\n"
                f"\n"
                f"See `?help iam` for assigning selfroles."))

        else:
            await ctx.send(embed=Embed(
                color=ctx.guild.me.colour,
                title="Selfroles",
                description="There are currently no assignable selfroles.\n"
                "Staff may configure selfroles with `?selfrole add`."))

    @awbw_staff()
    @selfroles.command(name="add")
    async def _add(self, ctx: Context, *, role) -> None:
        """Configures a role as a selfrole"""

        r_ids = [i for i in self._selfroles.get(ctx.guild.id)]

        try:
            role = await RoleConverter().convert(ctx, role)
        except BadArgument:
            await ctx.send(
                embed=Embed(color=ctx.guild.me.colour,
                            title="⚠ Selfroles Management",
                            description=f"Could not recognize role."))
        else:
            if role.id in r_ids:
                await ctx.send(embed=Embed(
                    color=ctx.guild.me.colour,
                    title="⚠ Selfroles Management",
                    description=
                    f"Role {role.mention} is already configured as a selfrole."
                ))
            else:
                self.config.sadd(f"{self.config}:{ctx.guild.id}", str(role.id))
                self._selfroles[ctx.guild.id].append(role.id)
                await ctx.send(embed=Embed(
                    color=ctx.guild.me.colour,
                    title="Selfroles Management",
                    description=
                    f"Role {role.mention} has been added to selfroles."))

    @awbw_staff()
    @selfroles.command(name="rem", aliases=["del", "remove", "delete"])
    async def _rem(self, ctx: Context, *, role):
        """Removes a role from selfroles"""

        r_ids = [i for i in self._selfroles.get(ctx.guild.id)]

        try:
            role = await RoleConverter().convert(ctx, role)

        except BadArgument:
            await ctx.send(
                embed=Embed(color=ctx.guild.me.colour,
                            title="⚠ Selfroles Management",
                            description=f"Could not recognize role."))

        else:
            if role.id in r_ids:
                self.config.srem(f"{self.config}:{ctx.guild.id}", role.id)
                self._selfroles[ctx.guild.id].remove(role.id)
                await ctx.send(embed=Embed(
                    color=ctx.guild.me.colour,
                    title="Selfroles Management",
                    description=
                    f"Role {role.mention} has been removed  from selfroles."))
            else:
                await ctx.send(embed=Embed(
                    color=ctx.guild.me.colour,
                    title="⚠ Selfroles Management",
                    description=
                    f"Role {role.mention} is not configured as a selfrole."))
Example #3
0
class Poll(Cog):
    """User-created polls"""
    def __init__(self, bot: Bot):
        self.bot = bot
        self.config = SubRedis(bot.db, "poll")

        self.errorlog = bot.errorlog

    @check(has_permission)
    @group(name="poll", invoke_without_command=True)
    async def poll(self, ctx: Context):
        await ctx.send("Placeholder")

    """ #######
         Setup
        ####### """

    @check(has_permission)
    @poll.command(name="view")
    async def view(self, ctx: Context):
        pass

    @check(has_permission)
    @poll.command(name="create", aliases=["new"])
    async def create(self, ctx: Context, *, name: str):
        pass

    @check(has_permission)
    @poll.command(name="discard", aliases=["drop"])
    async def discard(self, ctx: Context):
        pass

    @check(has_permission)
    @poll.command(name="name", aliases=["title"])
    async def name(self, ctx: Context):
        pass

    @check(has_permission)
    @poll.command(name="add_option", aliases=["add"])
    async def add_option(self, ctx: Context):
        pass

    @check(has_permission)
    @poll.command(name="remove_option",
                  aliases=["remove", "delete_option", "delete", "rem", "del"])
    async def remove_option(self, ctx: Context):
        pass

    """ #########
         Running
        ######### """

    @check(has_permission)
    @poll.command(name="start")
    async def start(self, ctx: Context, *, channel: TextChannel):
        pass

    @check(has_permission)
    @poll.command(name="view")
    async def view(self, ctx: Context):
        pass

    """ ################
         Administrative
        ################ """

    @has_guild_permissions(manage_guild=True)
    @poll.command(name="add_role", hidden=True)
    async def add_role(self, ctx: Context, role: Role):

        if self.config.sismember(f"allowed_roles:{ctx.guild.id}", role.id):
            em = Embed(title="Polls Administration",
                       description=
                       f"{role.mention} is already allowed to conduct polls.",
                       color=Color.red())
            await ctx.send(embed=em, delete_after=5)

        else:
            self.config.sadd(f"allowed_roles:{ctx.guild.id}", role.id)
            em = Embed(
                title="Polls Administration",
                description=f"{role.mention} is now allowed to conduct polls.",
                color=Color.green())
            await ctx.send(embed=em, delete_after=5)

    @has_guild_permissions(manage_guild=True)
    @poll.command(name="remove_role", aliases=["rem_role"], hidden=True)
    async def remove_role(self, ctx: Context, role: Role):

        if self.config.sismember(f"allowed_roles:{ctx.guild.id}", role.id):
            self.config.srem(f"allowed_roles:{ctx.guild.id}", role.id)
            em = Embed(
                title="Polls Administration",
                description=
                f"{role.mention} is no longer allowed to conduct polls.",
                color=Color.green())
            await ctx.send(embed=em, delete_after=5)

        else:
            em = Embed(
                title="Polls Administration",
                description=f"{role.mention} is not allowed to conduct polls.",
                color=Color.red())
            await ctx.send(embed=em, delete_after=5)

    @has_guild_permissions(manage_guild=True)
    @poll.command(name="list_roles", aliases=["roles"], hidden=True)
    async def list_roles(self, ctx: Context):
        role_ids = sorted(
            self.config.smembers(f"allowed_roles:{ctx.guild.id}"))

        if role_ids:
            roles = "\n".join([
                ctx.guild.get_role(int(role_id)).mention
                for role_id in role_ids
            ])
            em = Embed(
                title="Polls Administration",
                description=
                f"The following roles are allowed to conduct polls:\n{roles}",
                color=Color.green())
            await ctx.send(embed=em, delete_after=5)

        else:
            em = Embed(
                title="Polls Administration",
                description=
                "There are currently no roles allowed to conduct polls.",
                color=Color.red())
            await ctx.send(embed=em, delete_after=5)