예제 #1
0
def move_check(
        ctx: Union[Context, SlashContext], destination: GuildChannel
) -> (Union[Context, SlashContext], TextChannel):
    if ctx.guild is None:
        raise CommandUseFailure("Command must be performed in a guild.")
    if not isinstance(destination, TextChannel):
        raise CommandUseFailure("Destination must be a text channel.")
    author: Member = ctx.author
    bot: Member = ctx.guild.get_member(ctx.bot.user.id)
    origin: TextChannel = ctx.channel
    if origin == destination:
        raise CommandUseFailure("Destination must be a different channel.")
    if not destination.permissions_for(author).send_messages:
        raise CommandUseFailure(
            "You must have permission to send messages in the destination channel."
        )
    if not origin.permissions_for(bot).send_messages:
        raise CommandUseFailure(
            "Bot must have permission to send messages in the current channel."
        )
    if not destination.permissions_for(bot).send_messages:
        raise CommandUseFailure(
            "Bot must have permission to send messages in the destination channel."
        )
    if not origin.permissions_for(bot).embed_links:
        raise CommandUseFailure(
            "Bot must have permission to embed links in the current channel.")
    if not destination.permissions_for(bot).embed_links:
        raise CommandUseFailure(
            "Bot must have permission to embed links in the destination channel."
        )
    return ctx, destination  # Return passed parameters in order to perform type casting on destination
예제 #2
0
def add_channel_perms_field(
    embed: t.Optional[Embed],
    channel_before: GuildChannel,
    channel_after: GuildChannel,
) -> Embed:
    """
    Compare overwrites fo passed channels `channel_before` and `channel_after`.
    Return the passed embed with a new field, containing formatted differences between
    channel permission overrides. Returned object is a new Embed, to avoid mutating original.
    """
    embed_lines = []
    all_overwrites = set(channel_before.overwrites.keys()).union(
        set(channel_after.overwrites.keys()))

    for overwrite_for in all_overwrites:
        before_overwrites = channel_before.overwrites_for(overwrite_for)
        after_overwrites = channel_after.overwrites_for(overwrite_for)

        if before_overwrites == after_overwrites:
            continue

        embed_lines.append(
            f"**Overwrite changes for {overwrite_for.mention}:**")

        for before_perm, after_perm in zip(before_overwrites,
                                           after_overwrites):
            if before_perm[1] != after_perm[1]:
                perm_name = before_perm[0].replace("_", " ").replace(
                    ".", " ").capitalize()

                if before_perm[1] is True:
                    before_emoji = "✅"
                elif before_perm[1] is False:
                    before_emoji = "❌"
                else:
                    before_emoji = "⬜"

                if after_perm[1] is True:
                    after_emoji = "✅"
                elif after_perm[1] is False:
                    after_emoji = "❌"
                else:
                    after_emoji = "⬜"

                embed_lines.append(
                    f"**`{perm_name}:`** {before_emoji} ➜ {after_emoji}")

    embed = embed.copy()
    embed.add_field(name="Details", value="\n".join(embed_lines), inline=False)

    return embed
예제 #3
0
    async def edit_webhook(
        self,
        ctx: commands.Context,
        chan: Optional[discord.TextChannel],
        service: ServiceConverter,
        webhook: bool,
    ):
        """Set whether or not to use webhooks for status updates.

        Using a webhook means that the status updates will be sent with the avatar as the service's
        logo and the name will be `[service] Status Update`, instead of my avatar and name.

        If you don't specify a channel, I will use the current channel.
        """
        if TYPE_CHECKING:
            channel = GuildChannel()
            me = Member()
        else:
            channel = chan or ctx.channel
            me = ctx.me

        old_conf = await self.config.channel(channel).feeds()
        if service.name not in old_conf.keys():
            return await ctx.send(
                f"It looks like I don't send {service.friendly} status updates to "
                f"{channel.mention}")

        if old_conf[service.name]["webhook"] == webhook:
            word = "use" if webhook else "don't use"
            return await ctx.send(
                f"It looks like I already {word} webhooks for {service.friendly} status updates "
                f"in {channel.mention}")

        if webhook and not channel.permissions_for(me).manage_webhooks:
            return await ctx.send(
                "I don't have manage webhook permissions so I can't do that.")

        old_conf[service.name]["edit_id"] = {}
        old_conf[service.name]["webhook"] = webhook
        await self.config.channel(channel).feeds.set_raw(  # type:ignore
            service.name, value=old_conf[service.name])

        word = "use" if webhook else "not use"
        await ctx.send(
            f"{service.friendly} status updates in {channel.mention} will now {word} webhooks."
        )
예제 #4
0
    async def edit_restrict(
        self,
        ctx: commands.Context,
        chan: Optional[discord.TextChannel],
        service: ServiceConverter,
        restrict: bool,
    ):
        """
        Restrict access to the service in the `status` command.

        Enabling this will reduce spam. Instead of sending the whole update
        (if there's an incident) members will instead be redirected to channels
        that automatically receive the status updates, that they have permission to to view.
        """
        if TYPE_CHECKING:
            channel = GuildChannel()
        else:
            channel = chan or ctx.channel

        feed_settings = await self.config.channel(channel).feeds()
        if service.name not in feed_settings.keys():
            return await ctx.send(
                f"It looks like I don't send {service.friendly} status updates to "
                f"{channel.mention}")

        old_conf = (await self.config.guild(
            channel.guild).service_restrictions()).get(service.name, [])
        old_bool = channel.id in old_conf
        if old_bool == restrict:
            word = "" if restrict else "don't "
            return await ctx.send(
                f"It looks like I already {word}restrict {service.friendly} status updates for "
                "the `status` command.")

        async with self.config.guild(
                channel.guild).service_restrictions() as sr:
            if restrict:
                try:
                    sr[service.name].append(channel.id)
                except KeyError:
                    sr[service.name] = [channel.id]
                self.service_restrictions_cache.add_restriction(
                    channel.guild.id, service.name, channel.id)
            else:
                sr[service.name].remove(channel.id)
                self.service_restrictions_cache.remove_restriction(
                    channel.guild.id, service.name, channel.id)

        word = "" if restrict else "not "
        await ctx.send(
            f"{service.friendly} will now {word}be restricted in the `status` command."
        )
예제 #5
0
    async def edit_mode(
        self,
        ctx: commands.Context,
        chan: Optional[discord.TextChannel],
        service: ServiceConverter,
        mode: ModeConverter,
    ):
        """Change what mode to use for status updates.

        **All**: Every time the service posts an update on an incident, I will send a new message
        containing the previous updates as well as the new update. Best used in a fast-moving
        channel with other users.

        **Latest**: Every time the service posts an update on an incident, I will send a new
        message containing only the latest update. Best used in a dedicated status channel.

        **Edit**: When a new incident is created, I will sent a new message. When this incident is
        updated, I will then add the update to the original message. Best used in a dedicated
        status channel.

        If you don't specify a channel, I will use the current channel.
        """
        if TYPE_CHECKING:
            channel = GuildChannel()
        else:
            channel = chan or ctx.channel

        old_conf = await self.config.channel(channel).feeds()
        if service.name not in old_conf.keys():
            return await ctx.send(
                f"It looks like I don't send {service.friendly} status updates to "
                f"{channel.mention}")

        if old_conf[service.name]["mode"] == mode:
            return await ctx.send(
                f"It looks like I already use that mode for {service.friendly} updates in "
                f"{channel.mention}")

        old_conf[service.name]["mode"] = mode
        await self.config.channel(channel).feeds.set_raw(  # type:ignore
            service.name, value=old_conf[service.name])

        await ctx.send(
            f"{service.friendly} status updates in {channel.mention} will now use the {mode} mode."
        )
예제 #6
0
파일: utils.py 프로젝트: Vexed01/Vex-Cogs
def channel_perm_check(me: Member, channel: GuildChannel) -> str:
    """Check if I have the correct permissions for this to be the Birthday channel.

    Parameters
    ----------
    me : discord.Member
        My user object

    channel : discord.TextChannel
        Channel to check

    Returns
    -------
    str
        Error message or empty string
    """
    if channel.permissions_for(me).send_messages is False:
        return "I don't have the Send Messages permission."
    return ""
예제 #7
0
    async def statusset_remove(
        self,
        ctx: commands.Context,
        service: ServiceConverter,
        chan: Optional[discord.TextChannel],
    ):
        """
        Stop status updates for a specific service in this server.

        If you don't specify a channel, I will use the current channel.
        """
        if TYPE_CHECKING:
            channel = GuildChannel()
        else:
            channel = chan or ctx.channel

        async with self.config.channel(channel).feeds() as feeds:
            if not feeds.pop(service.name, None):
                return await ctx.send(
                    f"It looks like I don't send {service.friendly} updates in {channel.mention}."
                )

        self.used_feeds.remove_feed(service.name)

        sr: Dict[str, List[int]]
        async with self.config.guild(
                channel.guild).service_restrictions() as sr:
            try:
                sr[service.name].remove(channel.id)
            except ValueError:
                pass

            self.service_restrictions_cache.remove_restriction(
                channel.id, service.name, channel.id)

        await ctx.send(
            f"Removed {service.friendly} status updated from {channel.mention}"
        )
예제 #8
0
 def role_can_read(channel: GuildChannel, role: Role) -> bool:
     """Return True if `role` can read messages in `channel`."""
     overwrites = channel.overwrites_for(role)
     return overwrites.read_messages is True
예제 #9
0
    async def statusset_add(
        self,
        ctx: commands.Context,
        service: ServiceConverter,
        chan: Optional[discord.TextChannel],
    ):
        """
        Start getting status updates for the chosen service!

        There is a list of services you can use in the `[p]statusset list` command.

        This is an interactive command. It will ask what mode you want to use and if you
        want to use a webhook. You can use the `[p]statusset preview` command to see how
        different options look or take a look at
        https://vex-cogs.rtfd.io/en/latest/cogs/statusref.html

        If you don't specify a specific channel, I will use the current channel.
        """
        if TYPE_CHECKING:
            channel = GuildChannel()
        else:
            channel = chan or ctx.channel

        if not channel.permissions_for(ctx.me).send_messages:  # type:ignore
            return await ctx.send(
                f"I don't have permissions to send messages in {channel.mention}"
            )

        existing_feeds = await self.config.channel(channel).feeds(
        )  # type:ignore
        if service in existing_feeds.keys():
            return await ctx.send(
                f"{channel.mention} already receives {service.friendly} status "  # type:ignore
                f"updates. You can edit it with `{ctx.clean_prefix}statusset edit`."
            )

        modes = (
            "**All**: Every time the service posts an update on an incident, I will send a new "
            "message containing the previous updates as well as the new update. Best used in a "
            "fast-moving channel with other users.\n\n"
            "**Latest**: Every time the service posts an update on an incident, I will send a new "
            "message containing only the latest update. Best used in a dedicated status channel.\n"
            "\n**Edit**: When a new incident is created, I will sent a new message. When this "
            "incident is updated, I will then add the update to the original message. Best used "
            "in a dedicated status channel.\n\n")

        # === MODE ===

        await ctx.send(
            "This is an interactive configuration. You have 2 minutes to answer each question.\n"
            "If you aren't sure what to choose, just say `cancel` and take a look at the "
            f"**`{ctx.clean_prefix}statusset preview`** command.\n\n**What mode do you want to "
            f"use?**\n\n{modes}")

        try:
            # really shouldn't monkey patch this
            mode = await ModeConverter.convert(  # type:ignore
                None,
                ctx,
                (await
                 self.bot.wait_for("message",
                                   check=MessagePredicate.same_context(ctx),
                                   timeout=120)).content,
            )
        except asyncio.TimeoutError:
            return await ctx.send("Timed out. Cancelling.")
        except commands.BadArgument as e:
            return await ctx.send(e)

        # === WEBHOOK ===

        if channel.permissions_for(ctx.me).manage_webhooks:  # type:ignore
            await ctx.send(
                "**Would you like to use a webhook?** (yes or no answer)\nUsing a webhook means "
                f"that the status updates will be sent with the avatar as {service.friendly}'s "
                f"logo and the name will be `{service.friendly} Status Update`, instead of my "
                "avatar and name.")

            pred = MessagePredicate.yes_or_no(ctx)
            try:
                await self.bot.wait_for("message", check=pred, timeout=120)
            except asyncio.TimeoutError:
                return await ctx.send("Timed out. Cancelling.")

            webhook = pred.result
            if webhook:
                # already checked for perms to create
                # thanks flare for your webhook logic (redditpost) (or trusty?)

                # i know this makes the webhook in the wrong channel if a specific one is chosen...
                # its remade later, mypy makes this annoying to fix
                # TODO: ^
                existing_webhook = False
                for hook in await ctx.channel.webhooks():
                    if hook.name == channel.guild.me.name:
                        existing_webhook = True
                if not existing_webhook:
                    await ctx.channel.create_webhook(
                        name=channel.guild.me.name,
                        reason="Created for status updates.")
        else:
            await ctx.send(
                "I would ask about whether you want me to send updates as a webhook (so they "
                "match the service), however I don't have the `manage webhooks` permission."
            )
            webhook = False

        # === RESTRICT ===

        await ctx.send(
            f"**Would you like to restrict access to {service.friendly} in the "  # type:ignore
            f"`{ctx.clean_prefix}status` command?** (yes or no answer)\nThis will reduce spam. If "
            f"there's an incident, members will instead be redirected to {channel.mention} and "
            f"any other channels that you've set to receive {service.friendly} status updates "
            "which have restrict enabled.")

        pred = MessagePredicate.yes_or_no(ctx)
        try:
            await self.bot.wait_for("message", check=pred, timeout=120)
        except asyncio.TimeoutError:
            return await ctx.send("Timed out. Cancelling.")

        if pred.result is True:
            async with self.config.guild(
                    ctx.guild).service_restrictions() as sr:
                try:
                    sr[service.name].append(channel.id)
                except KeyError:
                    sr[service.name] = [channel.id]

                self.service_restrictions_cache.add_restriction(
                    ctx.guild.id, service.name, channel.id)

        # === FINISH ===

        settings = {"mode": mode, "webhook": webhook, "edit_id": {}}
        await self.config.channel(channel).feeds.set_raw(  # type:ignore
            service.name, value=settings)
        self.used_feeds.add_feed(service.name)

        if service in SPECIAL_INFO.keys():
            msg = f"NOTE: {SPECIAL_INFO[service.name]}\n"
        else:
            msg = ""

        await ctx.send(
            f"{msg}Done, {channel.mention} will now receive {service.friendly} status updates."
        )