|
| 1 | +"""This file is a part of the source code for PygameCommunityBot. |
| 2 | +
|
| 3 | +Copyright (c) 2022-present pygame-community |
| 4 | +
|
| 5 | +Bot extension for Discord channel management. |
| 6 | +""" |
| 7 | + |
| 8 | +import discord |
| 9 | +from discord.ext import commands |
| 10 | + |
| 11 | +import snakecore |
| 12 | +from snakecore.commands.decorators import flagconverter_kwargs |
| 13 | +from snakecore.commands.converters import CodeBlock, String, Parens, TimeDelta |
| 14 | + |
| 15 | +from ..base import BaseExtensionCog |
| 16 | + |
| 17 | +BotT = snakecore.commands.Bot | snakecore.commands.AutoShardedBot |
| 18 | + |
| 19 | + |
| 20 | +async def clone_forum( |
| 21 | + forum: discord.ForumChannel, new_name: str | None = None, reason: str | None = None |
| 22 | +) -> discord.ForumChannel: |
| 23 | + return await forum.guild.create_forum( |
| 24 | + name=new_name if new_name else forum.name, |
| 25 | + topic=forum.topic or discord.utils.MISSING, |
| 26 | + category=forum.category, |
| 27 | + position=forum.position + 1, |
| 28 | + slowmode_delay=forum.slowmode_delay, |
| 29 | + overwrites=( |
| 30 | + discord.utils.MISSING if forum.permissions_synced else forum.overwrites |
| 31 | + ), # type: ignore |
| 32 | + default_auto_archive_duration=forum.default_auto_archive_duration, |
| 33 | + default_thread_slowmode_delay=forum.default_thread_slowmode_delay, |
| 34 | + default_sort_order=( |
| 35 | + forum.default_sort_order |
| 36 | + if forum.default_sort_order is not None |
| 37 | + else discord.utils.MISSING |
| 38 | + ), |
| 39 | + default_reaction_emoji=( |
| 40 | + forum.default_reaction_emoji |
| 41 | + if forum.default_reaction_emoji is not None |
| 42 | + else discord.utils.MISSING |
| 43 | + ), |
| 44 | + default_layout=forum.default_layout, |
| 45 | + available_tags=( |
| 46 | + forum.available_tags if forum.available_tags else discord.utils.MISSING |
| 47 | + ), |
| 48 | + reason=reason, |
| 49 | + ) |
| 50 | + |
| 51 | + |
| 52 | +async def clone_category( |
| 53 | + category: discord.CategoryChannel, |
| 54 | + new_name: str | None, |
| 55 | + clone_channels: bool = True, |
| 56 | + reason: str | None = None, |
| 57 | +): |
| 58 | + new_category = await category.clone(name=new_name) |
| 59 | + |
| 60 | + if clone_channels: |
| 61 | + for channel in category.channels: |
| 62 | + if isinstance(channel, discord.ForumChannel): |
| 63 | + channel_clone = await clone_forum( |
| 64 | + channel, new_name=new_name, reason=reason |
| 65 | + ) |
| 66 | + else: |
| 67 | + channel_clone = await channel.clone() |
| 68 | + |
| 69 | + await channel_clone.move(category=new_category) # type: ignore |
| 70 | + |
| 71 | + return new_category |
| 72 | + |
| 73 | + |
| 74 | +class ChannelManagerCog(BaseExtensionCog, name="channels"): |
| 75 | + """Channel management commands.""" |
| 76 | + |
| 77 | + @commands.group(invoke_without_command=True) |
| 78 | + async def channel(self, ctx: commands.Context[BotT]): |
| 79 | + pass |
| 80 | + |
| 81 | + @channel.command( |
| 82 | + usage="<channel: Channel> [new_name: Text[100]] [deep_clone_category: yes|no]", |
| 83 | + ) |
| 84 | + @flagconverter_kwargs() |
| 85 | + async def channel_clone( |
| 86 | + self, |
| 87 | + ctx: commands.Context[BotT], |
| 88 | + channel: ( |
| 89 | + discord.TextChannel |
| 90 | + | discord.VoiceChannel |
| 91 | + | discord.ForumChannel |
| 92 | + | discord.StageChannel |
| 93 | + | discord.CategoryChannel |
| 94 | + ), |
| 95 | + new_name: str | None = None, |
| 96 | + deep_clone_category: bool = False, |
| 97 | + ): |
| 98 | + """Clone the specified channel. More reliable than Discord's built-in channel cloning system. |
| 99 | +
|
| 100 | + __**Parameters:**__ |
| 101 | +
|
| 102 | + **`<channel: Channel>`** |
| 103 | + > The channel to clone. |
| 104 | +
|
| 105 | + **`[new_name: Text[100]]`** |
| 106 | + > The new name of the cloned channel. Must not exceed 100 characters. |
| 107 | +
|
| 108 | + **`[deep_clone_category: yes|no]`** |
| 109 | + > Whether to clone all channels within a specified category channel. |
| 110 | + > Defaults to 'no'. |
| 111 | + """ |
| 112 | + if isinstance(channel, discord.CategoryChannel): |
| 113 | + clone = await clone_category( |
| 114 | + channel, |
| 115 | + new_name=new_name, |
| 116 | + clone_channels=deep_clone_category, |
| 117 | + reason=f"Channel {channel.name} ({channel.mention}) cloned as " |
| 118 | + f"{clone.name} ({clone.mention}) upon request by " # type: ignore |
| 119 | + f"{ctx.author.name} ({ctx.author.mention})", |
| 120 | + ) |
| 121 | + elif isinstance(channel, discord.ForumChannel): |
| 122 | + clone = await clone_forum( |
| 123 | + channel, |
| 124 | + reason=f"Channel created as clone of #{channel.name} " |
| 125 | + f"({channel.mention}) upon request by {ctx.author.name} " |
| 126 | + f"({ctx.author.mention})", |
| 127 | + ) |
| 128 | + |
| 129 | + |
| 130 | +async def setup(bot: BotT, color: int | discord.Color = 0): |
| 131 | + await bot.add_cog(ChannelManagerCog(bot, theme_color=color)) |
0 commit comments