Make roles and guilds stateful.
This commit is contained in:
parent
d1d54a468a
commit
a7a60e433b
309
discord/guild.py
309
discord/guild.py
@ -24,6 +24,9 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .member import Member, VoiceState
|
from .member import Member, VoiceState
|
||||||
@ -32,8 +35,8 @@ from .game import Game
|
|||||||
from .channel import *
|
from .channel import *
|
||||||
from .enums import GuildRegion, Status, ChannelType, try_enum, VerificationLevel
|
from .enums import GuildRegion, Status, ChannelType, try_enum, VerificationLevel
|
||||||
from .mixins import Hashable
|
from .mixins import Hashable
|
||||||
|
from .user import User
|
||||||
import copy
|
from .invite import Invite
|
||||||
|
|
||||||
class Guild(Hashable):
|
class Guild(Hashable):
|
||||||
"""Represents a Discord guild.
|
"""Represents a Discord guild.
|
||||||
@ -375,3 +378,305 @@ class Guild(Hashable):
|
|||||||
return m.nick == name or m.name == name
|
return m.nick == name or m.name == name
|
||||||
|
|
||||||
return utils.find(pred, members)
|
return utils.find(pred, members)
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def leave(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Leaves the guild.
|
||||||
|
|
||||||
|
Note
|
||||||
|
--------
|
||||||
|
You cannot leave the guild that you own, you must delete it instead
|
||||||
|
via :meth:`delete`.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
HTTPException
|
||||||
|
Leaving the guild failed.
|
||||||
|
"""
|
||||||
|
yield from self._state.http.leave_guild(self.id)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def delete(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Deletes the guild. You must be the guild owner to delete the
|
||||||
|
guild.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
HTTPException
|
||||||
|
Deleting the guild failed.
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to delete the guild.
|
||||||
|
"""
|
||||||
|
|
||||||
|
yield from self._state.http.delete_guild(self.id)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def edit(self, **fields):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edits the guild.
|
||||||
|
|
||||||
|
You must have the :attr:`Permissions.manage_guild` permission
|
||||||
|
to edit the guild.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: str
|
||||||
|
The new name of the guild.
|
||||||
|
icon: bytes
|
||||||
|
A *bytes-like* object representing the icon. Only PNG/JPEG supported.
|
||||||
|
Could be ``None`` to denote removal of the icon.
|
||||||
|
region: :class:`GuildRegion`
|
||||||
|
The new region for the guild's voice communication.
|
||||||
|
afk_channel: :class:`VoiceChannel`
|
||||||
|
The new channel that is the AFK channel. Could be ``None`` for no AFK channel.
|
||||||
|
afk_timeout: int
|
||||||
|
The number of seconds until someone is moved to the AFK channel.
|
||||||
|
owner: :class:`Member`
|
||||||
|
The new owner of the guild to transfer ownership to. Note that you must
|
||||||
|
be owner of the guild to do this.
|
||||||
|
verification_level: :class:`VerificationLevel`
|
||||||
|
The new verification level for the guild.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to edit the guild.
|
||||||
|
HTTPException
|
||||||
|
Editing the guild failed.
|
||||||
|
InvalidArgument
|
||||||
|
The image format passed in to ``icon`` is invalid. It must be
|
||||||
|
PNG or JPG. This is also raised if you are not the owner of the
|
||||||
|
guild and request an ownership transfer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
icon_bytes = fields['icon']
|
||||||
|
except KeyError:
|
||||||
|
icon = self.icon
|
||||||
|
else:
|
||||||
|
if icon_bytes is not None:
|
||||||
|
icon = utils._bytes_to_base64_data(icon_bytes)
|
||||||
|
else:
|
||||||
|
icon = None
|
||||||
|
|
||||||
|
fields['icon'] = icon
|
||||||
|
if 'afk_channel' in fields:
|
||||||
|
fields['afk_channel_id'] = fields['afk_channel'].id
|
||||||
|
|
||||||
|
if 'owner' in fields:
|
||||||
|
if self.owner != self.me:
|
||||||
|
raise InvalidArgument('To transfer ownership you must be the owner of the guild.')
|
||||||
|
|
||||||
|
fields['owner_id'] = fields['owner'].id
|
||||||
|
|
||||||
|
if 'region' in fields:
|
||||||
|
fields['region'] = str(fields['region'])
|
||||||
|
|
||||||
|
level = fields.get('verification_level', self.verification_level)
|
||||||
|
if not isinstance(level, VerificationLevel):
|
||||||
|
raise InvalidArgument('verification_level field must of type VerificationLevel')
|
||||||
|
|
||||||
|
fields['verification_level'] = level.value
|
||||||
|
yield from self._state.http.edit_guild(self.id, **fields)
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def bans(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves all the :class:`User`\s that are banned from the guild.
|
||||||
|
|
||||||
|
You must have :attr:`Permissions.ban_members` permission
|
||||||
|
to get this information.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have proper permissions to get the information.
|
||||||
|
HTTPException
|
||||||
|
An error occurred while fetching the information.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
list
|
||||||
|
A list of :class:`User` that have been banned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = yield from self._state.http.get_bans(self.id)
|
||||||
|
return [User(state=self._state, data=user) for user in data]
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def prune_members(self, *, days):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Prunes the guild from its inactive members.
|
||||||
|
|
||||||
|
The inactive members are denoted if they have not logged on in
|
||||||
|
``days`` number of days and they have no roles.
|
||||||
|
|
||||||
|
You must have the :attr:`Permissions.kick_members` permission
|
||||||
|
to use this.
|
||||||
|
|
||||||
|
To check how many members you would prune without actually pruning,
|
||||||
|
see the :meth:`estimate_pruned_members` function.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
days: int
|
||||||
|
The number of days before counting as inactive.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to prune members.
|
||||||
|
HTTPException
|
||||||
|
An error occurred while pruning members.
|
||||||
|
InvalidArgument
|
||||||
|
An integer was not passed for ``days``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
int
|
||||||
|
The number of members pruned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(days, int):
|
||||||
|
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
|
||||||
|
|
||||||
|
data = yield from self._state.http.prune_members(self.id, days)
|
||||||
|
return data['pruned']
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def estimate_pruned_members(self, *, days):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Similar to :meth:`prune_members` except instead of actually
|
||||||
|
pruning members, it returns how many members it would prune
|
||||||
|
from the guild had it been called.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
days: int
|
||||||
|
The number of days before counting as inactive.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to prune members.
|
||||||
|
HTTPException
|
||||||
|
An error occurred while fetching the prune members estimate.
|
||||||
|
InvalidArgument
|
||||||
|
An integer was not passed for ``days``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
int
|
||||||
|
The number of members estimated to be pruned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(days, int):
|
||||||
|
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
|
||||||
|
|
||||||
|
data = yield from self._state.http.estimate_pruned_members(self.id, days)
|
||||||
|
return data['pruned']
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def invites(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Returns a list of all active instant invites from the guild.
|
||||||
|
|
||||||
|
You must have :attr:`Permissions.manage_guild` to get this information.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have proper permissions to get the information.
|
||||||
|
HTTPException
|
||||||
|
An error occurred while fetching the information.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list of :class:`Invite`
|
||||||
|
The list of invites that are currently active.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = yield from self._state.http.invites_from(guild.id)
|
||||||
|
result = []
|
||||||
|
for invite in data:
|
||||||
|
channel = self.get_channel(int(invite['channel']['id']))
|
||||||
|
invite['channel'] = channel
|
||||||
|
invite['guild'] = self
|
||||||
|
result.append(Invite(state=self._state, data=invite))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def create_custom_emoji(self, *, name, image):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Creates a custom :class:`Emoji` for the guild.
|
||||||
|
|
||||||
|
This endpoint is only allowed for user bots or white listed
|
||||||
|
bots. If this is done by a user bot then this is a local
|
||||||
|
emoji that can only be used inside the guild. If done by
|
||||||
|
a whitelisted bot, then this emoji is "global".
|
||||||
|
|
||||||
|
There is currently a limit of 50 local emotes per guild.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
name: str
|
||||||
|
The emoji name. Must be at least 2 characters.
|
||||||
|
image: bytes
|
||||||
|
The *bytes-like* object representing the image data to use.
|
||||||
|
Only JPG and PNG images are supported.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Emoji`
|
||||||
|
The created emoji.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You are not allowed to create emojis.
|
||||||
|
HTTPException
|
||||||
|
An error occurred creating an emoji.
|
||||||
|
"""
|
||||||
|
|
||||||
|
img = utils._bytes_to_base64_data(image)
|
||||||
|
data = yield from self._state.http.create_custom_emoji(self.id, name, img)
|
||||||
|
return Emoji(guild=self, data=data, state=self._state)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def create_role(self, **fields):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Creates a :class:`Role` for the guild.
|
||||||
|
|
||||||
|
This function is similar to :meth:`Role.edit` in both
|
||||||
|
the fields taken and exceptions thrown.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Role`
|
||||||
|
The newly created role.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = yield from self._state.http.create_role(self.id)
|
||||||
|
role = Role(guild=self, data=data, state=self._state)
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
# we have to call edit because you can't pass a payload to the
|
||||||
|
# http request currently.
|
||||||
|
yield from role.edit(**fields)
|
||||||
|
|
||||||
|
# TODO: add to cache
|
||||||
|
return role
|
||||||
|
108
discord/role.py
108
discord/role.py
@ -24,6 +24,8 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .colour import Colour
|
from .colour import Colour
|
||||||
from .mixins import Hashable
|
from .mixins import Hashable
|
||||||
@ -144,3 +146,109 @@ class Role(Hashable):
|
|||||||
def mention(self):
|
def mention(self):
|
||||||
"""Returns a string that allows you to mention a role."""
|
"""Returns a string that allows you to mention a role."""
|
||||||
return '<@&{}>'.format(self.id)
|
return '<@&{}>'.format(self.id)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _move(self, position):
|
||||||
|
if position <= 0:
|
||||||
|
raise InvalidArgument("Cannot move role to position 0 or below")
|
||||||
|
|
||||||
|
if self.is_everyone:
|
||||||
|
raise InvalidArgument("Cannot move default role")
|
||||||
|
|
||||||
|
if self.position == position:
|
||||||
|
return # Save discord the extra request.
|
||||||
|
|
||||||
|
http = self._state.http
|
||||||
|
url = '{0}/{1}/roles'.format(http.GUILDS, self.guild.id)
|
||||||
|
|
||||||
|
change_range = range(min(self.position, position), max(self.position, position) + 1)
|
||||||
|
sorted_roles = sorted((x for x in self.guild.roles if x.position in change_range and x.id != self.id),
|
||||||
|
key=lambda x: x.position)
|
||||||
|
|
||||||
|
roles = [r.id for r in sorted_roles]
|
||||||
|
|
||||||
|
if self.position > position:
|
||||||
|
roles.insert(0, self.id)
|
||||||
|
else:
|
||||||
|
roles.append(self.id)
|
||||||
|
|
||||||
|
payload = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)]
|
||||||
|
yield from http.patch(url, json=payload, bucket='move_role')
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def edit(self, **fields):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edits the role.
|
||||||
|
|
||||||
|
You must have the :attr:`Permissions.manage_roles` permission to
|
||||||
|
use this.
|
||||||
|
|
||||||
|
All fields are optional.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
name: str
|
||||||
|
The new role name to change to.
|
||||||
|
permissions: :class:`Permissions`
|
||||||
|
The new permissions to change to.
|
||||||
|
colour: :class:`Colour`
|
||||||
|
The new colour to change to. (aliased to color as well)
|
||||||
|
hoist: bool
|
||||||
|
Indicates if the role should be shown separately in the member list.
|
||||||
|
mentionable: bool
|
||||||
|
Indicates if the role should be mentionable by others.
|
||||||
|
position: int
|
||||||
|
The new role's position. This must be below your top role's
|
||||||
|
position or it will fail.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to change the role.
|
||||||
|
HTTPException
|
||||||
|
Editing the role failed.
|
||||||
|
InvalidArgument
|
||||||
|
An invalid position was given or the default
|
||||||
|
role was asked to be moved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
position = fields.get('position')
|
||||||
|
if position is not None:
|
||||||
|
yield from self._move(position)
|
||||||
|
self.position = position
|
||||||
|
|
||||||
|
try:
|
||||||
|
colour = fields['colour']
|
||||||
|
except KeyError:
|
||||||
|
colour = fields.get('color', self.colour)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'name': fields.get('name', self.name),
|
||||||
|
'permissions': fields.get('permissions', self.permissions).value,
|
||||||
|
'color': colour.value,
|
||||||
|
'hoist': fields.get('hoist', self.hoist),
|
||||||
|
'mentionable': fields.get('mentionable', self.mentionable)
|
||||||
|
}
|
||||||
|
|
||||||
|
data = yield from self._state.http.edit_role(self.guild.id, self.id, **payload)
|
||||||
|
self._update(data)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def delete(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Deletes the role.
|
||||||
|
|
||||||
|
You must have the :attr:`Permissions.manage_roles` permission to
|
||||||
|
use this.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to delete the role.
|
||||||
|
HTTPException
|
||||||
|
Deleting the role failed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
yield from self._state.http.delete_role(self.guild.id, self.id)
|
||||||
|
@ -589,7 +589,7 @@ class ConnectionState:
|
|||||||
def parse_guild_role_delete(self, data):
|
def parse_guild_role_delete(self, data):
|
||||||
guild = self._get_guild(int(data['guild_id']))
|
guild = self._get_guild(int(data['guild_id']))
|
||||||
if guild is not None:
|
if guild is not None:
|
||||||
role_id = data.get('role_id')
|
role_id = int(data['role_id'])
|
||||||
role = utils.find(lambda r: r.id == role_id, guild.roles)
|
role = utils.find(lambda r: r.id == role_id, guild.roles)
|
||||||
try:
|
try:
|
||||||
guild._remove_role(role)
|
guild._remove_role(role)
|
||||||
@ -602,7 +602,7 @@ class ConnectionState:
|
|||||||
guild = self._get_guild(int(data['guild_id']))
|
guild = self._get_guild(int(data['guild_id']))
|
||||||
if guild is not None:
|
if guild is not None:
|
||||||
role_data = data['role']
|
role_data = data['role']
|
||||||
role_id = role_data['id']
|
role_id = int(role_data['id'])
|
||||||
role = utils.find(lambda r: r.id == role_id, guild.roles)
|
role = utils.find(lambda r: r.id == role_id, guild.roles)
|
||||||
if role is not None:
|
if role is not None:
|
||||||
old_role = copy.copy(role)
|
old_role = copy.copy(role)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user