diff --git a/discord/iterators.py b/discord/iterators.py index e1de1ddb8..b8a13d013 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -35,6 +35,81 @@ from .object import Object PY35 = sys.version_info >= (3, 5) +class ReactionIterator: + def __init__(self, message, emoji, limit=100, after=None): + self.message = message + self.limit = limit + self.after = after + state = message._state + self.getter = state.http.get_reaction_users + self.state = state + self.emoji = emoji + self.guild = message.guild + self.channel_id = message.channel.id + self.users = asyncio.Queue(loop=state.loop) + + @asyncio.coroutine + def get(self): + if self.users.empty(): + yield from self.fill_users() + + try: + return self.users.get_nowait() + except asyncio.QueueEmpty: + raise NoMoreItems() + + @asyncio.coroutine + def fill_users(self): + # this is a hack because >circular imports< + from .user import User + + if self.limit > 0: + retrieve = self.limit if self.limit <= 100 else 100 + + after = self.after.id if self.after else None + data = yield from self.getter(self.message.id, self.channel_id, self.emoji, retrieve, after=after) + + if data: + self.limit -= retrieve + self.after = Object(id=int(data[0]['id'])) + + if self.guild is None: + for element in reversed(data): + yield from self.users.put(User(state=self.state, data=element)) + else: + for element in reversed(data): + member_id = int(element['id']) + member = self.guild.get_member(member_id) + if member is not None: + yield from self.users.put(member) + else: + yield from self.users.put(User(state=self.state, data=element)) + + @asyncio.coroutine + def flatten(self): + ret = [] + while True: + try: + user = yield from self.get() + except NoMoreItems: + return ret + else: + ret.append(user) + + if PY35: + @asyncio.coroutine + def __aiter__(self): + return self + + @asyncio.coroutine + def __anext__(self): + try: + msg = yield from self.get() + except NoMoreItems: + raise StopAsyncIteration() + else: + return msg + class HistoryIterator: """Iterator for receiving a channel's message history. diff --git a/discord/reaction.py b/discord/reaction.py index d604e26c9..4ec2fd750 100644 --- a/discord/reaction.py +++ b/discord/reaction.py @@ -26,7 +26,7 @@ DEALINGS IN THE SOFTWARE. import asyncio -from .user import User +from .iterators import ReactionIterator class Reaction: """Represents a reaction to a message. @@ -86,11 +86,11 @@ class Reaction: def __repr__(self): return ''.format(self) - @asyncio.coroutine - def users(self, limit=100, after=None): + def users(self, limit=None, after=None): """|coro| - Get the users that added this reaction. + Returns an asynchronous iterator representing the + users that have reacted to the message. The ``after`` parameter must represent a member and meet the :class:`abc.Snowflake` abc. @@ -99,6 +99,8 @@ class Reaction: ------------ limit: int The maximum number of results to return. + If not provided, returns all the users who + reacted to the message. after: :class:`abc.Snowflake` For pagination, reactions are sorted by member. @@ -107,23 +109,48 @@ class Reaction: HTTPException Getting the users for the reaction failed. - Returns + Examples + --------- + + Usage :: + + # I do not actually recommend doing this. + async for user in reaction.users(): + await channel.send('{0} has reacted with {1.emoji}!'.format(user, reaction)) + + Flattening into a list: :: + + users = await reaction.users().flatten() + # users is now a list... + winner = random.choice(users) + await channel.send('{} has won the raffle.'.format(winner)) + + Python 3.4 Usage :: + + iterator = reaction.users() + while True: + try: + user = yield from iterator.get() + except discord.NoMoreItems: + break + else: + await channel.send('{0} has reacted with {1.emoji}!'.format(user, reaction)) + + Yields -------- - List[:class:`User`] - A list of users who reacted to the message. + Union[:class:`User`, :class:`Member`] + The member (if retrievable) or the user that has reacted + to this message. The case where it can be a :class:`Member` is + in a guild message context. Sometimes it can be a :class:`User` + if the member has left the guild. """ - # TODO: Return an iterator a la `Messageable.history`? - if self.custom_emoji: emoji = '{0.name}:{0.id}'.format(self.emoji) else: emoji = self.emoji - if after: - after = after.id + if limit is None: + limit = self.count - msg = self.message - state = msg._state - data = yield from state.http.get_reaction_users(msg.id, msg.channel.id, emoji, limit, after=after) - return [User(state=state, data=user) for user in data] + return ReactionIterator(self.message, emoji, limit, after)