disagreement 0.2.0rc1__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. disagreement/__init__.py +2 -4
  2. disagreement/audio.py +42 -5
  3. disagreement/cache.py +43 -4
  4. disagreement/caching.py +121 -0
  5. disagreement/client.py +1682 -1535
  6. disagreement/enums.py +10 -3
  7. disagreement/error_handler.py +5 -1
  8. disagreement/errors.py +1341 -3
  9. disagreement/event_dispatcher.py +3 -5
  10. disagreement/ext/__init__.py +1 -0
  11. disagreement/ext/app_commands/__init__.py +0 -2
  12. disagreement/ext/app_commands/commands.py +0 -2
  13. disagreement/ext/app_commands/context.py +0 -2
  14. disagreement/ext/app_commands/converters.py +2 -4
  15. disagreement/ext/app_commands/decorators.py +5 -7
  16. disagreement/ext/app_commands/handler.py +1 -3
  17. disagreement/ext/app_commands/hybrid.py +0 -2
  18. disagreement/ext/commands/__init__.py +63 -61
  19. disagreement/ext/commands/cog.py +0 -2
  20. disagreement/ext/commands/converters.py +16 -5
  21. disagreement/ext/commands/core.py +728 -563
  22. disagreement/ext/commands/decorators.py +294 -219
  23. disagreement/ext/commands/errors.py +0 -2
  24. disagreement/ext/commands/help.py +0 -2
  25. disagreement/ext/commands/view.py +1 -3
  26. disagreement/gateway.py +632 -586
  27. disagreement/http.py +1362 -1041
  28. disagreement/interactions.py +0 -2
  29. disagreement/models.py +2682 -2263
  30. disagreement/shard_manager.py +0 -2
  31. disagreement/ui/view.py +167 -165
  32. disagreement/voice_client.py +263 -162
  33. {disagreement-0.2.0rc1.dist-info → disagreement-0.4.0.dist-info}/METADATA +33 -6
  34. disagreement-0.4.0.dist-info/RECORD +55 -0
  35. disagreement-0.2.0rc1.dist-info/RECORD +0 -54
  36. {disagreement-0.2.0rc1.dist-info → disagreement-0.4.0.dist-info}/WHEEL +0 -0
  37. {disagreement-0.2.0rc1.dist-info → disagreement-0.4.0.dist-info}/licenses/LICENSE +0 -0
  38. {disagreement-0.2.0rc1.dist-info → disagreement-0.4.0.dist-info}/top_level.txt +0 -0
@@ -1,162 +1,263 @@
1
- # disagreement/voice_client.py
2
- """Voice gateway and UDP audio client."""
3
-
4
- from __future__ import annotations
5
-
6
- import asyncio
7
- import contextlib
8
- import socket
9
- from typing import Optional, Sequence
10
-
11
- import aiohttp
12
-
13
- from .audio import AudioSource, FFmpegAudioSource
14
-
15
-
16
- class VoiceClient:
17
- """Handles the Discord voice WebSocket connection and UDP streaming."""
18
-
19
- def __init__(
20
- self,
21
- endpoint: str,
22
- session_id: str,
23
- token: str,
24
- guild_id: int,
25
- user_id: int,
26
- *,
27
- ws=None,
28
- udp: Optional[socket.socket] = None,
29
- loop: Optional[asyncio.AbstractEventLoop] = None,
30
- verbose: bool = False,
31
- ) -> None:
32
- self.endpoint = endpoint
33
- self.session_id = session_id
34
- self.token = token
35
- self.guild_id = str(guild_id)
36
- self.user_id = str(user_id)
37
- self._ws: Optional[aiohttp.ClientWebSocketResponse] = ws
38
- self._udp = udp
39
- self._session: Optional[aiohttp.ClientSession] = None
40
- self._heartbeat_task: Optional[asyncio.Task] = None
41
- self._heartbeat_interval: Optional[float] = None
42
- self._loop = loop or asyncio.get_event_loop()
43
- self.verbose = verbose
44
- self.ssrc: Optional[int] = None
45
- self.secret_key: Optional[Sequence[int]] = None
46
- self._server_ip: Optional[str] = None
47
- self._server_port: Optional[int] = None
48
- self._current_source: Optional[AudioSource] = None
49
- self._play_task: Optional[asyncio.Task] = None
50
-
51
- async def connect(self) -> None:
52
- if self._ws is None:
53
- self._session = aiohttp.ClientSession()
54
- self._ws = await self._session.ws_connect(self.endpoint)
55
-
56
- hello = await self._ws.receive_json()
57
- self._heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000
58
- self._heartbeat_task = self._loop.create_task(self._heartbeat())
59
-
60
- await self._ws.send_json(
61
- {
62
- "op": 0,
63
- "d": {
64
- "server_id": self.guild_id,
65
- "user_id": self.user_id,
66
- "session_id": self.session_id,
67
- "token": self.token,
68
- },
69
- }
70
- )
71
-
72
- ready = await self._ws.receive_json()
73
- data = ready["d"]
74
- self.ssrc = data["ssrc"]
75
- self._server_ip = data["ip"]
76
- self._server_port = data["port"]
77
-
78
- if self._udp is None:
79
- self._udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
80
- self._udp.connect((self._server_ip, self._server_port))
81
-
82
- await self._ws.send_json(
83
- {
84
- "op": 1,
85
- "d": {
86
- "protocol": "udp",
87
- "data": {
88
- "address": self._udp.getsockname()[0],
89
- "port": self._udp.getsockname()[1],
90
- "mode": "xsalsa20_poly1305",
91
- },
92
- },
93
- }
94
- )
95
-
96
- session_desc = await self._ws.receive_json()
97
- self.secret_key = session_desc["d"].get("secret_key")
98
-
99
- async def _heartbeat(self) -> None:
100
- assert self._ws is not None
101
- assert self._heartbeat_interval is not None
102
- try:
103
- while True:
104
- await self._ws.send_json({"op": 3, "d": int(self._loop.time() * 1000)})
105
- await asyncio.sleep(self._heartbeat_interval)
106
- except asyncio.CancelledError:
107
- pass
108
-
109
- async def send_audio_frame(self, frame: bytes) -> None:
110
- if not self._udp:
111
- raise RuntimeError("UDP socket not initialised")
112
- self._udp.send(frame)
113
-
114
- async def _play_loop(self) -> None:
115
- assert self._current_source is not None
116
- try:
117
- while True:
118
- data = await self._current_source.read()
119
- if not data:
120
- break
121
- await self.send_audio_frame(data)
122
- finally:
123
- await self._current_source.close()
124
- self._current_source = None
125
- self._play_task = None
126
-
127
- async def stop(self) -> None:
128
- if self._play_task:
129
- self._play_task.cancel()
130
- with contextlib.suppress(asyncio.CancelledError):
131
- await self._play_task
132
- self._play_task = None
133
- if self._current_source:
134
- await self._current_source.close()
135
- self._current_source = None
136
-
137
- async def play(self, source: AudioSource, *, wait: bool = True) -> None:
138
- """|coro| Play an :class:`AudioSource` on the voice connection."""
139
-
140
- await self.stop()
141
- self._current_source = source
142
- self._play_task = self._loop.create_task(self._play_loop())
143
- if wait:
144
- await self._play_task
145
-
146
- async def play_file(self, filename: str, *, wait: bool = True) -> None:
147
- """|coro| Stream an audio file or URL using FFmpeg."""
148
-
149
- await self.play(FFmpegAudioSource(filename), wait=wait)
150
-
151
- async def close(self) -> None:
152
- await self.stop()
153
- if self._heartbeat_task:
154
- self._heartbeat_task.cancel()
155
- with contextlib.suppress(asyncio.CancelledError):
156
- await self._heartbeat_task
157
- if self._ws:
158
- await self._ws.close()
159
- if self._session:
160
- await self._session.close()
161
- if self._udp:
162
- self._udp.close()
1
+ """Voice gateway and UDP audio client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import contextlib
7
+ import socket
8
+ import threading
9
+ from array import array
10
+
11
+
12
+ def _apply_volume(data: bytes, volume: float) -> bytes:
13
+ samples = array("h")
14
+ samples.frombytes(data)
15
+ for i, sample in enumerate(samples):
16
+ scaled = int(sample * volume)
17
+ if scaled > 32767:
18
+ scaled = 32767
19
+ elif scaled < -32768:
20
+ scaled = -32768
21
+ samples[i] = scaled
22
+ return samples.tobytes()
23
+
24
+
25
+ from typing import TYPE_CHECKING, Optional, Sequence
26
+
27
+ import aiohttp
28
+
29
+ # The following import is correct, but may be flagged by Pylance if the virtual
30
+ # environment is not configured correctly.
31
+ from nacl.secret import SecretBox
32
+
33
+ from .audio import AudioSink, AudioSource, FFmpegAudioSource
34
+ from .models import User
35
+
36
+ if TYPE_CHECKING:
37
+ from .client import Client
38
+
39
+
40
+ class VoiceClient:
41
+ """Handles the Discord voice WebSocket connection and UDP streaming."""
42
+
43
+ def __init__(
44
+ self,
45
+ client: Client,
46
+ endpoint: str,
47
+ session_id: str,
48
+ token: str,
49
+ guild_id: int,
50
+ user_id: int,
51
+ *,
52
+ ws=None,
53
+ udp: Optional[socket.socket] = None,
54
+ loop: Optional[asyncio.AbstractEventLoop] = None,
55
+ verbose: bool = False,
56
+ ) -> None:
57
+ self.client = client
58
+ self.endpoint = endpoint
59
+ self.session_id = session_id
60
+ self.token = token
61
+ self.guild_id = str(guild_id)
62
+ self.user_id = str(user_id)
63
+ self._ws: Optional[aiohttp.ClientWebSocketResponse] = ws
64
+ self._udp = udp
65
+ self._session: Optional[aiohttp.ClientSession] = None
66
+ self._heartbeat_task: Optional[asyncio.Task] = None
67
+ self._receive_task: Optional[asyncio.Task] = None
68
+ self._udp_receive_thread: Optional[threading.Thread] = None
69
+ self._heartbeat_interval: Optional[float] = None
70
+ try:
71
+ self._loop = loop or asyncio.get_running_loop()
72
+ except RuntimeError:
73
+ self._loop = asyncio.new_event_loop()
74
+ asyncio.set_event_loop(self._loop)
75
+ self.verbose = verbose
76
+ self.ssrc: Optional[int] = None
77
+ self.secret_key: Optional[Sequence[int]] = None
78
+ self._server_ip: Optional[str] = None
79
+ self._server_port: Optional[int] = None
80
+ self._current_source: Optional[AudioSource] = None
81
+ self._play_task: Optional[asyncio.Task] = None
82
+ self._sink: Optional[AudioSink] = None
83
+ self._ssrc_map: dict[int, int] = {}
84
+ self._ssrc_lock = threading.Lock()
85
+
86
+ async def connect(self) -> None:
87
+ if self._ws is None:
88
+ self._session = aiohttp.ClientSession()
89
+ self._ws = await self._session.ws_connect(self.endpoint)
90
+
91
+ hello = await self._ws.receive_json()
92
+ self._heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000
93
+ self._heartbeat_task = self._loop.create_task(self._heartbeat())
94
+
95
+ await self._ws.send_json(
96
+ {
97
+ "op": 0,
98
+ "d": {
99
+ "server_id": self.guild_id,
100
+ "user_id": self.user_id,
101
+ "session_id": self.session_id,
102
+ "token": self.token,
103
+ },
104
+ }
105
+ )
106
+
107
+ ready = await self._ws.receive_json()
108
+ data = ready["d"]
109
+ self.ssrc = data["ssrc"]
110
+ self._server_ip = data["ip"]
111
+ self._server_port = data["port"]
112
+
113
+ if self._udp is None:
114
+ self._udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
115
+ self._udp.connect((self._server_ip, self._server_port))
116
+
117
+ await self._ws.send_json(
118
+ {
119
+ "op": 1,
120
+ "d": {
121
+ "protocol": "udp",
122
+ "data": {
123
+ "address": self._udp.getsockname()[0],
124
+ "port": self._udp.getsockname()[1],
125
+ "mode": "xsalsa20_poly1305",
126
+ },
127
+ },
128
+ }
129
+ )
130
+
131
+ session_desc = await self._ws.receive_json()
132
+ self.secret_key = session_desc["d"].get("secret_key")
133
+
134
+ async def _heartbeat(self) -> None:
135
+ assert self._ws is not None
136
+ assert self._heartbeat_interval is not None
137
+ try:
138
+ while True:
139
+ await self._ws.send_json({"op": 3, "d": int(self._loop.time() * 1000)})
140
+ await asyncio.sleep(self._heartbeat_interval)
141
+ except asyncio.CancelledError:
142
+ pass
143
+
144
+ async def _receive_loop(self) -> None:
145
+ assert self._ws is not None
146
+ while True:
147
+ try:
148
+ msg = await self._ws.receive_json()
149
+ op = msg.get("op")
150
+ data = msg.get("d")
151
+ if op == 5: # Speaking
152
+ user_id = int(data["user_id"])
153
+ ssrc = data["ssrc"]
154
+ with self._ssrc_lock:
155
+ self._ssrc_map[ssrc] = user_id
156
+ except (asyncio.CancelledError, aiohttp.ClientError):
157
+ break
158
+
159
+ def _udp_receive_loop(self) -> None:
160
+ assert self._udp is not None
161
+ assert self.secret_key is not None
162
+ box = SecretBox(bytes(self.secret_key))
163
+ while True:
164
+ try:
165
+ packet = self._udp.recv(4096)
166
+ if len(packet) < 12:
167
+ continue
168
+
169
+ ssrc = int.from_bytes(packet[8:12], "big")
170
+ with self._ssrc_lock:
171
+ if ssrc not in self._ssrc_map:
172
+ continue
173
+ user_id = self._ssrc_map[ssrc]
174
+ user = self.client._users.get(str(user_id))
175
+ if not user:
176
+ continue
177
+
178
+ decrypted = box.decrypt(packet[12:])
179
+ if self._sink:
180
+ self._sink.write(user, decrypted)
181
+ except (socket.error, asyncio.CancelledError):
182
+ break
183
+ except Exception as e:
184
+ if self.verbose:
185
+ print(f"Error in UDP receive loop: {e}")
186
+
187
+ async def send_audio_frame(self, frame: bytes) -> None:
188
+ if not self._udp:
189
+ raise RuntimeError("UDP socket not initialised")
190
+ self._udp.send(frame)
191
+
192
+ async def _play_loop(self) -> None:
193
+ assert self._current_source is not None
194
+ try:
195
+ while True:
196
+ data = await self._current_source.read()
197
+ if not data:
198
+ break
199
+ volume = getattr(self._current_source, "volume", 1.0)
200
+ if volume != 1.0:
201
+ data = _apply_volume(data, volume)
202
+ await self.send_audio_frame(data)
203
+ finally:
204
+ await self._current_source.close()
205
+ self._current_source = None
206
+ self._play_task = None
207
+
208
+ async def stop(self) -> None:
209
+ if self._play_task:
210
+ self._play_task.cancel()
211
+ with contextlib.suppress(asyncio.CancelledError):
212
+ await self._play_task
213
+ self._play_task = None
214
+ if self._current_source:
215
+ await self._current_source.close()
216
+ self._current_source = None
217
+
218
+ async def play(self, source: AudioSource, *, wait: bool = True) -> None:
219
+ """|coro| Play an :class:`AudioSource` on the voice connection."""
220
+
221
+ await self.stop()
222
+ self._current_source = source
223
+ self._play_task = self._loop.create_task(self._play_loop())
224
+ if wait:
225
+ await self._play_task
226
+
227
+ async def play_file(self, filename: str, *, wait: bool = True) -> None:
228
+ """|coro| Stream an audio file or URL using FFmpeg."""
229
+
230
+ await self.play(FFmpegAudioSource(filename), wait=wait)
231
+
232
+ def listen(self, sink: AudioSink) -> None:
233
+ """Start listening to voice and routing to a sink."""
234
+ if not isinstance(sink, AudioSink):
235
+ raise TypeError("sink must be an AudioSink instance")
236
+
237
+ self._sink = sink
238
+ if not self._udp_receive_thread:
239
+ self._udp_receive_thread = threading.Thread(
240
+ target=self._udp_receive_loop, daemon=True
241
+ )
242
+ self._udp_receive_thread.start()
243
+
244
+ async def close(self) -> None:
245
+ await self.stop()
246
+ if self._heartbeat_task:
247
+ self._heartbeat_task.cancel()
248
+ with contextlib.suppress(asyncio.CancelledError):
249
+ await self._heartbeat_task
250
+ if self._receive_task:
251
+ self._receive_task.cancel()
252
+ with contextlib.suppress(asyncio.CancelledError):
253
+ await self._receive_task
254
+ if self._ws:
255
+ await self._ws.close()
256
+ if self._session:
257
+ await self._session.close()
258
+ if self._udp:
259
+ self._udp.close()
260
+ if self._udp_receive_thread:
261
+ self._udp_receive_thread.join(timeout=1)
262
+ if self._sink:
263
+ self._sink.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: disagreement
3
- Version: 0.2.0rc1
3
+ Version: 0.4.0
4
4
  Summary: A Python library for the Discord API.
5
5
  Author-email: Slipstream <me@slipstreamm.dev>
6
6
  License: BSD 3-Clause
@@ -23,6 +23,7 @@ Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: aiohttp<4.0.0,>=3.9.0
26
+ Requires-Dist: PyNaCl<2.0.0,>=1.5.0
26
27
  Provides-Extra: test
27
28
  Requires-Dist: pytest>=8.0.0; extra == "test"
28
29
  Requires-Dist: pytest-asyncio>=1.0.0; extra == "test"
@@ -37,6 +38,9 @@ A Python library for interacting with the Discord API, with a focus on bot devel
37
38
 
38
39
  ## Features
39
40
 
41
+ - Internationalization helpers
42
+ - Hybrid context for commands
43
+ - Built-in rate limiting
40
44
  - Asynchronous design using `aiohttp`
41
45
  - Gateway and HTTP API clients
42
46
  - Slash command framework
@@ -56,6 +60,13 @@ pip install -e .
56
60
 
57
61
  Requires Python 3.10 or newer.
58
62
 
63
+ To run the example scripts, you'll need the `python-dotenv` package to load
64
+ environment variables. Install the development extras with:
65
+
66
+ ```bash
67
+ pip install "disagreement[dev]"
68
+ ```
69
+
59
70
  ## Basic Usage
60
71
 
61
72
  ```python
@@ -64,6 +75,8 @@ import os
64
75
 
65
76
  import disagreement
66
77
  from disagreement.ext import commands
78
+ from dotenv import load_dotenv
79
+ load_dotenv()
67
80
 
68
81
 
69
82
  class Basics(commands.Cog):
@@ -72,18 +85,17 @@ class Basics(commands.Cog):
72
85
 
73
86
  @commands.command()
74
87
  async def ping(self, ctx: commands.CommandContext) -> None:
75
- await ctx.reply("Pong!")
88
+ await ctx.reply(f"Pong! Gateway Latency: {self.client.latency_ms} ms.")
76
89
 
77
90
 
78
91
  token = os.getenv("DISCORD_BOT_TOKEN")
79
92
  if not token:
80
93
  raise RuntimeError("DISCORD_BOT_TOKEN environment variable not set")
81
94
 
82
- client = disagreement.Client(token=token, command_prefix="!")
83
- client.add_cog(Basics(client))
84
-
85
-
95
+ intents = disagreement.GatewayIntent.default() | disagreement.GatewayIntent.MESSAGE_CONTENT
96
+ client = disagreement.Client(token=token, command_prefix="!", intents=intents, mention_replies=True)
86
97
  async def main() -> None:
98
+ client.add_cog(Basics(client))
87
99
  await client.run()
88
100
 
89
101
 
@@ -135,6 +147,20 @@ These options are forwarded to ``HTTPClient`` when it creates the underlying
135
147
  ``aiohttp.ClientSession``. You can specify a custom ``connector`` or any other
136
148
  session parameter supported by ``aiohttp``.
137
149
 
150
+ ### Default Allowed Mentions
151
+
152
+ Specify default mention behaviour for all outgoing messages when constructing the client:
153
+
154
+ ```python
155
+ client = disagreement.Client(
156
+ token=token,
157
+ allowed_mentions={"parse": [], "replied_user": False},
158
+ )
159
+ ```
160
+
161
+ This dictionary is used whenever ``send_message`` is called without an explicit
162
+ ``allowed_mentions`` argument.
163
+
138
164
  ### Defining Subcommands with `AppCommandGroup`
139
165
 
140
166
  ```python
@@ -153,6 +179,7 @@ async def show(ctx: AppCommandContext, key: str):
153
179
  @slash_command(name="set", description="Update a setting.", parent=admin_group)
154
180
  async def set_setting(ctx: AppCommandContext, key: str, value: str):
155
181
  ...
182
+ ```
156
183
  ## Fetching Guilds
157
184
 
158
185
  Use `Client.fetch_guild` to retrieve a guild from the Discord API if it
@@ -0,0 +1,55 @@
1
+ disagreement/__init__.py,sha256=-_a69aQUTp1AlZNLLbetYzvV4sEhvnfrHwlU673WkIE,1137
2
+ disagreement/audio.py,sha256=erBaupz-Hw8LR-5mGLDvhphCDAQeWk0ZBA0AMVwDUIM,4360
3
+ disagreement/cache.py,sha256=srmaw-x8iiqU53eh33JgLLkM-K-TgFnvOS2lYO7Z2FI,2866
4
+ disagreement/caching.py,sha256=KEicB8fIBRwc5Ifl1CCHHnPu-NfAvqdfArjxFuV557g,3841
5
+ disagreement/client.py,sha256=RBiprWFfW02Wkt8FwHaYvJfcK4vOR559Fn5p26ab9ME,67441
6
+ disagreement/color.py,sha256=0RumZU9geS51rmmywwotmkXFc8RyQghOviRGGrHmvW4,4495
7
+ disagreement/components.py,sha256=tEYJ2RHVpIFtZuPPxZ0v8ssUw_x7ybhYBzHNsRiXXvU,5250
8
+ disagreement/enums.py,sha256=F_DHgeAnnw17ILswOuxPR6G1OimL6aeuxIEAni1XPyY,11187
9
+ disagreement/error_handler.py,sha256=rj9AiJhRLPNAaAY6Gj0KQXgKNfPsgnZUtyTljclybD8,1161
10
+ disagreement/errors.py,sha256=Zi7x_hgGXCDw5ht2HpcQDba4NQbXkBbjQIVhU9RVyhE,32236
11
+ disagreement/event_dispatcher.py,sha256=o-PgfOYEwlCbmrWeLuycp-QT6tlXF2JEU3Dyv0VFZ0c,11419
12
+ disagreement/gateway.py,sha256=7ccRygL1gnd_w8DldsLFxreZy1iCdw6Nw65_UW8Ulz8,26831
13
+ disagreement/http.py,sha256=iYmX6QjBEkcZlHJTefJ-tHHYp8BV4TTQYE-vaHGjfUk,51729
14
+ disagreement/hybrid_context.py,sha256=VYCmcreTZdPBU9v-Cy48W38vgWO2t8nM2ulC6_z4HjU,1095
15
+ disagreement/i18n.py,sha256=1L4rcFuKP0XjHk9dVwbNh4BkLk2ZlxxZ_-tecGWa9S0,718
16
+ disagreement/interactions.py,sha256=moN2iQEE9ntC-Y2Q7uxUqoBBRYHegNwB00w-2UD30q4,21703
17
+ disagreement/logging_config.py,sha256=4q6baQPE6X_0lfaBTFMU1uqc03x5SbJqo2hsApdDFac,686
18
+ disagreement/models.py,sha256=ohpMS03K9Hs9lJ2uWLG_0P3VTJr8sSy9dNsc3uRerpM,97981
19
+ disagreement/oauth.py,sha256=TfDdCwg1J7osM9wDi61dtNBA5BrQk5DeQrrHsYycH34,2810
20
+ disagreement/permissions.py,sha256=7g5cIlg-evHXOL0-pmtT5EwqcB-stXot1HZSLz724sE,3008
21
+ disagreement/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ disagreement/rate_limiter.py,sha256=ubwR_UTPs2MotipBdtqpgwQKx0IHt2I3cdfFcXTFv7g,2521
23
+ disagreement/shard_manager.py,sha256=gE7562izYcN-5jTDWUP9kIFX5gffRjIryqNxk1mtnSM,2128
24
+ disagreement/typing.py,sha256=_1oFWfZ4HyH5Q3bnF7CO24s79z-3_B5Qb69kWiwLhhU,1242
25
+ disagreement/utils.py,sha256=mz7foTCOAmUv9n8EcdZeiFarwqB14xHOG8o0p8tFuKA,2014
26
+ disagreement/voice_client.py,sha256=AgBhId6syetP4wwHq47IgGlfNIqtLUt_5nbEE-c6KtQ,9250
27
+ disagreement/ext/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
28
+ disagreement/ext/loader.py,sha256=9_uULvNAa-a6UiaeQhWglwgIrHEPKbf9bnWtSL1KV5Q,1408
29
+ disagreement/ext/tasks.py,sha256=b14KI-btikbrjPlD76md3Ggt6znrxPqr7TDarU4PYBg,7269
30
+ disagreement/ext/app_commands/__init__.py,sha256=sVCAgeuu2EIqErP8SGqB8ytSsoL7YuYvD1xY3J2POJ4,969
31
+ disagreement/ext/app_commands/commands.py,sha256=yjMi-fc2gcRPjeXpcb3Re_HjDsvC8-zpUwGfTsxYXWU,18973
32
+ disagreement/ext/app_commands/context.py,sha256=MyEhXFgelxV6d4KZUrgGwFy60uSsr4v8mnC6z2Ivdxo,20787
33
+ disagreement/ext/app_commands/converters.py,sha256=CvkZwZ0xA8WgFiL018-vGA5JL0kl9rBE_68ME_ra37c,20877
34
+ disagreement/ext/app_commands/decorators.py,sha256=awnZICfZd9bjP4rDeS5YPBzFzldxbfVOBEneLqAjwHw,22661
35
+ disagreement/ext/app_commands/handler.py,sha256=T0T1jmFVozrfRewmeVdiHjzVLEzypEixnY9jchOJASM,28716
36
+ disagreement/ext/app_commands/hybrid.py,sha256=q07qjMOLBOvbjoZwsIoGkdFqnDzvZsdWuavcHXfuAPg,3287
37
+ disagreement/ext/commands/__init__.py,sha256=MzdDf3Gr9H745C-cA9xYOI1eLgmAl25qQVwtrEVj_DA,1320
38
+ disagreement/ext/commands/cog.py,sha256=VIx1kgO1HP_y1EdbeuFhnA9C_4QsstD7608akQmDP9c,6809
39
+ disagreement/ext/commands/converters.py,sha256=Xt4LkPxc4uX1ivOno75Y6smgwilaGhw_dSR8NH7EDzA,6300
40
+ disagreement/ext/commands/core.py,sha256=k2HgC40Jz0d6-1023LIi2S3UW5qP8gt_vQsV0mgU0B8,28352
41
+ disagreement/ext/commands/decorators.py,sha256=nsCF73fMH14Ox_dZbCEFD3KqwlLqqyJpEr7uAtfQ4Kg,10380
42
+ disagreement/ext/commands/errors.py,sha256=6Chp5FW8OVIUNdAnyu6cM8LXanhOzE95OvEZVR4DgJo,2262
43
+ disagreement/ext/commands/help.py,sha256=A0y-p18Vou0HG4KSHwTufZCgLXNJ8SDM-oMQEeNmaps,1419
44
+ disagreement/ext/commands/view.py,sha256=b4K83xGq5PmfDgA8uQZyOwVfKE4rzK8MyHQuUmDINJc,3215
45
+ disagreement/ui/__init__.py,sha256=PLA6eHiq9cu7JDOKS-7MKtaFhlqswjbI4AEUlpnbgO0,307
46
+ disagreement/ui/button.py,sha256=GHbY3-yMrvv6u674-qYONocuC1e2a0flEWjPKwJXKDo,3163
47
+ disagreement/ui/item.py,sha256=bm-EmQEZpe8Kt8JrRw-o0uQdccgjErORcFsBqaXOcV8,1112
48
+ disagreement/ui/modal.py,sha256=w0ZEVslXzx2-RWUP4jdVB54zDuT81jpueVWZ70byFnI,4137
49
+ disagreement/ui/select.py,sha256=XjWRlWkA09QZaDDLn-wDDOWIuj0Mb4VCWJEOAaExZXw,3018
50
+ disagreement/ui/view.py,sha256=UdOaoSe0NZXZMjOtM8CLCPcDHVf6Dn2yr2PHLSvoJsg,5834
51
+ disagreement-0.4.0.dist-info/licenses/LICENSE,sha256=zfmtgJhVFHnqT7a8LAQFthXu5bP7EEHmEL99trV66Ew,1474
52
+ disagreement-0.4.0.dist-info/METADATA,sha256=5kvSnyoSMfRzlbaDZoOaRX-6tWlA8Nuy_XX-PfxAQUQ,6268
53
+ disagreement-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ disagreement-0.4.0.dist-info/top_level.txt,sha256=t7FY_3iaYhdB6X6y9VybJ2j7UZbVeRUe9wRgH8d5Gtk,13
55
+ disagreement-0.4.0.dist-info/RECORD,,
@@ -1,54 +0,0 @@
1
- disagreement/__init__.py,sha256=tZlZwEhqnBj3CEfPX2BWXXvGIc5OIiLtzuybRG7w1wA,1184
2
- disagreement/audio.py,sha256=P6inobI8CNhNVkaRKU58RMYtLq1RrSREioF0Mui5VlA,3351
3
- disagreement/cache.py,sha256=juabGFl4naQih5OUIVN2aN-vAfw2ZC2cI38s4nGEn8U,1525
4
- disagreement/client.py,sha256=WJ1xLiXMha0_9i0rUDxXiMWXSG80_fXylq3Qmf9rr7k,59855
5
- disagreement/color.py,sha256=0RumZU9geS51rmmywwotmkXFc8RyQghOviRGGrHmvW4,4495
6
- disagreement/components.py,sha256=tEYJ2RHVpIFtZuPPxZ0v8ssUw_x7ybhYBzHNsRiXXvU,5250
7
- disagreement/enums.py,sha256=Km9rzmbkYdBpba3fDAv9YYtXDROoRpKuQfkMavsiY0s,11069
8
- disagreement/error_handler.py,sha256=c2lb6aTMnhTtITQuR6axZUtEaasYKUgmdSxAHEkeq50,1028
9
- disagreement/errors.py,sha256=XiYVPy8uFUGVi_EIf81yK7QbC7KyN4JHplSJSWw2RRk,3185
10
- disagreement/event_dispatcher.py,sha256=mp4LVhIj0SW1P2NruqbYpZoYH33X5rXvkAl3-RK40kE,11460
11
- disagreement/gateway.py,sha256=AxfGsSxu4eOWwpL3LQiNfcQVR3hyj33N9KfaPy0h8OU,24487
12
- disagreement/http.py,sha256=TOGF2LBnsg4hTrP0sFBscKz1VVM_qZ8eoPZfBoQQPQw,37063
13
- disagreement/hybrid_context.py,sha256=VYCmcreTZdPBU9v-Cy48W38vgWO2t8nM2ulC6_z4HjU,1095
14
- disagreement/i18n.py,sha256=1L4rcFuKP0XjHk9dVwbNh4BkLk2ZlxxZ_-tecGWa9S0,718
15
- disagreement/interactions.py,sha256=aUZwwEOLsEds15i6G-rxmSSDCDmaxz_cfoTYS4tv6Ao,21735
16
- disagreement/logging_config.py,sha256=4q6baQPE6X_0lfaBTFMU1uqc03x5SbJqo2hsApdDFac,686
17
- disagreement/models.py,sha256=Km75XDUiRV3gzhSPYDm2AByQEw2koZ-gyY1urvYffTE,82512
18
- disagreement/oauth.py,sha256=TfDdCwg1J7osM9wDi61dtNBA5BrQk5DeQrrHsYycH34,2810
19
- disagreement/permissions.py,sha256=7g5cIlg-evHXOL0-pmtT5EwqcB-stXot1HZSLz724sE,3008
20
- disagreement/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- disagreement/rate_limiter.py,sha256=ubwR_UTPs2MotipBdtqpgwQKx0IHt2I3cdfFcXTFv7g,2521
22
- disagreement/shard_manager.py,sha256=e9F8tx_4IEOlTX3-S3t51lfJToc6Ue3RVBzoNAiVKxs,2161
23
- disagreement/typing.py,sha256=_1oFWfZ4HyH5Q3bnF7CO24s79z-3_B5Qb69kWiwLhhU,1242
24
- disagreement/utils.py,sha256=mz7foTCOAmUv9n8EcdZeiFarwqB14xHOG8o0p8tFuKA,2014
25
- disagreement/voice_client.py,sha256=i_67gJ-SQWi9YH-pgtFM8N0lCYznyuQImyL-mf2O7KQ,5384
26
- disagreement/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
- disagreement/ext/loader.py,sha256=9_uULvNAa-a6UiaeQhWglwgIrHEPKbf9bnWtSL1KV5Q,1408
28
- disagreement/ext/tasks.py,sha256=b14KI-btikbrjPlD76md3Ggt6znrxPqr7TDarU4PYBg,7269
29
- disagreement/ext/app_commands/__init__.py,sha256=mnQLIuGP9SzqGMPEn5YgOh2eIU7lcYoDXP06vtXZfTA,1014
30
- disagreement/ext/app_commands/commands.py,sha256=xjrVPScEezdVxVq524_E2Gm2sTa-yq44TbGMu0gyA2o,19018
31
- disagreement/ext/app_commands/context.py,sha256=Xcm4Ka5K5uTQGviixF5LeCDdOdF9YQS5F7lZi2m--8s,20831
32
- disagreement/ext/app_commands/converters.py,sha256=J1VEmo-7H9K7kGPJodu5FX4RmFFI1BuzhlQAEs2MsD4,21036
33
- disagreement/ext/app_commands/decorators.py,sha256=dKiD4ZEsafRoPvfgn9zuQ9vvXXo2qYTMquHvyUM1604,23251
34
- disagreement/ext/app_commands/handler.py,sha256=KCMi5NEusuyLYo7Vk4sqLqXAJI5r3ppI0MNLUh0kU2c,28781
35
- disagreement/ext/app_commands/hybrid.py,sha256=u3kHNbVfY3-liymgzEIkFO5YV3WM_DqwytzdN9EXWMY,3330
36
- disagreement/ext/commands/__init__.py,sha256=miejXIfft2kq2Q4Lej28awSgQXIEEeEuaBhR3M7f9tk,1230
37
- disagreement/ext/commands/cog.py,sha256=-F2ZOXXC07r96xlt9NomRgqlIqlcxzBiha2Zhg1DVp4,6845
38
- disagreement/ext/commands/converters.py,sha256=mh8xJr1FIiah6bdYy0KsdccfYcPii2Yc_IdhzCTw5uE,5864
39
- disagreement/ext/commands/core.py,sha256=4AO-U9xFyDetWeQUZiqX_g4zZfue0-9s8QBnHIb2BTc,21265
40
- disagreement/ext/commands/decorators.py,sha256=fOhppBae8gt-9QI1YqUzDctwOXmMBdAK_JaUJLNWHww,7427
41
- disagreement/ext/commands/errors.py,sha256=L6losXKye62BqDl42fXxgkuAkG92W-OcqH9uwEgabb8,2301
42
- disagreement/ext/commands/help.py,sha256=yw0ydupOsPwmnhsIIoxa93xjj9MAcBcGfD8BXa7V8G8,1456
43
- disagreement/ext/commands/view.py,sha256=3Wo4gGJX9fb65qw8yHFwMjnAeJvMJAx19rZNHz-ZDUs,3315
44
- disagreement/ui/__init__.py,sha256=PLA6eHiq9cu7JDOKS-7MKtaFhlqswjbI4AEUlpnbgO0,307
45
- disagreement/ui/button.py,sha256=GHbY3-yMrvv6u674-qYONocuC1e2a0flEWjPKwJXKDo,3163
46
- disagreement/ui/item.py,sha256=bm-EmQEZpe8Kt8JrRw-o0uQdccgjErORcFsBqaXOcV8,1112
47
- disagreement/ui/modal.py,sha256=w0ZEVslXzx2-RWUP4jdVB54zDuT81jpueVWZ70byFnI,4137
48
- disagreement/ui/select.py,sha256=XjWRlWkA09QZaDDLn-wDDOWIuj0Mb4VCWJEOAaExZXw,3018
49
- disagreement/ui/view.py,sha256=QhWoYt39QKXwl1X6Mkm5gNNEqd8bt7O505lSpiG0L04,5567
50
- disagreement-0.2.0rc1.dist-info/licenses/LICENSE,sha256=zfmtgJhVFHnqT7a8LAQFthXu5bP7EEHmEL99trV66Ew,1474
51
- disagreement-0.2.0rc1.dist-info/METADATA,sha256=ZEw0xKaAbZyXurATHmXCYwODfC6pr98jo27SlubhC_Q,5382
52
- disagreement-0.2.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- disagreement-0.2.0rc1.dist-info/top_level.txt,sha256=t7FY_3iaYhdB6X6y9VybJ2j7UZbVeRUe9wRgH8d5Gtk,13
54
- disagreement-0.2.0rc1.dist-info/RECORD,,