disagreement 0.0.2__py3-none-any.whl → 0.1.0rc2__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.
disagreement/utils.py ADDED
@@ -0,0 +1,73 @@
1
+ """Utility helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ from typing import Any, AsyncIterator, Dict, Optional, TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING: # pragma: no cover - for type hinting only
9
+ from .models import Message, TextChannel
10
+
11
+
12
+ def utcnow() -> datetime:
13
+ """Return the current timezone-aware UTC time."""
14
+ return datetime.now(timezone.utc)
15
+
16
+
17
+ async def message_pager(
18
+ channel: "TextChannel",
19
+ *,
20
+ limit: Optional[int] = None,
21
+ before: Optional[str] = None,
22
+ after: Optional[str] = None,
23
+ ) -> AsyncIterator["Message"]:
24
+ """Asynchronously paginate a channel's messages.
25
+
26
+ Parameters
27
+ ----------
28
+ channel:
29
+ The :class:`TextChannel` to fetch messages from.
30
+ limit:
31
+ The maximum number of messages to yield. ``None`` fetches until no
32
+ more messages are returned.
33
+ before:
34
+ Fetch messages with IDs less than this snowflake.
35
+ after:
36
+ Fetch messages with IDs greater than this snowflake.
37
+
38
+ Yields
39
+ ------
40
+ Message
41
+ Messages in the channel, oldest first.
42
+ """
43
+
44
+ remaining = limit
45
+ last_id = before
46
+ while remaining is None or remaining > 0:
47
+ fetch_limit = 100
48
+ if remaining is not None:
49
+ fetch_limit = min(fetch_limit, remaining)
50
+
51
+ params: Dict[str, Any] = {"limit": fetch_limit}
52
+ if last_id is not None:
53
+ params["before"] = last_id
54
+ if after is not None:
55
+ params["after"] = after
56
+
57
+ data = await channel._client._http.request( # type: ignore[attr-defined]
58
+ "GET",
59
+ f"/channels/{channel.id}/messages",
60
+ params=params,
61
+ )
62
+
63
+ if not data:
64
+ break
65
+
66
+ for raw in data:
67
+ msg = channel._client.parse_message(raw) # type: ignore[attr-defined]
68
+ yield msg
69
+ last_id = msg.id
70
+ if remaining is not None:
71
+ remaining -= 1
72
+ if remaining == 0:
73
+ return
@@ -10,6 +10,8 @@ from typing import Optional, Sequence
10
10
 
11
11
  import aiohttp
12
12
 
13
+ from .audio import AudioSource, FFmpegAudioSource
14
+
13
15
 
14
16
  class VoiceClient:
15
17
  """Handles the Discord voice WebSocket connection and UDP streaming."""
@@ -43,6 +45,8 @@ class VoiceClient:
43
45
  self.secret_key: Optional[Sequence[int]] = None
44
46
  self._server_ip: Optional[str] = None
45
47
  self._server_port: Optional[int] = None
48
+ self._current_source: Optional[AudioSource] = None
49
+ self._play_task: Optional[asyncio.Task] = None
46
50
 
47
51
  async def connect(self) -> None:
48
52
  if self._ws is None:
@@ -107,7 +111,45 @@ class VoiceClient:
107
111
  raise RuntimeError("UDP socket not initialised")
108
112
  self._udp.send(frame)
109
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
+
110
151
  async def close(self) -> None:
152
+ await self.stop()
111
153
  if self._heartbeat_task:
112
154
  self._heartbeat_task.cancel()
113
155
  with contextlib.suppress(asyncio.CancelledError):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: disagreement
3
- Version: 0.0.2
3
+ Version: 0.1.0rc2
4
4
  Summary: A Python library for the Discord API.
5
5
  Author-email: Slipstream <me@slipstreamm.dev>
6
6
  License: BSD 3-Clause
@@ -12,22 +12,23 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: BSD License
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
15
16
  Classifier: Programming Language :: Python :: 3.11
16
17
  Classifier: Programming Language :: Python :: 3.12
17
18
  Classifier: Programming Language :: Python :: 3.13
18
19
  Classifier: Topic :: Software Development :: Libraries
19
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
21
  Classifier: Topic :: Internet
21
- Requires-Python: >=3.11
22
+ Requires-Python: >=3.10
22
23
  Description-Content-Type: text/markdown
23
24
  License-File: LICENSE
24
25
  Requires-Dist: aiohttp<4.0.0,>=3.9.0
25
26
  Provides-Extra: test
26
27
  Requires-Dist: pytest>=8.0.0; extra == "test"
27
28
  Requires-Dist: pytest-asyncio>=1.0.0; extra == "test"
28
- Requires-Dist: hypothesis>=6.89.0; extra == "test"
29
+ Requires-Dist: hypothesis>=6.132.0; extra == "test"
29
30
  Provides-Extra: dev
30
- Requires-Dist: dotenv>=0.0.5; extra == "dev"
31
+ Requires-Dist: python-dotenv>=1.0.0; extra == "dev"
31
32
  Dynamic: license-file
32
33
 
33
34
  # Disagreement
@@ -53,7 +54,7 @@ pip install disagreement
53
54
  pip install -e .
54
55
  ```
55
56
 
56
- Requires Python 3.11 or newer.
57
+ Requires Python 3.10 or newer.
57
58
 
58
59
  ## Basic Usage
59
60
 
@@ -149,13 +150,20 @@ roles = await client.fetch_roles(guild.id)
149
150
 
150
151
  ## Sharding
151
152
 
152
- To run your bot across multiple gateway shards, pass `shard_count` when creating
153
+ To run your bot across multiple gateway shards, pass ``shard_count`` when creating
153
154
  the client:
154
155
 
155
156
  ```python
156
157
  client = disagreement.Client(token=BOT_TOKEN, shard_count=2)
157
158
  ```
158
159
 
160
+ If you want the library to determine the recommended shard count automatically,
161
+ use ``AutoShardedClient``:
162
+
163
+ ```python
164
+ client = disagreement.AutoShardedClient(token=BOT_TOKEN)
165
+ ```
166
+
159
167
  See `examples/sharded_bot.py` for a full example.
160
168
 
161
169
  ## Contributing
@@ -0,0 +1,53 @@
1
+ disagreement/__init__.py,sha256=4T6_19N6SjwgFXFONo6GjB3VbgODPZUc5soxJoVS_zY,1084
2
+ disagreement/audio.py,sha256=P6inobI8CNhNVkaRKU58RMYtLq1RrSREioF0Mui5VlA,3351
3
+ disagreement/cache.py,sha256=juabGFl4naQih5OUIVN2aN-vAfw2ZC2cI38s4nGEn8U,1525
4
+ disagreement/client.py,sha256=7or564FS7fXALDyRvNrt-H32mtV1VLysuIs3CtQ9L3s,53182
5
+ disagreement/color.py,sha256=g-1ynMGCUbY0f6jJXzMLS1aJFoZg91bdMetFkZgaCC0,2387
6
+ disagreement/components.py,sha256=tEYJ2RHVpIFtZuPPxZ0v8ssUw_x7ybhYBzHNsRiXXvU,5250
7
+ disagreement/enums.py,sha256=CP03oF28maaPUVckOfE_tnVIm2ZOc5WL8mWlocmjeOQ,9785
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=mhFtBm_YPtbleQJklv3ph3DXE4LZxC1BhtNkd7Y-akQ,23113
12
+ disagreement/http.py,sha256=4FnVMIRjrNSpQ9IUEeWQGP6jYAhv3YTTANqFDNv0SdY,31046
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=JeHKJWrc7mN70oFNgd7Iic9_SgHfDlgz7aTRgMA-5PA,75070
18
+ disagreement/oauth.py,sha256=TfDdCwg1J7osM9wDi61dtNBA5BrQk5DeQrrHsYycH34,2810
19
+ disagreement/permissions.py,sha256=7g5cIlg-evHXOL0-pmtT5EwqcB-stXot1HZSLz724sE,3008
20
+ disagreement/rate_limiter.py,sha256=ubwR_UTPs2MotipBdtqpgwQKx0IHt2I3cdfFcXTFv7g,2521
21
+ disagreement/shard_manager.py,sha256=e9F8tx_4IEOlTX3-S3t51lfJToc6Ue3RVBzoNAiVKxs,2161
22
+ disagreement/typing.py,sha256=_1oFWfZ4HyH5Q3bnF7CO24s79z-3_B5Qb69kWiwLhhU,1242
23
+ disagreement/utils.py,sha256=mz7foTCOAmUv9n8EcdZeiFarwqB14xHOG8o0p8tFuKA,2014
24
+ disagreement/voice_client.py,sha256=i_67gJ-SQWi9YH-pgtFM8N0lCYznyuQImyL-mf2O7KQ,5384
25
+ disagreement/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ disagreement/ext/loader.py,sha256=9_uULvNAa-a6UiaeQhWglwgIrHEPKbf9bnWtSL1KV5Q,1408
27
+ disagreement/ext/tasks.py,sha256=b14KI-btikbrjPlD76md3Ggt6znrxPqr7TDarU4PYBg,7269
28
+ disagreement/ext/app_commands/__init__.py,sha256=mnQLIuGP9SzqGMPEn5YgOh2eIU7lcYoDXP06vtXZfTA,1014
29
+ disagreement/ext/app_commands/commands.py,sha256=0O5fJQg2esTQzx2FyEpM2ZrrLckNmv8fs3TIPnr1Q_s,19020
30
+ disagreement/ext/app_commands/context.py,sha256=Xcm4Ka5K5uTQGviixF5LeCDdOdF9YQS5F7lZi2m--8s,20831
31
+ disagreement/ext/app_commands/converters.py,sha256=J1VEmo-7H9K7kGPJodu5FX4RmFFI1BuzhlQAEs2MsD4,21036
32
+ disagreement/ext/app_commands/decorators.py,sha256=dKiD4ZEsafRoPvfgn9zuQ9vvXXo2qYTMquHvyUM1604,23251
33
+ disagreement/ext/app_commands/handler.py,sha256=XO9yLgcV7aIxzhTMgFcQ1Tbr4GRZRfDBzkIAkiu6mw8,26045
34
+ disagreement/ext/app_commands/hybrid.py,sha256=yRDnlnOqgo79X669WhBHQ6LJCCuFDXKUbmlC_u3aXR0,3329
35
+ disagreement/ext/commands/__init__.py,sha256=HxZWVfc4qvP_bCRbKTVZoMqXFq19Gj4mQvRumvQiApQ,1130
36
+ disagreement/ext/commands/cog.py,sha256=U57yMrUpqj3_-W1-koyfGgH43MZG_JzJOl46kTur7iA,6636
37
+ disagreement/ext/commands/converters.py,sha256=mh8xJr1FIiah6bdYy0KsdccfYcPii2Yc_IdhzCTw5uE,5864
38
+ disagreement/ext/commands/core.py,sha256=vwsj3GR9wrEy0y-1HJv16Hg9-9xnm61tvT9b14QlYEI,19296
39
+ disagreement/ext/commands/decorators.py,sha256=Ox_D9KCFtMa-RiljFjOcsPb3stmDStRKeLw1DVeOdAw,6608
40
+ disagreement/ext/commands/errors.py,sha256=cG5sPA-osUq2gts5scrl5yT-BHEYVHLTb4TULjAmbaY,2065
41
+ disagreement/ext/commands/help.py,sha256=yw0ydupOsPwmnhsIIoxa93xjj9MAcBcGfD8BXa7V8G8,1456
42
+ disagreement/ext/commands/view.py,sha256=3Wo4gGJX9fb65qw8yHFwMjnAeJvMJAx19rZNHz-ZDUs,3315
43
+ disagreement/ui/__init__.py,sha256=PLA6eHiq9cu7JDOKS-7MKtaFhlqswjbI4AEUlpnbgO0,307
44
+ disagreement/ui/button.py,sha256=GHbY3-yMrvv6u674-qYONocuC1e2a0flEWjPKwJXKDo,3163
45
+ disagreement/ui/item.py,sha256=bm-EmQEZpe8Kt8JrRw-o0uQdccgjErORcFsBqaXOcV8,1112
46
+ disagreement/ui/modal.py,sha256=w0ZEVslXzx2-RWUP4jdVB54zDuT81jpueVWZ70byFnI,4137
47
+ disagreement/ui/select.py,sha256=XjWRlWkA09QZaDDLn-wDDOWIuj0Mb4VCWJEOAaExZXw,3018
48
+ disagreement/ui/view.py,sha256=QhWoYt39QKXwl1X6Mkm5gNNEqd8bt7O505lSpiG0L04,5567
49
+ disagreement-0.1.0rc2.dist-info/licenses/LICENSE,sha256=zfmtgJhVFHnqT7a8LAQFthXu5bP7EEHmEL99trV66Ew,1474
50
+ disagreement-0.1.0rc2.dist-info/METADATA,sha256=qMql6NO40Fgv8w-yvoFgBq7V8glJbRJvN3-pyeDnQOY,4889
51
+ disagreement-0.1.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
+ disagreement-0.1.0rc2.dist-info/top_level.txt,sha256=t7FY_3iaYhdB6X6y9VybJ2j7UZbVeRUe9wRgH8d5Gtk,13
53
+ disagreement-0.1.0rc2.dist-info/RECORD,,
@@ -1,49 +0,0 @@
1
- disagreement/__init__.py,sha256=EHUd2pLLOoobJJ9HD_Vw0us5BbOshMKNvjl7toEVBp8,907
2
- disagreement/cache.py,sha256=juabGFl4naQih5OUIVN2aN-vAfw2ZC2cI38s4nGEn8U,1525
3
- disagreement/client.py,sha256=pFXj7z7R1kJWz3_GWOzDYrF1S-nQ1agjcXnRTSh-PWE,45820
4
- disagreement/components.py,sha256=W_R9iMETkQj6sr-Lzk2n7hLwLNaLWT4vBPArIPHQUNc,5232
5
- disagreement/enums.py,sha256=LLeXdYKcx4TUhlojNV5X4NDuvscMbnteWRNW79d0C2c,9668
6
- disagreement/error_handler.py,sha256=c2lb6aTMnhTtITQuR6axZUtEaasYKUgmdSxAHEkeq50,1028
7
- disagreement/errors.py,sha256=rCr9jVAzK8wsS6mxieeWpffKhTDX--sHuOBz45kwsAA,3215
8
- disagreement/event_dispatcher.py,sha256=BevGAi72qXAHN_FqCOSdvVhOhINLeI2ojyVLmvrSKJ0,9851
9
- disagreement/gateway.py,sha256=85WSEZregWUvuc0M-DjErOCJM697mqHwdRw9Iz-w7aU,21384
10
- disagreement/http.py,sha256=1lHIEq2RRVOzzSpfj9TNGprJsMW_nhbj_8-fPr0IupI,23986
11
- disagreement/hybrid_context.py,sha256=VYCmcreTZdPBU9v-Cy48W38vgWO2t8nM2ulC6_z4HjU,1095
12
- disagreement/i18n.py,sha256=1L4rcFuKP0XjHk9dVwbNh4BkLk2ZlxxZ_-tecGWa9S0,718
13
- disagreement/interactions.py,sha256=corwLVsbWM2JXHk5u8VR_Qp3GINLcKFo2y7dyI53QFA,21645
14
- disagreement/logging_config.py,sha256=4q6baQPE6X_0lfaBTFMU1uqc03x5SbJqo2hsApdDFac,686
15
- disagreement/models.py,sha256=TMvGP17h1LK72SlA4vNUId76LdonKAUCTzqeNJRFzVQ,61475
16
- disagreement/oauth.py,sha256=TfDdCwg1J7osM9wDi61dtNBA5BrQk5DeQrrHsYycH34,2810
17
- disagreement/permissions.py,sha256=7g5cIlg-evHXOL0-pmtT5EwqcB-stXot1HZSLz724sE,3008
18
- disagreement/rate_limiter.py,sha256=ubwR_UTPs2MotipBdtqpgwQKx0IHt2I3cdfFcXTFv7g,2521
19
- disagreement/shard_manager.py,sha256=R0HXruJ0Wda_3ftTztQx7kpI182jyuAMsjU6fDtz8Us,2039
20
- disagreement/typing.py,sha256=_1oFWfZ4HyH5Q3bnF7CO24s79z-3_B5Qb69kWiwLhhU,1242
21
- disagreement/voice_client.py,sha256=KdFEH8PI6v45olTYoW_-DVOuTCk8SGg7BgSzxOQsILs,3869
22
- disagreement/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- disagreement/ext/loader.py,sha256=Gl2Btaimw_Tm159zLhni9f_Q7pXhOAA4uEpolB4mGAI,1128
24
- disagreement/ext/tasks.py,sha256=nlsmpHAhbITKe2Rp-jM8KWdruykNf1MTRrSrXdFMWUE,2671
25
- disagreement/ext/app_commands/__init__.py,sha256=DQ3vHz2EaJ_hTlvFRpRSofPlCuFueRoLgLriECI5xFg,991
26
- disagreement/ext/app_commands/commands.py,sha256=cY9gyXovnyBIEWmhUPe8YZfw_WXCA1qXWDWTMmX_Vm8,24095
27
- disagreement/ext/app_commands/context.py,sha256=Xcm4Ka5K5uTQGviixF5LeCDdOdF9YQS5F7lZi2m--8s,20831
28
- disagreement/ext/app_commands/converters.py,sha256=J1VEmo-7H9K7kGPJodu5FX4RmFFI1BuzhlQAEs2MsD4,21036
29
- disagreement/ext/app_commands/decorators.py,sha256=smgx5RHedmgn7wxhBza78rQo5Guz8PEDDOvK5niJlMc,23236
30
- disagreement/ext/app_commands/handler.py,sha256=XO9yLgcV7aIxzhTMgFcQ1Tbr4GRZRfDBzkIAkiu6mw8,26045
31
- disagreement/ext/commands/__init__.py,sha256=Yw--e6mhE_qjgNg1qYM4yvRAOZfZD6avQ_IOGH0IQxA,1051
32
- disagreement/ext/commands/cog.py,sha256=U57yMrUpqj3_-W1-koyfGgH43MZG_JzJOl46kTur7iA,6636
33
- disagreement/ext/commands/converters.py,sha256=mh8xJr1FIiah6bdYy0KsdccfYcPii2Yc_IdhzCTw5uE,5864
34
- disagreement/ext/commands/core.py,sha256=CoX38qL-5WIola_KnShb3Za-uZSLhpfBW1pPkHw2f84,18905
35
- disagreement/ext/commands/decorators.py,sha256=ca06AuRznoe4ZTma9EmO-lw9kQRliOHWlQcpURb840o,5525
36
- disagreement/ext/commands/errors.py,sha256=cG5sPA-osUq2gts5scrl5yT-BHEYVHLTb4TULjAmbaY,2065
37
- disagreement/ext/commands/help.py,sha256=yw0ydupOsPwmnhsIIoxa93xjj9MAcBcGfD8BXa7V8G8,1456
38
- disagreement/ext/commands/view.py,sha256=3Wo4gGJX9fb65qw8yHFwMjnAeJvMJAx19rZNHz-ZDUs,3315
39
- disagreement/ui/__init__.py,sha256=PLA6eHiq9cu7JDOKS-7MKtaFhlqswjbI4AEUlpnbgO0,307
40
- disagreement/ui/button.py,sha256=GHbY3-yMrvv6u674-qYONocuC1e2a0flEWjPKwJXKDo,3163
41
- disagreement/ui/item.py,sha256=bm-EmQEZpe8Kt8JrRw-o0uQdccgjErORcFsBqaXOcV8,1112
42
- disagreement/ui/modal.py,sha256=FLWFy_VkZ9UAPumX3Q_bT0q7M06O1Q7XzBLhCZyhYhI,4120
43
- disagreement/ui/select.py,sha256=XjWRlWkA09QZaDDLn-wDDOWIuj0Mb4VCWJEOAaExZXw,3018
44
- disagreement/ui/view.py,sha256=QhWoYt39QKXwl1X6Mkm5gNNEqd8bt7O505lSpiG0L04,5567
45
- disagreement-0.0.2.dist-info/licenses/LICENSE,sha256=zfmtgJhVFHnqT7a8LAQFthXu5bP7EEHmEL99trV66Ew,1474
46
- disagreement-0.0.2.dist-info/METADATA,sha256=8Sc8qa3lQKVkXWV-9TbXV-fDi0gbWjrOm0a0pPlFTTg,4645
47
- disagreement-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
- disagreement-0.0.2.dist-info/top_level.txt,sha256=t7FY_3iaYhdB6X6y9VybJ2j7UZbVeRUe9wRgH8d5Gtk,13
49
- disagreement-0.0.2.dist-info/RECORD,,