Update dependencies and copyright year (#2436)

- aiohttp 3.5
- websockets 7
- Rapptz/discord.py@700dbb5
- A few others

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
Toby Harradine
2019-02-13 10:49:11 +11:00
committed by GitHub
parent c87286d3c6
commit 9869f95bd6
54 changed files with 439 additions and 294 deletions

View File

@@ -6,7 +6,7 @@ Discord API Wrapper
A basic wrapper for the Discord API.
:copyright: (c) 2015-2017 Rapptz
:copyright: (c) 2015-2019 Rapptz
:license: MIT, see LICENSE for more details.
"""
@@ -14,7 +14,7 @@ A basic wrapper for the Discord API.
__title__ = "discord"
__author__ = "Rapptz"
__license__ = "MIT"
__copyright__ = "Copyright 2015-2017 Rapptz"
__copyright__ = "Copyright 2015-2019 Rapptz"
__version__ = "1.0.0a"
from collections import namedtuple

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -667,6 +667,28 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
ret.sort(key=comparator)
return ret
@property
def text_channels(self):
"""List[:class:`TextChannel`]: Returns the text channels that are under this category."""
ret = [
c
for c in self.guild.channels
if c.category_id == self.id and isinstance(c, TextChannel)
]
ret.sort(key=lambda c: (c.position, c.id))
return ret
@property
def voice_channels(self):
"""List[:class:`VoiceChannel`]: Returns the text channels that are under this category."""
ret = [
c
for c in self.guild.channels
if c.category_id == self.id and isinstance(c, VoiceChannel)
]
ret.sort(key=lambda c: (c.position, c.id))
return ret
class DMChannel(discord.abc.Messageable, Hashable):
"""Represents a Discord direct message channel.

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -76,6 +76,13 @@ class PartialEmoji(namedtuple("PartialEmoji", "animated name id")):
return "<a:%s:%s>" % (self.name, self.id)
return "<:%s:%s>" % (self.name, self.id)
def __eq__(self, other):
if self.is_unicode_emoji():
return isinstance(other, PartialEmoji) and self.name == other.name
if isinstance(other, (PartialEmoji, Emoji)):
return self.id == other.id
def is_custom_emoji(self):
"""Checks if this is a custom non-Unicode emoji."""
return self.id is not None
@@ -186,6 +193,9 @@ class Emoji(Hashable):
def __repr__(self):
return "<Emoji id={0.id} name={0.name!r}>".format(self)
def __eq__(self, other):
return isinstance(other, (PartialEmoji, Emoji)) and self.id == other.id
@property
def created_at(self):
"""Returns the emoji's creation time in UTC."""

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -30,6 +30,7 @@ __all__ = [
"ChannelType",
"MessageType",
"VoiceRegion",
"SpeakingState",
"VerificationLevel",
"ContentFilter",
"Status",
@@ -91,6 +92,16 @@ class VoiceRegion(Enum):
return self.value
class SpeakingState(IntEnum):
none = 0
voice = 1
soundshare = 2
priority = 4
def __str__(self):
return self.name
class VerificationLevel(IntEnum):
none = 0
low = 1

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -6,7 +6,7 @@ discord.ext.commands
An extension module to facilitate creation of bot commands.
:copyright: (c) 2017 Rapptz
:copyright: (c) 2019 Rapptz
:license: MIT, see LICENSE for more details.
"""

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -443,9 +443,9 @@ class BotBase(GroupMixin):
Parameters
-----------
func : :ref:`coroutine <coroutine>`
The extra event to listen to.
The function to call.
name : Optional[str]
The name of the command to use. Defaults to ``func.__name__``.
The name of the event to listen for. Defaults to ``func.__name__``.
Example
--------

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -523,7 +523,7 @@ class clean_content(Converter):
result = pattern.sub(repl, argument)
if self.escape_markdown:
transformations = {re.escape(c): "\\" + c for c in ("*", "`", "_", "~", "\\")}
transformations = {re.escape(c): "\\" + c for c in ("*", "`", "_", "~", "\\", "||")}
def replace(obj):
return transformations.get(re.escape(obj.group(0)), "")

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -385,22 +385,18 @@ class Command:
# for use with a manual undo
previous = view.index
# parsing errors get propagated
view.skip_ws()
argument = quoted_word(view)
try:
value = await self.do_conversion(ctx, converter, argument, param)
except CommandError:
if not result:
if required:
raise
else:
view.index = previous
return param.default
view.index = previous
break
else:
result.append(value)
if not result and not required:
return param.default
return result
async def _transform_greedy_var_pos(self, ctx, param, converter):
@@ -750,9 +746,9 @@ class Command:
If that lookup leads to an empty string then the first line of the
:attr:`help` attribute is used instead.
"""
if self.brief:
if self.brief is not None:
return self.brief
if self.help:
if self.help is not None:
return self.help.split("\n", 1)[0]
return ""
@@ -771,7 +767,7 @@ class Command:
name = self.name if not parent else parent + " " + self.name
result.append(name)
if self.usage:
if self.usage is not None:
result.append(self.usage)
return " ".join(result)

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -26,6 +26,7 @@ DEALINGS IN THE SOFTWARE.
import itertools
import inspect
import discord.utils
from .core import GroupMixin, Command
from .errors import CommandError
@@ -183,7 +184,9 @@ class HelpFormatter:
if commands:
return max(
map(
lambda c: len(c.name) if self.show_hidden or not c.hidden else 0,
lambda c: discord.utils._string_width(c.name)
if self.show_hidden or not c.hidden
else 0,
commands.values(),
)
)
@@ -272,8 +275,10 @@ class HelpFormatter:
if name in command.aliases:
# skip aliases
continue
entry = " {0:<{width}} {1}".format(name, command.short_doc, width=max_width)
width_gap = discord.utils._string_width(name) - len(name)
entry = " {0:<{width}} {1}".format(
name, command.short_doc, width=max_width - width_gap
)
shortened = self.shorten(entry)
self._paginator.add_line(shortened)

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -26,6 +26,7 @@ DEALINGS IN THE SOFTWARE.
import asyncio
from collections import namedtuple
import concurrent.futures
import json
import logging
import struct
@@ -38,6 +39,7 @@ import websockets
from . import utils
from .activity import _ActivityTag
from .enums import SpeakingState
from .errors import ConnectionClosed, InvalidArgument
log = logging.getLogger(__name__)
@@ -72,6 +74,8 @@ class KeepAliveHandler(threading.Thread):
self.daemon = True
self.shard_id = shard_id
self.msg = "Keeping websocket alive with sequence %s."
self.block_msg = "Heartbeat blocked for more than %s seconds."
self.behind_msg = "Can't keep up, websocket is %.1fs behind."
self._stop_ev = threading.Event()
self._last_ack = time.perf_counter()
self._last_send = time.perf_counter()
@@ -102,7 +106,15 @@ class KeepAliveHandler(threading.Thread):
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try:
# block until sending is complete
f.result()
total = 0
while True:
try:
f.result(5)
break
except concurrent.futures.TimeoutError:
total += 5
log.warning(self.block_msg, total)
except Exception:
self.stop()
else:
@@ -118,12 +130,16 @@ class KeepAliveHandler(threading.Thread):
ack_time = time.perf_counter()
self._last_ack = ack_time
self.latency = ack_time - self._last_send
if self.latency > 10:
log.warning(self.behind_msg, self.latency)
class VoiceKeepAliveHandler(KeepAliveHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.msg = "Keeping voice websocket alive with timestamp %s."
self.block_msg = "Voice heartbeat blocked for more than %s seconds"
self.behind_msg = "Can't keep up, voice websocket is %.1fs behind"
def get_payload(self):
return {"op": self.ws.HEARTBEAT, "d": int(time.time() * 1000)}
@@ -482,7 +498,7 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
async def send_as_json(self, data):
try:
await super().send(utils.to_json(data))
await self.send(utils.to_json(data))
except websockets.exceptions.ConnectionClosed as exc:
if not self._can_handle_close(exc.code):
raise ConnectionClosed(exc, shard_id=self.shard_id) from exc
@@ -561,6 +577,10 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
Receive only. Tells you that your websocket connection was acknowledged.
INVALIDATE_SESSION
Sent only. Tells you that your RESUME request has failed and to re-IDENTIFY.
CLIENT_CONNECT
Indicates a user has connected to voice.
CLIENT_DISCONNECT
Receive only. Indicates a user has disconnected from voice.
"""
IDENTIFY = 0
@@ -573,6 +593,8 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
RESUME = 7
HELLO = 8
INVALIDATE_SESSION = 9
CLIENT_CONNECT = 12
CLIENT_DISCONNECT = 13
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -611,7 +633,7 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
@classmethod
async def from_client(cls, client, *, resume=False):
"""Creates a voice websocket for the :class:`VoiceClient`."""
gateway = "wss://" + client.endpoint + "/?v=3"
gateway = "wss://" + client.endpoint + "/?v=4"
ws = await websockets.connect(gateway, loop=client.loop, klass=cls, compression=None)
ws.gateway = gateway
ws._connection = client
@@ -624,19 +646,21 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
return ws
async def select_protocol(self, ip, port):
async def select_protocol(self, ip, port, mode):
payload = {
"op": self.SELECT_PROTOCOL,
"d": {
"protocol": "udp",
"data": {"address": ip, "port": port, "mode": "xsalsa20_poly1305"},
},
"d": {"protocol": "udp", "data": {"address": ip, "port": port, "mode": mode}},
}
await self.send_as_json(payload)
async def speak(self, is_speaking=True):
payload = {"op": self.SPEAKING, "d": {"speaking": is_speaking, "delay": 0}}
async def client_connect(self):
payload = {"op": self.CLIENT_CONNECT, "d": {"audio_ssrc": self._connection.ssrc}}
await self.send_as_json(payload)
async def speak(self, state=SpeakingState.voice):
payload = {"op": self.SPEAKING, "d": {"speaking": int(state), "delay": 0}}
await self.send_as_json(payload)
@@ -646,9 +670,6 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
data = msg.get("d")
if op == self.READY:
interval = data["heartbeat_interval"] / 1000.0
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval)
self._keep_alive.start()
await self.initial_connection(data)
elif op == self.HEARTBEAT_ACK:
self._keep_alive.ack()
@@ -656,7 +677,12 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
log.info("Voice RESUME failed.")
await self.identify()
elif op == self.SESSION_DESCRIPTION:
self._connection.mode = data["mode"]
await self.load_secret_key(data)
elif op == self.HELLO:
interval = data["heartbeat_interval"] / 1000.0
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval)
self._keep_alive.start()
async def initial_connection(self, data):
state = self._connection
@@ -677,15 +703,23 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
# the port is a little endian unsigned short in the last two bytes
# yes, this is different endianness from everything else
state.port = struct.unpack_from("<H", recv, len(recv) - 2)[0]
log.debug("detected ip: %s port: %s", state.ip, state.port)
await self.select_protocol(state.ip, state.port)
log.info("selected the voice protocol for use")
# there *should* always be at least one supported mode (xsalsa20_poly1305)
modes = [mode for mode in data["modes"] if mode in self._connection.supported_modes]
log.debug("received supported encryption modes: %s", ", ".join(modes))
mode = modes[0]
await self.select_protocol(state.ip, state.port, mode)
log.info("selected the voice protocol for use (%s)", mode)
await self.client_connect()
async def load_secret_key(self, data):
log.info("received secret key for voice connection")
self._connection.secret_key = data.get("secret_key")
await self.speak()
await self.speak(False)
async def poll_event(self):
try:

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -592,7 +592,7 @@ class Guild(Hashable):
return utils.find(pred, members)
def _create_channel(self, name, overwrites, channel_type, category=None, reason=None):
def _create_channel(self, name, overwrites, channel_type, category=None, **options):
if overwrites is None:
overwrites = {}
elif not isinstance(overwrites, dict):
@@ -615,17 +615,24 @@ class Guild(Hashable):
perms.append(payload)
try:
options["rate_limit_per_user"] = options.pop("slowmode_delay")
except KeyError:
pass
parent_id = category.id if category else None
return self._state.http.create_channel(
self.id,
name,
channel_type.value,
name=name,
parent_id=parent_id,
permission_overwrites=perms,
reason=reason,
**options
)
async def create_text_channel(self, name, *, overwrites=None, category=None, reason=None):
async def create_text_channel(
self, name, *, overwrites=None, category=None, reason=None, **options
):
"""|coro|
Creates a :class:`TextChannel` for the guild.
@@ -637,6 +644,12 @@ class Guild(Hashable):
channel upon creation. This parameter expects a :class:`dict` of
overwrites with the target (either a :class:`Member` or a :class:`Role`)
as the key and a :class:`PermissionOverwrite` as the value.
Note
--------
Creating a channel of a specified position will not update the position of
other channels to follow suit. A follow-up call to :meth:`~TextChannel.edit`
will be required to update the position of the channel in the channel list.
Examples
----------
@@ -660,7 +673,7 @@ class Guild(Hashable):
Parameters
-----------
name: str
name: :class:`str`
The channel's name.
overwrites
A :class:`dict` of target (either a role or a member) to
@@ -670,7 +683,17 @@ class Guild(Hashable):
The category to place the newly created channel under.
The permissions will be automatically synced to category if no
overwrites are provided.
reason: Optional[str]
position: :class:`int`
The position in the channel list. This is a number that starts
at 0. e.g. the top channel is position 0.
topic: Optional[:class:`str`]
The new channel's topic.
slowmode_delay: :class:`int`
Specifies the slowmode rate limit for user in this channel.
The maximum value possible is `120`.
nsfw: :class:`bool`
To mark the channel as NSFW or not.
reason: Optional[:class:`str`]
The reason for creating this channel. Shows up on the audit log.
Raises
@@ -688,7 +711,7 @@ class Guild(Hashable):
The channel that was just created.
"""
data = await self._create_channel(
name, overwrites, ChannelType.text, category, reason=reason
name, overwrites, ChannelType.text, category, reason=reason, **options
)
channel = TextChannel(state=self._state, guild=self, data=data)
@@ -696,13 +719,23 @@ class Guild(Hashable):
self._channels[channel.id] = channel
return channel
async def create_voice_channel(self, name, *, overwrites=None, category=None, reason=None):
async def create_voice_channel(
self, name, *, overwrites=None, category=None, reason=None, **options
):
"""|coro|
Same as :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead.
This is similar to :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead, in addition
to having the following new parameters.
Parameters
-----------
bitrate: :class:`int`
The channel's preferred audio bitrate in bits per second.
user_limit: :class:`int`
The channel's limit for number of members that can be in a voice channel.
"""
data = await self._create_channel(
name, overwrites, ChannelType.voice, category, reason=reason
name, overwrites, ChannelType.voice, category, reason=reason, **options
)
channel = VoiceChannel(state=self._state, guild=self, data=data)

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -152,7 +152,7 @@ class HTTPClient:
# wait until the global lock is complete
await self._global_over.wait()
await lock
await lock.acquire()
with MaybeUnlock(lock) as maybe_lock:
for tries in range(5):
async with self._session.request(method, url, **kwargs) as r:
@@ -596,23 +596,21 @@ class HTTPClient:
r = Route("PATCH", "/guilds/{guild_id}/channels", guild_id=guild_id)
return self.request(r, json=data, reason=reason)
def create_channel(
self,
guild_id,
name,
channel_type,
parent_id=None,
permission_overwrites=None,
*,
reason=None
):
payload = {"name": name, "type": channel_type}
def create_channel(self, guild_id, channel_type, *, reason=None, **options):
payload = {"type": channel_type}
if permission_overwrites is not None:
payload["permission_overwrites"] = permission_overwrites
if parent_id is not None:
payload["parent_id"] = parent_id
valid_keys = (
"name",
"parent_id",
"topic",
"bitrate",
"nsfw",
"user_limit",
"position",
"permission_overwrites",
"rate_limit_per_user",
)
payload.update({k: v for k, v in options.items() if k in valid_keys and v is not None})
return self.request(
Route("POST", "/guilds/{guild_id}/channels", guild_id=guild_id),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -160,7 +160,7 @@ class ReactionIterator(_AsyncIterator):
if data:
self.limit -= retrieve
self.after = Object(id=int(data[0]["id"]))
self.after = Object(id=int(data[-1]["id"]))
if self.guild is None:
for element in reversed(data):

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -157,7 +157,7 @@ class Permissions:
- kick_members
- ban_members
- administrator
- change_nicknames
- change_nickname
- manage_nicknames
"""
return cls(0b00110011111101111111110001010001)
@@ -543,6 +543,10 @@ class PermissionOverwrite:
+-----------+------------------------------------------+
| Operation | Description |
+===========+==========================================+
| x == y | Checks if two overwrites are equal. |
+-----------+------------------------------------------+
| x != y | Checks if two overwrites are not equal. |
+-----------+------------------------------------------+
| iter(x) | Returns an iterator of (perm, value) |
| | pairs. This allows this class to be used |
| | as an iterable in e.g. set/list/dict |
@@ -566,6 +570,9 @@ class PermissionOverwrite:
setattr(self, key, value)
def __eq__(self, other):
return self._values == other._values
def _set(self, key, value):
if value not in (True, None, False):
raise TypeError(

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -27,6 +27,7 @@ DEALINGS IN THE SOFTWARE.
import threading
import subprocess
import audioop
import asyncio
import logging
import shlex
import time
@@ -286,6 +287,7 @@ class AudioPlayer(threading.Thread):
# getattr lookup speed ups
play_audio = self.client.send_audio_packet
self._speak(True)
while not self._end.is_set():
# are we paused?
@@ -334,14 +336,19 @@ class AudioPlayer(threading.Thread):
def stop(self):
self._end.set()
self._resumed.set()
self._speak(False)
def pause(self):
def pause(self, *, update_speaking=True):
self._resumed.clear()
if update_speaking:
self._speak(False)
def resume(self):
def resume(self, *, update_speaking=True):
self.loops = 0
self._start = time.time()
self._resumed.set()
if update_speaking:
self._speak(True)
def is_playing(self):
return self._resumed.is_set() and not self._end.is_set()
@@ -351,6 +358,12 @@ class AudioPlayer(threading.Thread):
def _set_source(self, source):
with self._lock:
self.pause()
self.pause(update_speaking=False)
self.source = source
self.resume()
self.resume(update_speaking=False)
def _speak(self, speaking):
try:
asyncio.run_coroutine_threadsafe(self.client.ws.speak(speaking), self.client.loop)
except Exception as e:
log.info("Speaking call in player failed: %s", e)

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2018 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -103,7 +103,7 @@ class RawReactionActionEvent:
message_id: :class:`int`
The message ID that got or lost a reaction.
user_id: :class:`int`
The user ID who added or removed the reaction.
The user ID who added the reaction or whose reaction was removed.
channel_id: :class:`int`
The channel ID where the reaction got added or removed.
guild_id: Optional[:class:`int`]

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -26,6 +26,7 @@ DEALINGS IN THE SOFTWARE.
import array
import asyncio
import unicodedata
from base64 import b64encode
from bisect import bisect_left
import datetime
@@ -33,7 +34,7 @@ from email.utils import parsedate_to_datetime
import functools
from inspect import isawaitable as _isawaitable
import json
from re import split as re_split
import re
import warnings
from .errors import InvalidArgument
@@ -83,7 +84,7 @@ def cached_slot_property(name):
def parse_time(timestamp):
if timestamp:
return datetime.datetime(*map(int, re_split(r"[^\d]", timestamp.replace("+00:00", ""))))
return datetime.datetime(*map(int, re.split(r"[^\d]", timestamp.replace("+00:00", ""))))
return None
@@ -265,9 +266,7 @@ def _get_mime_type_for_image(data):
return "image/png"
elif data.startswith(b"\xFF\xD8") and data.rstrip(b"\0").endswith(b"\xFF\xD9"):
return "image/jpeg"
elif data.startswith(b"\x47\x49\x46\x38\x37\x61") or data.startswith(
b"\x47\x49\x46\x38\x39\x61"
):
elif data.startswith((b"\x47\x49\x46\x38\x37\x61", b"\x47\x49\x46\x38\x39\x61")):
return "image/gif"
elif data.startswith(b"RIFF") and data[8:12] == b"WEBP":
return "image/webp"
@@ -351,3 +350,20 @@ class SnowflakeList(array.array):
def has(self, element):
i = bisect_left(self, element)
return i != len(self) and self[i] == element
_IS_ASCII = re.compile(r"^[\x00-\x7f]+$")
def _string_width(string, *, _IS_ASCII=_IS_ASCII):
"""Returns string's width."""
match = _IS_ASCII.match(string)
if match:
return match.endpos
UNICODE_WIDE_CHAR_TYPE = "WFA"
width = 0
func = unicodedata.east_asian_width
for char in string:
width += 2 if func(char) in UNICODE_WIDE_CHAR_TYPE else 1
return width

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -105,6 +105,7 @@ class VoiceClient:
self._connected = threading.Event()
self._handshake_complete = asyncio.Event(loop=self.loop)
self.mode = None
self._connections = 0
self.sequence = 0
self.timestamp = 0
@@ -113,6 +114,7 @@ class VoiceClient:
self.encoder = opus.Encoder()
warn_nacl = not has_nacl
supported_modes = ("xsalsa20_poly1305_suffix", "xsalsa20_poly1305")
@property
def guild(self):
@@ -305,22 +307,30 @@ class VoiceClient:
def _get_voice_packet(self, data):
header = bytearray(12)
nonce = bytearray(24)
box = nacl.secret.SecretBox(bytes(self.secret_key))
# Formulate header
# Formulate rtp header
header[0] = 0x80
header[1] = 0x78
struct.pack_into(">H", header, 2, self.sequence)
struct.pack_into(">I", header, 4, self.timestamp)
struct.pack_into(">I", header, 8, self.ssrc)
# Copy header to nonce's first 12 bytes
encrypt_packet = getattr(self, "_encrypt_" + self.mode)
return encrypt_packet(header, data)
def _encrypt_xsalsa20_poly1305(self, header, data):
box = nacl.secret.SecretBox(bytes(self.secret_key))
nonce = bytearray(24)
nonce[:12] = header
# Encrypt and return the data
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext
def _encrypt_xsalsa20_poly1305_suffix(self, header, data):
box = nacl.secret.SecretBox(bytes(self.secret_key))
nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE)
return header + box.encrypt(bytes(data), nonce).ciphertext + nonce
def play(self, source, *, after=None):
"""Plays an :class:`AudioSource`.

View File

@@ -3,7 +3,7 @@
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Copyright (c) 2015-2019 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -103,7 +103,7 @@ class WebhookAdapter:
def store_user(self, data):
# mocks a ConnectionState for appropriate use for Message
return BaseUser(state=self, data=data)
return BaseUser(state=self.webhook._state, data=data)
def execute_webhook(self, *, payload, wait=False, file=None, files=None):
if file is not None:
@@ -195,7 +195,7 @@ class AsyncWebhookAdapter(WebhookAdapter):
# transform into Message object
from .message import Message
return Message(data=data, state=self, channel=self.webhook.channel)
return Message(data=data, state=self.webhook._state, channel=self.webhook.channel)
class RequestsWebhookAdapter(WebhookAdapter):
@@ -279,7 +279,7 @@ class RequestsWebhookAdapter(WebhookAdapter):
# transform into Message object
from .message import Message
return Message(data=response, state=self, channel=self.webhook.channel)
return Message(data=response, state=self.webhook._state, channel=self.webhook.channel)
class Webhook: