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/__init__.py +8 -3
- disagreement/audio.py +116 -0
- disagreement/client.py +176 -6
- disagreement/color.py +50 -0
- disagreement/components.py +2 -2
- disagreement/errors.py +13 -8
- disagreement/event_dispatcher.py +102 -45
- disagreement/ext/commands/__init__.py +9 -1
- disagreement/ext/commands/core.py +7 -0
- disagreement/ext/commands/decorators.py +72 -30
- disagreement/ext/loader.py +12 -1
- disagreement/ext/tasks.py +101 -8
- disagreement/gateway.py +56 -13
- disagreement/http.py +104 -3
- disagreement/models.py +308 -1
- disagreement/shard_manager.py +2 -0
- disagreement/utils.py +10 -0
- disagreement/voice_client.py +42 -0
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc1.dist-info}/METADATA +9 -2
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc1.dist-info}/RECORD +23 -20
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc1.dist-info}/WHEEL +0 -0
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc1.dist-info}/licenses/LICENSE +0 -0
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc1.dist-info}/top_level.txt +0 -0
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
|
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")
|
disagreement/shard_manager.py
CHANGED
@@ -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
disagreement/voice_client.py
CHANGED
@@ -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.
|
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
|
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=
|
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=
|
4
|
-
disagreement/
|
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=
|
8
|
-
disagreement/event_dispatcher.py,sha256=
|
9
|
-
disagreement/gateway.py,sha256=
|
10
|
-
disagreement/http.py,sha256=
|
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=
|
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=
|
21
|
+
disagreement/shard_manager.py,sha256=e9F8tx_4IEOlTX3-S3t51lfJToc6Ue3RVBzoNAiVKxs,2161
|
20
22
|
disagreement/typing.py,sha256=_1oFWfZ4HyH5Q3bnF7CO24s79z-3_B5Qb69kWiwLhhU,1242
|
21
|
-
disagreement/
|
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=
|
24
|
-
disagreement/ext/tasks.py,sha256=
|
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=
|
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=
|
35
|
-
disagreement/ext/commands/decorators.py,sha256=
|
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.
|
46
|
-
disagreement-0.
|
47
|
-
disagreement-0.
|
48
|
-
disagreement-0.
|
49
|
-
disagreement-0.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|