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:
		@@ -35,7 +35,7 @@ import discord.abc
 | 
			
		||||
import time
 | 
			
		||||
import asyncio
 | 
			
		||||
 | 
			
		||||
__all__ = ('TextChannel', 'VoiceChannel', 'DMChannel', 'GroupChannel', '_channel_factory')
 | 
			
		||||
__all__ = ('TextChannel', 'VoiceChannel', 'DMChannel', 'CategoryChannel', 'GroupChannel', '_channel_factory')
 | 
			
		||||
 | 
			
		||||
@asyncio.coroutine
 | 
			
		||||
def _single_delete_strategy(messages):
 | 
			
		||||
@@ -71,6 +71,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
 | 
			
		||||
        The guild the channel belongs to.
 | 
			
		||||
    id: int
 | 
			
		||||
        The channel ID.
 | 
			
		||||
    category_id: int
 | 
			
		||||
        The category channel ID this channel belongs to.
 | 
			
		||||
    topic: Optional[str]
 | 
			
		||||
        The channel's topic. None if it doesn't exist.
 | 
			
		||||
    position: int
 | 
			
		||||
@@ -79,7 +81,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __slots__ = ( 'name', 'id', 'guild', 'topic', '_state', 'nsfw',
 | 
			
		||||
                  'position', '_overwrites' )
 | 
			
		||||
                  'category_id', 'position', '_overwrites' )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *, state, guild, data):
 | 
			
		||||
        self._state = state
 | 
			
		||||
@@ -92,6 +94,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
 | 
			
		||||
    def _update(self, guild, data):
 | 
			
		||||
        self.guild = guild
 | 
			
		||||
        self.name = data['name']
 | 
			
		||||
        self.category_id = utils._get_as_snowflake(data, 'parent_id')
 | 
			
		||||
        self.topic = data.get('topic')
 | 
			
		||||
        self.position = data['position']
 | 
			
		||||
        self.nsfw = data.get('nsfw', False)
 | 
			
		||||
@@ -140,6 +143,12 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
 | 
			
		||||
            The new channel's position.
 | 
			
		||||
        nsfw: bool
 | 
			
		||||
            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]
 | 
			
		||||
            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
 | 
			
		||||
            Editing the channel failed.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            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)
 | 
			
		||||
        yield from self._edit(options, reason=reason)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def delete_messages(self, messages):
 | 
			
		||||
@@ -411,6 +410,8 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
 | 
			
		||||
        The guild the channel belongs to.
 | 
			
		||||
    id: int
 | 
			
		||||
        The channel ID.
 | 
			
		||||
    category_id: int
 | 
			
		||||
        The category channel ID this channel belongs to.
 | 
			
		||||
    position: int
 | 
			
		||||
        The position in the channel list. This is a number that starts at 0. e.g. the
 | 
			
		||||
        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',
 | 
			
		||||
                 '_state', 'position', '_overwrites' )
 | 
			
		||||
                 '_state', 'position', '_overwrites', 'category_id' )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *, state, guild, data):
 | 
			
		||||
        self._state = state
 | 
			
		||||
@@ -440,6 +441,7 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
 | 
			
		||||
    def _update(self, guild, data):
 | 
			
		||||
        self.guild = guild
 | 
			
		||||
        self.name = data['name']
 | 
			
		||||
        self.category_id = utils._get_as_snowflake(data, 'parent_id')
 | 
			
		||||
        self.position = data['position']
 | 
			
		||||
        self.bitrate = data.get('bitrate')
 | 
			
		||||
        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.
 | 
			
		||||
        position: int
 | 
			
		||||
            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]
 | 
			
		||||
            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.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
            position = options.pop('position')
 | 
			
		||||
        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)
 | 
			
		||||
            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):
 | 
			
		||||
    """Represents a Discord direct message channel.
 | 
			
		||||
 | 
			
		||||
@@ -810,6 +922,8 @@ def _channel_factory(channel_type):
 | 
			
		||||
        return VoiceChannel, value
 | 
			
		||||
    elif value is ChannelType.private:
 | 
			
		||||
        return DMChannel, value
 | 
			
		||||
    elif value is ChannelType.category:
 | 
			
		||||
        return CategoryChannel, value
 | 
			
		||||
    elif value is ChannelType.group:
 | 
			
		||||
        return GroupChannel, value
 | 
			
		||||
    else:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user