disagreement 0.0.2__py3-none-any.whl → 0.1.0rc1__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/models.py CHANGED
@@ -5,6 +5,9 @@ Data models for Discord objects.
5
5
  """
6
6
 
7
7
  import json
8
+ import asyncio
9
+ import aiohttp # pylint: disable=import-error
10
+ import asyncio
8
11
  from typing import Optional, TYPE_CHECKING, List, Dict, Any, Union
9
12
 
10
13
  from .errors import DisagreementException, HTTPException
@@ -21,12 +24,14 @@ from .enums import ( # These enums will need to be defined in disagreement/enum
21
24
  ButtonStyle, # Added for Button
22
25
  # SelectMenuType will be part of ComponentType or a new enum if needed
23
26
  )
27
+ from .permissions import Permissions
24
28
 
25
29
 
26
30
  if TYPE_CHECKING:
27
31
  from .client import Client # For type hinting to avoid circular imports
28
32
  from .enums import OverwriteType # For PermissionOverwrite model
29
33
  from .ui.view import View
34
+ from .interactions import Snowflake
30
35
 
31
36
  # Forward reference Message if it were used in type hints before its definition
32
37
  # from .models import Message # Not needed as Message is defined before its use in TextChannel.send etc.
@@ -72,6 +77,7 @@ class Message:
72
77
  timestamp (str): When this message was sent (ISO8601 timestamp).
73
78
  components (Optional[List[ActionRow]]): Structured components attached
74
79
  to the message if present.
80
+ attachments (List[Attachment]): Attachments included with the message.
75
81
  """
76
82
 
77
83
  def __init__(self, data: dict, client_instance: "Client"):
@@ -92,6 +98,9 @@ class Message:
92
98
  ]
93
99
  else:
94
100
  self.components = None
101
+ self.attachments: List[Attachment] = [
102
+ Attachment(a) for a in data.get("attachments", [])
103
+ ]
95
104
  # Add other fields as needed, e.g., attachments, embeds, reactions, etc.
96
105
  # self.mentions: List[User] = [User(u) for u in data.get("mentions", [])]
97
106
  # self.mention_roles: List[str] = data.get("mention_roles", [])
@@ -192,6 +201,22 @@ class Message:
192
201
  view=view,
193
202
  )
194
203
 
204
+ async def delete(self, delay: Optional[float] = None) -> None:
205
+ """|coro|
206
+
207
+ Deletes this message.
208
+
209
+ Parameters
210
+ ----------
211
+ delay:
212
+ If provided, wait this many seconds before deleting.
213
+ """
214
+
215
+ if delay is not None:
216
+ await asyncio.sleep(delay)
217
+
218
+ await self._client._http.delete_message(self.channel_id, self.id)
219
+
195
220
  def __repr__(self) -> str:
196
221
  return f"<Message id='{self.id}' channel_id='{self.channel_id}' author='{self.author!r}'>"
197
222
 
@@ -373,6 +398,14 @@ class Attachment:
373
398
  return payload
374
399
 
375
400
 
401
+ class File:
402
+ """Represents a file to be uploaded."""
403
+
404
+ def __init__(self, filename: str, data: bytes):
405
+ self.filename = filename
406
+ self.data = data
407
+
408
+
376
409
  class AllowedMentions:
377
410
  """Represents allowed mentions for a message or interaction response."""
378
411
 
@@ -527,6 +560,34 @@ class Member(User): # Member inherits from User
527
560
  reason=reason,
528
561
  )
529
562
 
563
+ @property
564
+ def top_role(self) -> Optional["Role"]:
565
+ """Return the member's highest role from the guild cache."""
566
+
567
+ if not self.guild_id or not self._client:
568
+ return None
569
+
570
+ guild = self._client.get_guild(self.guild_id)
571
+ if not guild:
572
+ return None
573
+
574
+ if not guild.roles and hasattr(self._client, "fetch_roles"):
575
+ try:
576
+ self._client.loop.run_until_complete(
577
+ self._client.fetch_roles(self.guild_id)
578
+ )
579
+ except RuntimeError:
580
+ future = asyncio.run_coroutine_threadsafe(
581
+ self._client.fetch_roles(self.guild_id), self._client.loop
582
+ )
583
+ future.result()
584
+
585
+ role_objects = [r for r in guild.roles if r.id in self.roles]
586
+ if not role_objects:
587
+ return None
588
+
589
+ return max(role_objects, key=lambda r: r.position)
590
+
530
591
 
531
592
  class PartialEmoji:
532
593
  """Represents a partial emoji, often used in components or reactions.
@@ -853,6 +914,31 @@ class Guild:
853
914
  def get_member(self, user_id: str) -> Optional[Member]:
854
915
  return self._members.get(user_id)
855
916
 
917
+ def get_member_named(self, name: str) -> Optional[Member]:
918
+ """Retrieve a cached member by username or nickname.
919
+
920
+ The lookup is case-insensitive and searches both the username and
921
+ guild nickname for a match.
922
+
923
+ Parameters
924
+ ----------
925
+ name: str
926
+ The username or nickname to search for.
927
+
928
+ Returns
929
+ -------
930
+ Optional[Member]
931
+ The matching member if found, otherwise ``None``.
932
+ """
933
+
934
+ lowered = name.lower()
935
+ for member in self._members.values():
936
+ if member.username.lower() == lowered:
937
+ return member
938
+ if member.nick and member.nick.lower() == lowered:
939
+ return member
940
+ return None
941
+
856
942
  def get_role(self, role_id: str) -> Optional[Role]:
857
943
  return next((role for role in self.roles if role.id == role_id), None)
858
944
 
@@ -893,6 +979,78 @@ class Channel:
893
979
  def __repr__(self) -> str:
894
980
  return f"<Channel id='{self.id}' name='{self.name}' type='{self.type.name if hasattr(self.type, 'name') else self._type_val}'>"
895
981
 
982
+ def permission_overwrite_for(
983
+ self, target: Union["Role", "Member", str]
984
+ ) -> Optional["PermissionOverwrite"]:
985
+ """Return the :class:`PermissionOverwrite` for ``target`` if present."""
986
+
987
+ if isinstance(target, str):
988
+ target_id = target
989
+ else:
990
+ target_id = target.id
991
+ for overwrite in self.permission_overwrites:
992
+ if overwrite.id == target_id:
993
+ return overwrite
994
+ return None
995
+
996
+ @staticmethod
997
+ def _apply_overwrite(
998
+ perms: Permissions, overwrite: Optional["PermissionOverwrite"]
999
+ ) -> Permissions:
1000
+ if overwrite is None:
1001
+ return perms
1002
+
1003
+ perms &= ~Permissions(int(overwrite.deny))
1004
+ perms |= Permissions(int(overwrite.allow))
1005
+ return perms
1006
+
1007
+ def permissions_for(self, member: "Member") -> Permissions:
1008
+ """Resolve channel permissions for ``member``."""
1009
+
1010
+ if self.guild_id is None:
1011
+ return Permissions(~0)
1012
+
1013
+ if not hasattr(self._client, "get_guild"):
1014
+ return Permissions(0)
1015
+
1016
+ guild = self._client.get_guild(self.guild_id)
1017
+ if guild is None:
1018
+ return Permissions(0)
1019
+
1020
+ base = Permissions(0)
1021
+
1022
+ everyone = guild.get_role(guild.id)
1023
+ if everyone is not None:
1024
+ base |= Permissions(int(everyone.permissions))
1025
+
1026
+ for rid in member.roles:
1027
+ role = guild.get_role(rid)
1028
+ if role is not None:
1029
+ base |= Permissions(int(role.permissions))
1030
+
1031
+ if base & Permissions.ADMINISTRATOR:
1032
+ return Permissions(~0)
1033
+
1034
+ # Apply @everyone overwrite
1035
+ base = self._apply_overwrite(base, self.permission_overwrite_for(guild.id))
1036
+
1037
+ # Role overwrites
1038
+ role_allow = Permissions(0)
1039
+ role_deny = Permissions(0)
1040
+ for rid in member.roles:
1041
+ ow = self.permission_overwrite_for(rid)
1042
+ if ow is not None:
1043
+ role_allow |= Permissions(int(ow.allow))
1044
+ role_deny |= Permissions(int(ow.deny))
1045
+
1046
+ base &= ~role_deny
1047
+ base |= role_allow
1048
+
1049
+ # Member overwrite
1050
+ base = self._apply_overwrite(base, self.permission_overwrite_for(member.id))
1051
+
1052
+ return base
1053
+
896
1054
 
897
1055
  class TextChannel(Channel):
898
1056
  """Represents a guild text channel or announcement channel."""
@@ -928,6 +1086,27 @@ class TextChannel(Channel):
928
1086
  components=components,
929
1087
  )
930
1088
 
1089
+ async def purge(
1090
+ self, limit: int, *, before: "Snowflake | None" = None
1091
+ ) -> List["Snowflake"]:
1092
+ """Bulk delete messages from this channel."""
1093
+
1094
+ params: Dict[str, Union[int, str]] = {"limit": limit}
1095
+ if before is not None:
1096
+ params["before"] = before
1097
+
1098
+ messages = await self._client._http.request(
1099
+ "GET", f"/channels/{self.id}/messages", params=params
1100
+ )
1101
+ ids = [m["id"] for m in messages]
1102
+ if not ids:
1103
+ return []
1104
+
1105
+ await self._client._http.bulk_delete_messages(self.id, ids)
1106
+ for mid in ids:
1107
+ self._client._messages.pop(mid, None)
1108
+ return ids
1109
+
931
1110
  def __repr__(self) -> str:
932
1111
  return f"<TextChannel id='{self.id}' name='{self.name}' guild_id='{self.guild_id}'>"
933
1112
 
@@ -1090,6 +1269,55 @@ class PartialChannel:
1090
1269
  return f"<PartialChannel id='{self.id}' name='{self.name}' type='{type_name}'>"
1091
1270
 
1092
1271
 
1272
+ class Webhook:
1273
+ """Represents a Discord Webhook."""
1274
+
1275
+ def __init__(
1276
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
1277
+ ):
1278
+ self._client: Optional["Client"] = client_instance
1279
+ self.id: str = data["id"]
1280
+ self.type: int = int(data.get("type", 1))
1281
+ self.guild_id: Optional[str] = data.get("guild_id")
1282
+ self.channel_id: Optional[str] = data.get("channel_id")
1283
+ self.name: Optional[str] = data.get("name")
1284
+ self.avatar: Optional[str] = data.get("avatar")
1285
+ self.token: Optional[str] = data.get("token")
1286
+ self.application_id: Optional[str] = data.get("application_id")
1287
+ self.url: Optional[str] = data.get("url")
1288
+ self.user: Optional[User] = User(data["user"]) if data.get("user") else None
1289
+
1290
+ def __repr__(self) -> str:
1291
+ return f"<Webhook id='{self.id}' name='{self.name}'>"
1292
+
1293
+ @classmethod
1294
+ def from_url(
1295
+ cls, url: str, session: Optional[aiohttp.ClientSession] = None
1296
+ ) -> "Webhook":
1297
+ """Create a minimal :class:`Webhook` from a webhook URL.
1298
+
1299
+ Parameters
1300
+ ----------
1301
+ url:
1302
+ The full Discord webhook URL.
1303
+ session:
1304
+ Unused for now. Present for API compatibility.
1305
+
1306
+ Returns
1307
+ -------
1308
+ Webhook
1309
+ A webhook instance containing only the ``id``, ``token`` and ``url``.
1310
+ """
1311
+
1312
+ parts = url.rstrip("/").split("/")
1313
+ if len(parts) < 2:
1314
+ raise ValueError("Invalid webhook URL")
1315
+ token = parts[-1]
1316
+ webhook_id = parts[-2]
1317
+
1318
+ return cls({"id": webhook_id, "token": token, "url": url})
1319
+
1320
+
1093
1321
  # --- Message Components ---
1094
1322
 
1095
1323
 
@@ -1433,7 +1661,7 @@ class MediaGallery(Component):
1433
1661
  return payload
1434
1662
 
1435
1663
 
1436
- class File(Component):
1664
+ class FileComponent(Component):
1437
1665
  """Represents a file component."""
1438
1666
 
1439
1667
  def __init__(
@@ -1614,6 +1842,85 @@ class TypingStart:
1614
1842
  return f"<TypingStart channel_id='{self.channel_id}' user_id='{self.user_id}'>"
1615
1843
 
1616
1844
 
1845
+ class Reaction:
1846
+ """Represents a message reaction event."""
1847
+
1848
+ def __init__(
1849
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
1850
+ ):
1851
+ self._client = client_instance
1852
+ self.user_id: str = data["user_id"]
1853
+ self.channel_id: str = data["channel_id"]
1854
+ self.message_id: str = data["message_id"]
1855
+ self.guild_id: Optional[str] = data.get("guild_id")
1856
+ self.member: Optional[Member] = (
1857
+ Member(data["member"], client_instance) if data.get("member") else None
1858
+ )
1859
+ self.emoji: Dict[str, Any] = data.get("emoji", {})
1860
+
1861
+ def __repr__(self) -> str:
1862
+ emoji_value = self.emoji.get("name") or self.emoji.get("id")
1863
+ return f"<Reaction message_id='{self.message_id}' user_id='{self.user_id}' emoji='{emoji_value}'>"
1864
+
1865
+
1866
+ class GuildMemberRemove:
1867
+ """Represents a GUILD_MEMBER_REMOVE event."""
1868
+
1869
+ def __init__(
1870
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
1871
+ ):
1872
+ self._client = client_instance
1873
+ self.guild_id: str = data["guild_id"]
1874
+ self.user: User = User(data["user"])
1875
+
1876
+ def __repr__(self) -> str:
1877
+ return (
1878
+ f"<GuildMemberRemove guild_id='{self.guild_id}' user_id='{self.user.id}'>"
1879
+ )
1880
+
1881
+
1882
+ class GuildBanAdd:
1883
+ """Represents a GUILD_BAN_ADD event."""
1884
+
1885
+ def __init__(
1886
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
1887
+ ):
1888
+ self._client = client_instance
1889
+ self.guild_id: str = data["guild_id"]
1890
+ self.user: User = User(data["user"])
1891
+
1892
+ def __repr__(self) -> str:
1893
+ return f"<GuildBanAdd guild_id='{self.guild_id}' user_id='{self.user.id}'>"
1894
+
1895
+
1896
+ class GuildBanRemove:
1897
+ """Represents a GUILD_BAN_REMOVE event."""
1898
+
1899
+ def __init__(
1900
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
1901
+ ):
1902
+ self._client = client_instance
1903
+ self.guild_id: str = data["guild_id"]
1904
+ self.user: User = User(data["user"])
1905
+
1906
+ def __repr__(self) -> str:
1907
+ return f"<GuildBanRemove guild_id='{self.guild_id}' user_id='{self.user.id}'>"
1908
+
1909
+
1910
+ class GuildRoleUpdate:
1911
+ """Represents a GUILD_ROLE_UPDATE event."""
1912
+
1913
+ def __init__(
1914
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
1915
+ ):
1916
+ self._client = client_instance
1917
+ self.guild_id: str = data["guild_id"]
1918
+ self.role: Role = Role(data["role"])
1919
+
1920
+ def __repr__(self) -> str:
1921
+ return f"<GuildRoleUpdate guild_id='{self.guild_id}' role_id='{self.role.id}'>"
1922
+
1923
+
1617
1924
  def channel_factory(data: Dict[str, Any], client: "Client") -> Channel:
1618
1925
  """Create a channel object from raw API data."""
1619
1926
  channel_type = data.get("type")
@@ -51,6 +51,8 @@ class ShardManager:
51
51
  verbose=self.client.verbose,
52
52
  shard_id=shard_id,
53
53
  shard_count=self.shard_count,
54
+ max_retries=self.client.gateway_max_retries,
55
+ max_backoff=self.client.gateway_max_backoff,
54
56
  )
55
57
  self.shards.append(Shard(shard_id, self.shard_count, gateway))
56
58
 
disagreement/utils.py ADDED
@@ -0,0 +1,10 @@
1
+ """Utility helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+
7
+
8
+ def utcnow() -> datetime:
9
+ """Return the current timezone-aware UTC time."""
10
+ return datetime.now(timezone.utc)
@@ -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.0rc1
4
4
  Summary: A Python library for the Discord API.
5
5
  Author-email: Slipstream <me@slipstreamm.dev>
6
6
  License: BSD 3-Clause
@@ -149,13 +149,20 @@ roles = await client.fetch_roles(guild.id)
149
149
 
150
150
  ## Sharding
151
151
 
152
- To run your bot across multiple gateway shards, pass `shard_count` when creating
152
+ To run your bot across multiple gateway shards, pass ``shard_count`` when creating
153
153
  the client:
154
154
 
155
155
  ```python
156
156
  client = disagreement.Client(token=BOT_TOKEN, shard_count=2)
157
157
  ```
158
158
 
159
+ If you want the library to determine the recommended shard count automatically,
160
+ use ``AutoShardedClient``:
161
+
162
+ ```python
163
+ client = disagreement.AutoShardedClient(token=BOT_TOKEN)
164
+ ```
165
+
159
166
  See `examples/sharded_bot.py` for a full example.
160
167
 
161
168
  ## Contributing
@@ -1,38 +1,41 @@
1
- disagreement/__init__.py,sha256=EHUd2pLLOoobJJ9HD_Vw0us5BbOshMKNvjl7toEVBp8,907
1
+ disagreement/__init__.py,sha256=hGvjtT1V8PPnxXYN_86hYZkHBvyP6u-tIoKD_4ZaJwo,1069
2
+ disagreement/audio.py,sha256=P6inobI8CNhNVkaRKU58RMYtLq1RrSREioF0Mui5VlA,3351
2
3
  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
4
+ disagreement/client.py,sha256=hwZtargXv4awVylzQMQUlb1WNWpvuICf5sIBK8Tn0bc,51858
5
+ disagreement/color.py,sha256=XK0Cw-q0_R5JMbHI1PNQeh1885TSplUHcLe2xF-oGho,1443
6
+ disagreement/components.py,sha256=tEYJ2RHVpIFtZuPPxZ0v8ssUw_x7ybhYBzHNsRiXXvU,5250
5
7
  disagreement/enums.py,sha256=LLeXdYKcx4TUhlojNV5X4NDuvscMbnteWRNW79d0C2c,9668
6
8
  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
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=uFKowJZYmguz_fe9PWEh6tfq2rvcEUUy2Abbqx9h-T0,27534
11
13
  disagreement/hybrid_context.py,sha256=VYCmcreTZdPBU9v-Cy48W38vgWO2t8nM2ulC6_z4HjU,1095
12
14
  disagreement/i18n.py,sha256=1L4rcFuKP0XjHk9dVwbNh4BkLk2ZlxxZ_-tecGWa9S0,718
13
15
  disagreement/interactions.py,sha256=corwLVsbWM2JXHk5u8VR_Qp3GINLcKFo2y7dyI53QFA,21645
14
16
  disagreement/logging_config.py,sha256=4q6baQPE6X_0lfaBTFMU1uqc03x5SbJqo2hsApdDFac,686
15
- disagreement/models.py,sha256=TMvGP17h1LK72SlA4vNUId76LdonKAUCTzqeNJRFzVQ,61475
17
+ disagreement/models.py,sha256=v_BfG7waYmzwouJUP6w3V94Y1lRR6AgyGvAaiZX_K1Y,71173
16
18
  disagreement/oauth.py,sha256=TfDdCwg1J7osM9wDi61dtNBA5BrQk5DeQrrHsYycH34,2810
17
19
  disagreement/permissions.py,sha256=7g5cIlg-evHXOL0-pmtT5EwqcB-stXot1HZSLz724sE,3008
18
20
  disagreement/rate_limiter.py,sha256=ubwR_UTPs2MotipBdtqpgwQKx0IHt2I3cdfFcXTFv7g,2521
19
- disagreement/shard_manager.py,sha256=R0HXruJ0Wda_3ftTztQx7kpI182jyuAMsjU6fDtz8Us,2039
21
+ disagreement/shard_manager.py,sha256=e9F8tx_4IEOlTX3-S3t51lfJToc6Ue3RVBzoNAiVKxs,2161
20
22
  disagreement/typing.py,sha256=_1oFWfZ4HyH5Q3bnF7CO24s79z-3_B5Qb69kWiwLhhU,1242
21
- disagreement/voice_client.py,sha256=KdFEH8PI6v45olTYoW_-DVOuTCk8SGg7BgSzxOQsILs,3869
23
+ disagreement/utils.py,sha256=Nj6rx--KnWCaamRHj1B2OXsZCznaMLrNdce-XY3YDg0,220
24
+ disagreement/voice_client.py,sha256=i_67gJ-SQWi9YH-pgtFM8N0lCYznyuQImyL-mf2O7KQ,5384
22
25
  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
26
+ disagreement/ext/loader.py,sha256=9_uULvNAa-a6UiaeQhWglwgIrHEPKbf9bnWtSL1KV5Q,1408
27
+ disagreement/ext/tasks.py,sha256=4MueuF6r9iPJ-2cEOw1A8tgn6SCetQc6Sr442-g8OrQ,5591
25
28
  disagreement/ext/app_commands/__init__.py,sha256=DQ3vHz2EaJ_hTlvFRpRSofPlCuFueRoLgLriECI5xFg,991
26
29
  disagreement/ext/app_commands/commands.py,sha256=cY9gyXovnyBIEWmhUPe8YZfw_WXCA1qXWDWTMmX_Vm8,24095
27
30
  disagreement/ext/app_commands/context.py,sha256=Xcm4Ka5K5uTQGviixF5LeCDdOdF9YQS5F7lZi2m--8s,20831
28
31
  disagreement/ext/app_commands/converters.py,sha256=J1VEmo-7H9K7kGPJodu5FX4RmFFI1BuzhlQAEs2MsD4,21036
29
32
  disagreement/ext/app_commands/decorators.py,sha256=smgx5RHedmgn7wxhBza78rQo5Guz8PEDDOvK5niJlMc,23236
30
33
  disagreement/ext/app_commands/handler.py,sha256=XO9yLgcV7aIxzhTMgFcQ1Tbr4GRZRfDBzkIAkiu6mw8,26045
31
- disagreement/ext/commands/__init__.py,sha256=Yw--e6mhE_qjgNg1qYM4yvRAOZfZD6avQ_IOGH0IQxA,1051
34
+ disagreement/ext/commands/__init__.py,sha256=HxZWVfc4qvP_bCRbKTVZoMqXFq19Gj4mQvRumvQiApQ,1130
32
35
  disagreement/ext/commands/cog.py,sha256=U57yMrUpqj3_-W1-koyfGgH43MZG_JzJOl46kTur7iA,6636
33
36
  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
37
+ disagreement/ext/commands/core.py,sha256=SAHvmhFKmsPWP-h3SzZ_sS2U8BSyM0clURqtfrGV8T0,19144
38
+ disagreement/ext/commands/decorators.py,sha256=Ox_D9KCFtMa-RiljFjOcsPb3stmDStRKeLw1DVeOdAw,6608
36
39
  disagreement/ext/commands/errors.py,sha256=cG5sPA-osUq2gts5scrl5yT-BHEYVHLTb4TULjAmbaY,2065
37
40
  disagreement/ext/commands/help.py,sha256=yw0ydupOsPwmnhsIIoxa93xjj9MAcBcGfD8BXa7V8G8,1456
38
41
  disagreement/ext/commands/view.py,sha256=3Wo4gGJX9fb65qw8yHFwMjnAeJvMJAx19rZNHz-ZDUs,3315
@@ -42,8 +45,8 @@ disagreement/ui/item.py,sha256=bm-EmQEZpe8Kt8JrRw-o0uQdccgjErORcFsBqaXOcV8,1112
42
45
  disagreement/ui/modal.py,sha256=FLWFy_VkZ9UAPumX3Q_bT0q7M06O1Q7XzBLhCZyhYhI,4120
43
46
  disagreement/ui/select.py,sha256=XjWRlWkA09QZaDDLn-wDDOWIuj0Mb4VCWJEOAaExZXw,3018
44
47
  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,,
48
+ disagreement-0.1.0rc1.dist-info/licenses/LICENSE,sha256=zfmtgJhVFHnqT7a8LAQFthXu5bP7EEHmEL99trV66Ew,1474
49
+ disagreement-0.1.0rc1.dist-info/METADATA,sha256=cFNTwfEbF3wsGzTBw16ZjYgNZbjDOexCsLlI3bSsxxE,4830
50
+ disagreement-0.1.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ disagreement-0.1.0rc1.dist-info/top_level.txt,sha256=t7FY_3iaYhdB6X6y9VybJ2j7UZbVeRUe9wRgH8d5Gtk,13
52
+ disagreement-0.1.0rc1.dist-info/RECORD,,