mirror of
				https://github.com/Rapptz/discord.py.git
				synced 2025-10-30 04:52:56 +00:00 
			
		
		
		
	Add category support.
This adds: * CategoryChannel, which represents a category * Guild.by_category() which traverses the channels grouping by category * Guild.categories to get a list of categories * abc.GuildChannel.category to get the category a channel belongs to * sync_permissions keyword argument to abc.GuildChannel.edit to sync permissions with a pre-existing or new category * category keyword argument to abc.GuildChannel.edit to move a channel to a category
This commit is contained in:
		| @@ -188,7 +188,7 @@ class GuildChannel: | |||||||
|         return self.name |         return self.name | ||||||
|  |  | ||||||
|     @asyncio.coroutine |     @asyncio.coroutine | ||||||
|     def _move(self, position, *, reason): |     def _move(self, position, parent_id=None, lock_permissions=False, *, reason): | ||||||
|         if position < 0: |         if position < 0: | ||||||
|             raise InvalidArgument('Channel position cannot be less than 0.') |             raise InvalidArgument('Channel position cannot be less than 0.') | ||||||
|  |  | ||||||
| @@ -211,8 +211,45 @@ class GuildChannel: | |||||||
|             # add ourselves at our designated position |             # add ourselves at our designated position | ||||||
|             channels.insert(position, self) |             channels.insert(position, self) | ||||||
|  |  | ||||||
|         payload = [{'id': c.id, 'position': index } for index, c in enumerate(channels)] |         payload = [] | ||||||
|         yield from http.move_channel_position(self.guild.id, payload, reason=reason) |         for index, c in enumerate(channels): | ||||||
|  |             d = {'id': c.id, 'position': index} | ||||||
|  |             if parent_id is not _undefined and c.id == self.id: | ||||||
|  |                 d.update(parent_id=parent_id, lock_permissions=lock_permissions) | ||||||
|  |             payload.append(d) | ||||||
|  |  | ||||||
|  |         yield from http.bulk_channel_update(self.guild.id, payload, reason=reason) | ||||||
|  |         self.position = position | ||||||
|  |         if parent_id is not _undefined: | ||||||
|  |             self.category_id = int(parent_id) | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     def _edit(self, options, reason): | ||||||
|  |         try: | ||||||
|  |             parent = options.pop('category') | ||||||
|  |         except KeyError: | ||||||
|  |             parent_id = _undefined | ||||||
|  |         else: | ||||||
|  |             parent_id = parent and parent.id | ||||||
|  |  | ||||||
|  |         lock_permissions = options.pop('sync_permissions', False) | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             position = options.pop('position') | ||||||
|  |         except KeyError: | ||||||
|  |             if parent_id is not _undefined: | ||||||
|  |                 yield from self._move(self.position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason) | ||||||
|  |             elif lock_permissions and self.category_id is not None: | ||||||
|  |                 # if we're syncing permissions on a pre-existing channel category without changing it | ||||||
|  |                 # we need to update the permissions to point to the pre-existing category | ||||||
|  |                 category = self.guild.get_channel(self.category_id) | ||||||
|  |                 options['permission_overwrites'] = [c._asdict() for c in category._overwrites] | ||||||
|  |         else: | ||||||
|  |             yield from self._move(position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason) | ||||||
|  |  | ||||||
|  |         if options: | ||||||
|  |             data = yield from self._state.http.edit_channel(self.id, reason=reason, **options) | ||||||
|  |             self._update(self.guild, data) | ||||||
|  |  | ||||||
|     def _fill_overwrites(self, data): |     def _fill_overwrites(self, data): | ||||||
|         self._overwrites = [] |         self._overwrites = [] | ||||||
| @@ -322,6 +359,14 @@ class GuildChannel: | |||||||
|             ret.append((target, overwrite)) |             ret.append((target, overwrite)) | ||||||
|         return ret |         return ret | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def category(self): | ||||||
|  |         """Optional[:class:`CategoryChannel`]: The category this channel belongs to. | ||||||
|  |  | ||||||
|  |         If there is no category then this is ``None``. | ||||||
|  |         """ | ||||||
|  |         return self.guild.get_channel(self.category_id) | ||||||
|  |  | ||||||
|     def permissions_for(self, member): |     def permissions_for(self, member): | ||||||
|         """Handles permission resolution for the current :class:`Member`. |         """Handles permission resolution for the current :class:`Member`. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ import discord.abc | |||||||
| import time | import time | ||||||
| import asyncio | import asyncio | ||||||
|  |  | ||||||
| __all__ = ('TextChannel', 'VoiceChannel', 'DMChannel', 'GroupChannel', '_channel_factory') | __all__ = ('TextChannel', 'VoiceChannel', 'DMChannel', 'CategoryChannel', 'GroupChannel', '_channel_factory') | ||||||
|  |  | ||||||
| @asyncio.coroutine | @asyncio.coroutine | ||||||
| def _single_delete_strategy(messages): | def _single_delete_strategy(messages): | ||||||
| @@ -71,6 +71,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): | |||||||
|         The guild the channel belongs to. |         The guild the channel belongs to. | ||||||
|     id: int |     id: int | ||||||
|         The channel ID. |         The channel ID. | ||||||
|  |     category_id: int | ||||||
|  |         The category channel ID this channel belongs to. | ||||||
|     topic: Optional[str] |     topic: Optional[str] | ||||||
|         The channel's topic. None if it doesn't exist. |         The channel's topic. None if it doesn't exist. | ||||||
|     position: int |     position: int | ||||||
| @@ -79,7 +81,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     __slots__ = ( 'name', 'id', 'guild', 'topic', '_state', 'nsfw', |     __slots__ = ( 'name', 'id', 'guild', 'topic', '_state', 'nsfw', | ||||||
|                   'position', '_overwrites' ) |                   'category_id', 'position', '_overwrites' ) | ||||||
|  |  | ||||||
|     def __init__(self, *, state, guild, data): |     def __init__(self, *, state, guild, data): | ||||||
|         self._state = state |         self._state = state | ||||||
| @@ -92,6 +94,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): | |||||||
|     def _update(self, guild, data): |     def _update(self, guild, data): | ||||||
|         self.guild = guild |         self.guild = guild | ||||||
|         self.name = data['name'] |         self.name = data['name'] | ||||||
|  |         self.category_id = utils._get_as_snowflake(data, 'parent_id') | ||||||
|         self.topic = data.get('topic') |         self.topic = data.get('topic') | ||||||
|         self.position = data['position'] |         self.position = data['position'] | ||||||
|         self.nsfw = data.get('nsfw', False) |         self.nsfw = data.get('nsfw', False) | ||||||
| @@ -140,6 +143,12 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): | |||||||
|             The new channel's position. |             The new channel's position. | ||||||
|         nsfw: bool |         nsfw: bool | ||||||
|             To mark the channel as NSFW or not. |             To mark the channel as NSFW or not. | ||||||
|  |         sync_permissions: bool | ||||||
|  |             Whether to sync permissions with the channel's new or pre-existing | ||||||
|  |             category. Defaults to ``False``. | ||||||
|  |         category: Optional[:class:`CategoryChannel`] | ||||||
|  |             The new category for this channel. Can be ``None`` to remove the | ||||||
|  |             category. | ||||||
|         reason: Optional[str] |         reason: Optional[str] | ||||||
|             The reason for editing this channel. Shows up on the audit log. |             The reason for editing this channel. Shows up on the audit log. | ||||||
|  |  | ||||||
| @@ -152,17 +161,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): | |||||||
|         HTTPException |         HTTPException | ||||||
|             Editing the channel failed. |             Editing the channel failed. | ||||||
|         """ |         """ | ||||||
|         try: |         yield from self._edit(options, reason=reason) | ||||||
|             position = options.pop('position') |  | ||||||
|         except KeyError: |  | ||||||
|             pass |  | ||||||
|         else: |  | ||||||
|             yield from self._move(position, reason=reason) |  | ||||||
|             self.position = position |  | ||||||
|  |  | ||||||
|         if options: |  | ||||||
|             data = yield from self._state.http.edit_channel(self.id, reason=reason, **options) |  | ||||||
|             self._update(self.guild, data) |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |     @asyncio.coroutine | ||||||
|     def delete_messages(self, messages): |     def delete_messages(self, messages): | ||||||
| @@ -411,6 +410,8 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): | |||||||
|         The guild the channel belongs to. |         The guild the channel belongs to. | ||||||
|     id: int |     id: int | ||||||
|         The channel ID. |         The channel ID. | ||||||
|  |     category_id: int | ||||||
|  |         The category channel ID this channel belongs to. | ||||||
|     position: int |     position: int | ||||||
|         The position in the channel list. This is a number that starts at 0. e.g. the |         The position in the channel list. This is a number that starts at 0. e.g. the | ||||||
|         top channel is position 0. |         top channel is position 0. | ||||||
| @@ -421,7 +422,7 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     __slots__ = ('name', 'id', 'guild', 'bitrate',  'user_limit', |     __slots__ = ('name', 'id', 'guild', 'bitrate',  'user_limit', | ||||||
|                  '_state', 'position', '_overwrites' ) |                  '_state', 'position', '_overwrites', 'category_id' ) | ||||||
|  |  | ||||||
|     def __init__(self, *, state, guild, data): |     def __init__(self, *, state, guild, data): | ||||||
|         self._state = state |         self._state = state | ||||||
| @@ -440,6 +441,7 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): | |||||||
|     def _update(self, guild, data): |     def _update(self, guild, data): | ||||||
|         self.guild = guild |         self.guild = guild | ||||||
|         self.name = data['name'] |         self.name = data['name'] | ||||||
|  |         self.category_id = utils._get_as_snowflake(data, 'parent_id') | ||||||
|         self.position = data['position'] |         self.position = data['position'] | ||||||
|         self.bitrate = data.get('bitrate') |         self.bitrate = data.get('bitrate') | ||||||
|         self.user_limit = data.get('user_limit') |         self.user_limit = data.get('user_limit') | ||||||
| @@ -473,6 +475,12 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): | |||||||
|             The new channel's user limit. |             The new channel's user limit. | ||||||
|         position: int |         position: int | ||||||
|             The new channel's position. |             The new channel's position. | ||||||
|  |         sync_permissions: bool | ||||||
|  |             Whether to sync permissions with the channel's new or pre-existing | ||||||
|  |             category. Defaults to ``False``. | ||||||
|  |         category: Optional[:class:`CategoryChannel`] | ||||||
|  |             The new category for this channel. Can be ``None`` to remove the | ||||||
|  |             category. | ||||||
|         reason: Optional[str] |         reason: Optional[str] | ||||||
|             The reason for editing this channel. Shows up on the audit log. |             The reason for editing this channel. Shows up on the audit log. | ||||||
|  |  | ||||||
| @@ -484,6 +492,97 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): | |||||||
|             Editing the channel failed. |             Editing the channel failed. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  |         yield from self._edit(options, reason=reason) | ||||||
|  |  | ||||||
|  | class CategoryChannel(discord.abc.GuildChannel, Hashable): | ||||||
|  |     """Represents a Discord channel category. | ||||||
|  |  | ||||||
|  |     These are useful to group channels to logical compartments. | ||||||
|  |  | ||||||
|  |     .. container:: operations | ||||||
|  |  | ||||||
|  |         .. describe:: x == y | ||||||
|  |  | ||||||
|  |             Checks if two channels are equal. | ||||||
|  |  | ||||||
|  |         .. describe:: x != y | ||||||
|  |  | ||||||
|  |             Checks if two channels are not equal. | ||||||
|  |  | ||||||
|  |         .. describe:: hash(x) | ||||||
|  |  | ||||||
|  |             Returns the category's hash. | ||||||
|  |  | ||||||
|  |         .. describe:: str(x) | ||||||
|  |  | ||||||
|  |             Returns the category's name. | ||||||
|  |  | ||||||
|  |     Attributes | ||||||
|  |     ----------- | ||||||
|  |     name: str | ||||||
|  |         The category name. | ||||||
|  |     guild: :class:`Guild` | ||||||
|  |         The guild the category belongs to. | ||||||
|  |     id: int | ||||||
|  |         The category channel ID. | ||||||
|  |     position: int | ||||||
|  |         The position in the category list. This is a number that starts at 0. e.g. the | ||||||
|  |         top category is position 0. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     __slots__ = ('name', 'id', 'guild', 'nsfw', '_state', 'position', '_overwrites', 'category_id') | ||||||
|  |  | ||||||
|  |     def __init__(self, *, state, guild, data): | ||||||
|  |         self._state = state | ||||||
|  |         self.id = int(data['id']) | ||||||
|  |         self._update(guild, data) | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         return '<CategoryChannel id={0.id} name={0.name!r} position={0.position}>'.format(self) | ||||||
|  |  | ||||||
|  |     def _update(self, guild, data): | ||||||
|  |         self.guild = guild | ||||||
|  |         self.name = data['name'] | ||||||
|  |         self.category_id = utils._get_as_snowflake(data, 'parent_id') | ||||||
|  |         self.nsfw = data.get('nsfw', False) | ||||||
|  |         self.position = data['position'] | ||||||
|  |         self._fill_overwrites(data) | ||||||
|  |  | ||||||
|  |     def is_nsfw(self): | ||||||
|  |         """Checks if the category is NSFW.""" | ||||||
|  |         n = self.name | ||||||
|  |         return self.nsfw or n == 'nsfw' or n[:5] == 'nsfw-' | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     def edit(self, *, reason=None, **options): | ||||||
|  |         """|coro| | ||||||
|  |  | ||||||
|  |         Edits the channel. | ||||||
|  |  | ||||||
|  |         You must have the :attr:`Permissions.manage_channel` permission to | ||||||
|  |         use this. | ||||||
|  |  | ||||||
|  |         Parameters | ||||||
|  |         ---------- | ||||||
|  |         name: str | ||||||
|  |             The new category's name. | ||||||
|  |         position: int | ||||||
|  |             The new category's position. | ||||||
|  |         nsfw: bool | ||||||
|  |             To mark the category as NSFW or not. | ||||||
|  |         reason: Optional[str] | ||||||
|  |             The reason for editing this category. Shows up on the audit log. | ||||||
|  |  | ||||||
|  |         Raises | ||||||
|  |         ------ | ||||||
|  |         InvalidArgument | ||||||
|  |             If position is less than 0 or greater than the number of categories. | ||||||
|  |         Forbidden | ||||||
|  |             You do not have permissions to edit the category. | ||||||
|  |         HTTPException | ||||||
|  |             Editing the category failed. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             position = options.pop('position') |             position = options.pop('position') | ||||||
|         except KeyError: |         except KeyError: | ||||||
| @@ -496,6 +595,19 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): | |||||||
|             data = yield from self._state.http.edit_channel(self.id, reason=reason, **options) |             data = yield from self._state.http.edit_channel(self.id, reason=reason, **options) | ||||||
|             self._update(self.guild, data) |             self._update(self.guild, data) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def channels(self): | ||||||
|  |         """List[:class:`abc.GuildChannel`]: Returns the channels that are under this category. | ||||||
|  |  | ||||||
|  |         These are sorted by the official Discord UI, which places voice channels below the text channels. | ||||||
|  |         """ | ||||||
|  |         def comparator(channel): | ||||||
|  |             return (not isinstance(channel, TextChannel), channel.position) | ||||||
|  |  | ||||||
|  |         ret = [c for c in self.guild.channels if c.category_id == self.id] | ||||||
|  |         ret.sort(key=comparator) | ||||||
|  |         return ret | ||||||
|  |  | ||||||
| class DMChannel(discord.abc.Messageable, Hashable): | class DMChannel(discord.abc.Messageable, Hashable): | ||||||
|     """Represents a Discord direct message channel. |     """Represents a Discord direct message channel. | ||||||
|  |  | ||||||
| @@ -810,6 +922,8 @@ def _channel_factory(channel_type): | |||||||
|         return VoiceChannel, value |         return VoiceChannel, value | ||||||
|     elif value is ChannelType.private: |     elif value is ChannelType.private: | ||||||
|         return DMChannel, value |         return DMChannel, value | ||||||
|  |     elif value is ChannelType.category: | ||||||
|  |         return CategoryChannel, value | ||||||
|     elif value is ChannelType.group: |     elif value is ChannelType.group: | ||||||
|         return GroupChannel, value |         return GroupChannel, value | ||||||
|     else: |     else: | ||||||
|   | |||||||
| @@ -31,10 +31,11 @@ __all__ = ['ChannelType', 'MessageType', 'VoiceRegion', 'VerificationLevel', | |||||||
|            'AuditLogAction', 'AuditLogActionCategory', 'UserFlags', ] |            'AuditLogAction', 'AuditLogActionCategory', 'UserFlags', ] | ||||||
|  |  | ||||||
| class ChannelType(Enum): | class ChannelType(Enum): | ||||||
|     text    = 0 |     text     = 0 | ||||||
|     private = 1 |     private  = 1 | ||||||
|     voice   = 2 |     voice    = 2 | ||||||
|     group   = 3 |     group    = 3 | ||||||
|  |     category = 4 | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.name |         return self.name | ||||||
|   | |||||||
| @@ -250,11 +250,11 @@ class Guild(Hashable): | |||||||
|             channels = data['channels'] |             channels = data['channels'] | ||||||
|             for c in channels: |             for c in channels: | ||||||
|                 if c['type'] == ChannelType.text.value: |                 if c['type'] == ChannelType.text.value: | ||||||
|                     channel = TextChannel(guild=self, data=c, state=self._state) |                     self._add_channel(TextChannel(guild=self, data=c, state=self._state)) | ||||||
|                     self._add_channel(channel) |  | ||||||
|                 elif c['type'] == ChannelType.voice.value: |                 elif c['type'] == ChannelType.voice.value: | ||||||
|                     channel = VoiceChannel(guild=self, data=c, state=self._state) |                     self._add_channel(VoiceChannel(guild=self, data=c, state=self._state)) | ||||||
|                     self._add_channel(channel) |                 elif c['type'] == ChannelType.category.value: | ||||||
|  |                     self._add_channel(CategoryChannel(guild=self, data=c, state=self._state)) | ||||||
|  |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
| @@ -309,6 +309,52 @@ class Guild(Hashable): | |||||||
|         r.sort(key=lambda c: c.position) |         r.sort(key=lambda c: c.position) | ||||||
|         return r |         return r | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def categories(self): | ||||||
|  |         """List[:class:`CategoryChannel`]: A list of categories that belongs to this guild. | ||||||
|  |  | ||||||
|  |         This is sorted by the position and are in UI order from top to bottom. | ||||||
|  |         """ | ||||||
|  |         r = [ch for ch in self._channels.values() if isinstance(ch, CategoryChannel)] | ||||||
|  |         r.sort(key=lambda c: c.position) | ||||||
|  |         return r | ||||||
|  |  | ||||||
|  |     def by_category(self): | ||||||
|  |         """Returns every :class:`CategoryChannel` and their associated channels. | ||||||
|  |  | ||||||
|  |         These channels and categories are sorted in the official Discord UI order. | ||||||
|  |  | ||||||
|  |         If the channels do not have a category, then the first element of the tuple is | ||||||
|  |         ``None``. | ||||||
|  |  | ||||||
|  |         Returns | ||||||
|  |         -------- | ||||||
|  |         List[Tuple[Optional[:class:`CategoryChannel`], List[:class:`abc.GuildChannel`]]]: | ||||||
|  |             The categories and their associated channels. | ||||||
|  |         """ | ||||||
|  |         grouped = {} | ||||||
|  |         for channel in self._channels.values(): | ||||||
|  |             if isinstance(channel, CategoryChannel): | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             try: | ||||||
|  |                 channels = grouped[channel.category_id] | ||||||
|  |             except KeyError: | ||||||
|  |                 channels = grouped[channel.category_id] = [] | ||||||
|  |  | ||||||
|  |             channels.append(channel) | ||||||
|  |  | ||||||
|  |         def key(t): | ||||||
|  |             k, v = t | ||||||
|  |             return (k.position if k else -1, v) | ||||||
|  |  | ||||||
|  |         _get = self._channels.get | ||||||
|  |         as_list = [(_get(k), v) for k, v in grouped.items()] | ||||||
|  |         as_list.sort(key=key) | ||||||
|  |         for _, channels in as_list: | ||||||
|  |             channels.sort(key=lambda c: c.position) | ||||||
|  |         return as_list | ||||||
|  |  | ||||||
|     def get_channel(self, channel_id): |     def get_channel(self, channel_id): | ||||||
|         """Returns a :class:`abc.GuildChannel` with the given ID. If not found, returns None.""" |         """Returns a :class:`abc.GuildChannel` with the given ID. If not found, returns None.""" | ||||||
|         return self._channels.get(channel_id) |         return self._channels.get(channel_id) | ||||||
|   | |||||||
| @@ -500,16 +500,16 @@ class HTTPClient: | |||||||
|  |  | ||||||
|     def edit_channel(self, channel_id, *, reason=None, **options): |     def edit_channel(self, channel_id, *, reason=None, **options): | ||||||
|         r = Route('PATCH', '/channels/{channel_id}', channel_id=channel_id) |         r = Route('PATCH', '/channels/{channel_id}', channel_id=channel_id) | ||||||
|         valid_keys = ('name', 'topic', 'bitrate', 'nsfw', 'user_limit', 'position') |         valid_keys = ('name', 'topic', 'bitrate', 'nsfw', 'user_limit', 'position', 'permission_overwrites') | ||||||
|         payload = { |         payload = { | ||||||
|             k: v for k, v in options.items() if k in valid_keys |             k: v for k, v in options.items() if k in valid_keys | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return self.request(r, reason=reason, json=payload) |         return self.request(r, reason=reason, json=payload) | ||||||
|  |  | ||||||
|     def move_channel_position(self, guild_id, positions, *, reason=None): |     def bulk_channel_update(self, guild_id, data, *, reason=None): | ||||||
|         r = Route('PATCH', '/guilds/{guild_id}/channels', guild_id=guild_id) |         r = Route('PATCH', '/guilds/{guild_id}/channels', guild_id=guild_id) | ||||||
|         return self.request(r, json=positions, reason=reason) |         return self.request(r, json=data, reason=reason) | ||||||
|  |  | ||||||
|     def create_channel(self, guild_id, name, channe_type, permission_overwrites=None, *, reason=None): |     def create_channel(self, guild_id, name, channe_type, permission_overwrites=None, *, reason=None): | ||||||
|         payload = { |         payload = { | ||||||
|   | |||||||
| @@ -484,6 +484,10 @@ class ConnectionState: | |||||||
|  |  | ||||||
|     def parse_channel_create(self, data): |     def parse_channel_create(self, data): | ||||||
|         factory, ch_type = _channel_factory(data['type']) |         factory, ch_type = _channel_factory(data['type']) | ||||||
|  |         if factory is None: | ||||||
|  |             log.warning('CHANNEL_CREATE referencing an unknown channel type %s. Discarding.', data['type']) | ||||||
|  |             return | ||||||
|  |  | ||||||
|         channel = None |         channel = None | ||||||
|  |  | ||||||
|         if ch_type in (ChannelType.group, ChannelType.private): |         if ch_type in (ChannelType.group, ChannelType.private): | ||||||
| @@ -496,10 +500,6 @@ class ConnectionState: | |||||||
|             guild_id = utils._get_as_snowflake(data, 'guild_id') |             guild_id = utils._get_as_snowflake(data, 'guild_id') | ||||||
|             guild = self._get_guild(guild_id) |             guild = self._get_guild(guild_id) | ||||||
|             if guild is not None: |             if guild is not None: | ||||||
|                 if factory is None: |  | ||||||
|                     log.warning('CHANNEL_CREATE referencing an unknown channel type %s. Discarding.', data['type']) |  | ||||||
|                     return |  | ||||||
|  |  | ||||||
|                 channel = factory(guild=guild, state=self, data=data) |                 channel = factory(guild=guild, state=self, data=data) | ||||||
|                 guild._add_channel(channel) |                 guild._add_channel(channel) | ||||||
|                 self.dispatch('guild_channel_create', channel) |                 self.dispatch('guild_channel_create', channel) | ||||||
|   | |||||||
| @@ -1912,6 +1912,13 @@ VoiceChannel | |||||||
|     :members: |     :members: | ||||||
|     :inherited-members: |     :inherited-members: | ||||||
|  |  | ||||||
|  | CategoryChannel | ||||||
|  | ~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | .. autoclass:: CategoryChannel() | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |  | ||||||
| DMChannel | DMChannel | ||||||
| ~~~~~~~~~ | ~~~~~~~~~ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -366,11 +366,15 @@ They will be enumerated here. | |||||||
| **Added** | **Added** | ||||||
|  |  | ||||||
| - :class:`Attachment` to represent a discord attachment. | - :class:`Attachment` to represent a discord attachment. | ||||||
|  | - :class:`CategoryChannel` to represent a channel category. | ||||||
| - :attr:`VoiceChannel.members` for fetching members connected to a voice channel. | - :attr:`VoiceChannel.members` for fetching members connected to a voice channel. | ||||||
| - :attr:`TextChannel.members` for fetching members that can see the channel. | - :attr:`TextChannel.members` for fetching members that can see the channel. | ||||||
| - :attr:`Role.members` for fetching members that have the role. | - :attr:`Role.members` for fetching members that have the role. | ||||||
| - :attr:`Guild.text_channels` for fetching text channels only. | - :attr:`Guild.text_channels` for fetching text channels only. | ||||||
| - :attr:`Guild.voice_channels` for fetching voice channels only. | - :attr:`Guild.voice_channels` for fetching voice channels only. | ||||||
|  | - :attr:`Guild.categories` for fetching channel categories only. | ||||||
|  | - :attr:`TextChannel.category` and :attr:`VoiceChannel.category` to get the category a channel belongs to. | ||||||
|  | - :meth:`Guild.by_category` to get channels grouped by their category. | ||||||
| - :attr:`Guild.chunked` to check member chunking status. | - :attr:`Guild.chunked` to check member chunking status. | ||||||
| - :attr:`Guild.explicit_content_filter` to fetch the content filter. | - :attr:`Guild.explicit_content_filter` to fetch the content filter. | ||||||
| - :attr:`Guild.shard_id` to get a guild's Shard ID if you're sharding. | - :attr:`Guild.shard_id` to get a guild's Shard ID if you're sharding. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user