aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--discord/abc.py11
-rw-r--r--discord/emoji.py6
-rw-r--r--discord/member.py34
-rw-r--r--discord/role.py3
-rw-r--r--discord/utils.py34
5 files changed, 67 insertions, 21 deletions
diff --git a/discord/abc.py b/discord/abc.py
index 8482104a..02dc765f 100644
--- a/discord/abc.py
+++ b/discord/abc.py
@@ -415,9 +415,10 @@ class GuildChannel:
default = self.guild.default_role
base = Permissions(default.permissions.value)
+ roles = member.roles
# Apply guild roles that the member has.
- for role in member.roles:
+ for role in roles:
base.value |= role.permissions.value
# Guild-wide Administrator -> True for everything
@@ -436,7 +437,13 @@ class GuildChannel:
except IndexError:
remaining_overwrites = self._overwrites
- member_role_ids = set(map(lambda r: r.id, member.roles))
+ # not sure if doing member._roles.get(...) is better than the
+ # set approach. While this is O(N) to re-create into a set for O(1)
+ # the direct approach would just be O(log n) for searching with no
+ # extra memory overhead. For now, I'll keep the set cast
+ # Note that the member.roles accessor up top also creates a
+ # temporary list
+ member_role_ids = {r.id for r in roles}
denies = 0
allows = 0
diff --git a/discord/emoji.py b/discord/emoji.py
index 16b5bbd7..facdaa93 100644
--- a/discord/emoji.py
+++ b/discord/emoji.py
@@ -154,7 +154,7 @@ class Emoji(Hashable):
self.id = int(emoji['id'])
self.name = emoji['name']
self.animated = emoji.get('animated', False)
- self._roles = set(emoji.get('roles', []))
+ self._roles = utils.SnowflakeList(map(int, emoji.get('roles', [])))
def _iterator(self):
for attr in self.__slots__:
@@ -187,7 +187,7 @@ class Emoji(Hashable):
@property
def roles(self):
- """List[:class:`Role`]: A list of roles that is allowed to use this emoji.
+ """List[:class:`Role`]: A :class:`list` of roles that is allowed to use this emoji.
If roles is empty, the emoji is unrestricted.
"""
@@ -195,7 +195,7 @@ class Emoji(Hashable):
if guild is None:
return []
- return [role for role in guild.roles if role.id in self._roles]
+ return [role for role in guild.roles if self._roles.has(role.id)]
@property
def guild(self):
diff --git a/discord/member.py b/discord/member.py
index e407546a..009a2104 100644
--- a/discord/member.py
+++ b/discord/member.py
@@ -136,10 +136,6 @@ class Member(discord.abc.Messageable, _BaseUser):
Attributes
----------
- roles: List[:class:`Role`]
- A :class:`list` of :class:`Role` that the member belongs to. Note that the first element of this
- list is always the default '@everyone' role. These roles are sorted by their position
- in the role hierarchy.
joined_at: `datetime.datetime`
A datetime object that specifies the date and time in UTC that the member joined the guild for
the first time.
@@ -154,7 +150,7 @@ class Member(discord.abc.Messageable, _BaseUser):
The guild specific nickname of the user.
"""
- __slots__ = ('roles', 'joined_at', 'status', 'activity', 'guild', 'nick', '_user', '_state')
+ __slots__ = ('_roles', 'joined_at', 'status', 'activity', 'guild', 'nick', '_user', '_state')
def __init__(self, *, data, guild, state):
self._state = state
@@ -187,15 +183,7 @@ class Member(discord.abc.Messageable, _BaseUser):
return ch
def _update_roles(self, data):
- # update the roles
- self.roles = [self.guild.default_role]
- for role_id in map(int, data['roles']):
- role = self.guild.get_role(role_id)
- if role is not None:
- self.roles.append(role)
-
- # sort the roles by hierarchy since they can be "randomised"
- self.roles.sort()
+ self._roles = utils.SnowflakeList(map(int, data['roles']))
def _update(self, data, user=None):
if user:
@@ -249,6 +237,24 @@ class Member(discord.abc.Messageable, _BaseUser):
color = colour
@property
+ def roles(self):
+ """A :class:`list` of :class:`Role` that the member belongs to. Note
+ that the first element of this list is always the default '@everyone'
+ role.
+
+ These roles are sorted by their position in the role hierarchy.
+ """
+ result = []
+ g = self.guild
+ for role_id in self._roles:
+ role = g.get_role(role_id)
+ if role:
+ result.append(role)
+ result.append(g.default_role)
+ result.sort()
+ return result
+
+ @property
def mention(self):
"""Returns a string that mentions the member."""
if self.nick:
diff --git a/discord/role.py b/discord/role.py
index 4610c7d0..1068b20b 100644
--- a/discord/role.py
+++ b/discord/role.py
@@ -173,7 +173,8 @@ class Role(Hashable):
if self.is_default():
return all_members
- return [member for member in all_members if self in member.roles]
+ role_id = self.id
+ return [member for member in all_members if member._roles.has(role_id)]
async def _move(self, position, reason):
if position <= 0:
diff --git a/discord/utils.py b/discord/utils.py
index 6bdfc36e..f536200f 100644
--- a/discord/utils.py
+++ b/discord/utils.py
@@ -26,13 +26,16 @@ DEALINGS IN THE SOFTWARE.
from re import split as re_split
from .errors import InvalidArgument
-import datetime
from base64 import b64encode
from email.utils import parsedate_to_datetime
from inspect import isawaitable as _isawaitable
+from bisect import bisect_left
+
+import datetime
import asyncio
import json
import warnings, functools
+import array
DISCORD_EPOCH = 1420070400000
@@ -289,3 +292,32 @@ async def sane_wait_for(futures, *, timeout, loop):
def valid_icon_size(size):
"""Icons must be power of 2 within [16, 1024]."""
return ((size != 0) and not (size & (size - 1))) and size in range(16, 1025)
+
+class SnowflakeList(array.array):
+ """Internal data storage class to efficiently store a list of snowflakes.
+
+ This should have the following characteristics:
+
+ - Low memory usage
+ - O(n) iteration (obviously)
+ - O(n log n) initial creation if data is unsorted
+ - O(log n) search and indexing
+ - O(n) insertion
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, data, *, is_sorted=False):
+ return array.array.__new__(cls, 'Q', data if is_sorted else sorted(data))
+
+ def add(self, element):
+ i = bisect_left(self, element)
+ self.insert(i, element)
+
+ def get(self, element):
+ i = bisect_left(self, element)
+ return self[i] if i != len(self) and self[i] == element else None
+
+ def has(self, element):
+ i = bisect_left(self, element)
+ return i != len(self) and self[i] == element