From a335ed972c679d74d2b97784b1b1ab8172ee332b Mon Sep 17 00:00:00 2001 From: Slipstream Date: Sun, 15 Jun 2025 18:49:51 -0600 Subject: [PATCH] Add guild channel creation methods (#102) --- disagreement/http.py | 16 ++++ disagreement/models.py | 54 +++++++++++++ tests/test_guild_channel_create.py | 126 +++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 tests/test_guild_channel_create.py diff --git a/disagreement/http.py b/disagreement/http.py index a2aaddd..b67068a 100644 --- a/disagreement/http.py +++ b/disagreement/http.py @@ -711,6 +711,22 @@ class HTTPClient: """Fetches a channel by ID.""" return await self.request("GET", f"/channels/{channel_id}") + async def create_guild_channel( + self, + guild_id: "Snowflake", + payload: Dict[str, Any], + reason: Optional[str] = None, + ) -> Dict[str, Any]: + """Creates a new channel in the specified guild.""" + + headers = {"X-Audit-Log-Reason": reason} if reason else None + return await self.request( + "POST", + f"/guilds/{guild_id}/channels", + payload=payload, + custom_headers=headers, + ) + async def get_channel_invites( self, channel_id: "Snowflake" ) -> List[Dict[str, Any]]: diff --git a/disagreement/models.py b/disagreement/models.py index 10d92c5..d7c83e6 100644 --- a/disagreement/models.py +++ b/disagreement/models.py @@ -1378,6 +1378,60 @@ class Guild: self.id, days=days, compute_count=compute_count ) + async def create_text_channel( + self, + name: str, + *, + reason: Optional[str] = None, + **options: Any, + ) -> "TextChannel": + """|coro| Create a new text channel in this guild.""" + + payload: Dict[str, Any] = {"name": name, "type": ChannelType.GUILD_TEXT.value} + payload.update(options) + data = await self._client._http.create_guild_channel( + self.id, payload, reason=reason + ) + return cast("TextChannel", self._client.parse_channel(data)) + + async def create_voice_channel( + self, + name: str, + *, + reason: Optional[str] = None, + **options: Any, + ) -> "VoiceChannel": + """|coro| Create a new voice channel in this guild.""" + + payload: Dict[str, Any] = { + "name": name, + "type": ChannelType.GUILD_VOICE.value, + } + payload.update(options) + data = await self._client._http.create_guild_channel( + self.id, payload, reason=reason + ) + return cast("VoiceChannel", self._client.parse_channel(data)) + + async def create_category( + self, + name: str, + *, + reason: Optional[str] = None, + **options: Any, + ) -> "CategoryChannel": + """|coro| Create a new category channel in this guild.""" + + payload: Dict[str, Any] = { + "name": name, + "type": ChannelType.GUILD_CATEGORY.value, + } + payload.update(options) + data = await self._client._http.create_guild_channel( + self.id, payload, reason=reason + ) + return cast("CategoryChannel", self._client.parse_channel(data)) + class Channel: """Base class for Discord channels.""" diff --git a/tests/test_guild_channel_create.py b/tests/test_guild_channel_create.py new file mode 100644 index 0000000..d76b321 --- /dev/null +++ b/tests/test_guild_channel_create.py @@ -0,0 +1,126 @@ +import pytest +from types import SimpleNamespace +from unittest.mock import AsyncMock + +from disagreement.http import HTTPClient +from disagreement.client import Client +from disagreement.models import Guild, TextChannel, VoiceChannel, CategoryChannel +from disagreement.enums import ( + VerificationLevel, + MessageNotificationLevel, + ExplicitContentFilterLevel, + MFALevel, + GuildNSFWLevel, + PremiumTier, + ChannelType, +) + + +def _guild_data(): + return { + "id": "1", + "name": "g", + "owner_id": "1", + "afk_timeout": 60, + "verification_level": VerificationLevel.NONE.value, + "default_message_notifications": MessageNotificationLevel.ALL_MESSAGES.value, + "explicit_content_filter": ExplicitContentFilterLevel.DISABLED.value, + "roles": [], + "emojis": [], + "features": [], + "mfa_level": MFALevel.NONE.value, + "system_channel_flags": 0, + "premium_tier": PremiumTier.NONE.value, + "nsfw_level": GuildNSFWLevel.DEFAULT.value, + } + + +@pytest.mark.asyncio +async def test_http_create_guild_channel_calls_request(): + http = HTTPClient(token="t") + http.request = AsyncMock(return_value={}) + payload = {"name": "chan", "type": ChannelType.GUILD_TEXT.value} + + await http.create_guild_channel("1", payload, reason="r") + + http.request.assert_called_once_with( + "POST", + "/guilds/1/channels", + payload=payload, + custom_headers={"X-Audit-Log-Reason": "r"}, + ) + + +@pytest.mark.asyncio +async def test_guild_create_text_channel_returns_channel(): + http = SimpleNamespace( + create_guild_channel=AsyncMock( + return_value={ + "id": "10", + "type": ChannelType.GUILD_TEXT.value, + "guild_id": "1", + "permission_overwrites": [], + } + ) + ) + client = Client(token="t") + client._http = http + guild = Guild(_guild_data(), client_instance=client) + + channel = await guild.create_text_channel("general") + + http.create_guild_channel.assert_awaited_once_with( + "1", {"name": "general", "type": ChannelType.GUILD_TEXT.value}, reason=None + ) + assert isinstance(channel, TextChannel) + assert client._channels.get("10") is channel + + +@pytest.mark.asyncio +async def test_guild_create_voice_channel_returns_channel(): + http = SimpleNamespace( + create_guild_channel=AsyncMock( + return_value={ + "id": "11", + "type": ChannelType.GUILD_VOICE.value, + "guild_id": "1", + "permission_overwrites": [], + } + ) + ) + client = Client(token="t") + client._http = http + guild = Guild(_guild_data(), client_instance=client) + + channel = await guild.create_voice_channel("Voice") + + http.create_guild_channel.assert_awaited_once_with( + "1", {"name": "Voice", "type": ChannelType.GUILD_VOICE.value}, reason=None + ) + assert isinstance(channel, VoiceChannel) + assert client._channels.get("11") is channel + + +@pytest.mark.asyncio +async def test_guild_create_category_returns_channel(): + http = SimpleNamespace( + create_guild_channel=AsyncMock( + return_value={ + "id": "12", + "type": ChannelType.GUILD_CATEGORY.value, + "guild_id": "1", + "permission_overwrites": [], + } + ) + ) + client = Client(token="t") + client._http = http + guild = Guild(_guild_data(), client_instance=client) + + channel = await guild.create_category("Cat") + + http.create_guild_channel.assert_awaited_once_with( + "1", {"name": "Cat", "type": ChannelType.GUILD_CATEGORY.value}, reason=None + ) + assert isinstance(channel, CategoryChannel) + assert client._channels.get("12") is channel