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/__init__.py +8 -3
- disagreement/audio.py +116 -0
- disagreement/client.py +217 -8
- disagreement/color.py +78 -0
- disagreement/components.py +2 -2
- disagreement/enums.py +5 -0
- disagreement/errors.py +13 -8
- disagreement/event_dispatcher.py +102 -45
- disagreement/ext/app_commands/__init__.py +2 -0
- disagreement/ext/app_commands/commands.py +13 -99
- disagreement/ext/app_commands/decorators.py +1 -1
- disagreement/ext/app_commands/hybrid.py +61 -0
- disagreement/ext/commands/__init__.py +9 -1
- disagreement/ext/commands/core.py +15 -2
- disagreement/ext/commands/decorators.py +72 -30
- disagreement/ext/loader.py +12 -1
- disagreement/ext/tasks.py +147 -8
- disagreement/gateway.py +56 -13
- disagreement/http.py +219 -16
- disagreement/interactions.py +17 -14
- disagreement/models.py +432 -7
- disagreement/shard_manager.py +2 -0
- disagreement/ui/modal.py +1 -1
- disagreement/utils.py +73 -0
- disagreement/voice_client.py +42 -0
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc2.dist-info}/METADATA +14 -6
- disagreement-0.1.0rc2.dist-info/RECORD +53 -0
- disagreement-0.0.2.dist-info/RECORD +0 -49
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc2.dist-info}/WHEEL +0 -0
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc2.dist-info}/licenses/LICENSE +0 -0
- {disagreement-0.0.2.dist-info → disagreement-0.1.0rc2.dist-info}/top_level.txt +0 -0
disagreement/interactions.py
CHANGED
@@ -395,17 +395,14 @@ class Interaction:
|
|
395
395
|
|
396
396
|
async def respond_modal(self, modal: "Modal") -> None:
|
397
397
|
"""|coro| Send a modal in response to this interaction."""
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
"type": InteractionCallbackType.MODAL.value,
|
403
|
-
"data": modal.to_dict(),
|
404
|
-
}
|
398
|
+
payload = InteractionResponsePayload(
|
399
|
+
type=InteractionCallbackType.MODAL,
|
400
|
+
data=modal.to_dict(),
|
401
|
+
)
|
405
402
|
await self._client._http.create_interaction_response(
|
406
403
|
interaction_id=self.id,
|
407
404
|
interaction_token=self.token,
|
408
|
-
payload=
|
405
|
+
payload=payload,
|
409
406
|
)
|
410
407
|
|
411
408
|
async def edit(
|
@@ -489,7 +486,7 @@ class InteractionResponse:
|
|
489
486
|
"""Sends a modal response."""
|
490
487
|
payload = InteractionResponsePayload(
|
491
488
|
type=InteractionCallbackType.MODAL,
|
492
|
-
data=
|
489
|
+
data=modal.to_dict(),
|
493
490
|
)
|
494
491
|
await self._interaction._client._http.create_interaction_response(
|
495
492
|
self._interaction.id,
|
@@ -510,7 +507,7 @@ class InteractionCallbackData:
|
|
510
507
|
)
|
511
508
|
self.allowed_mentions: Optional[AllowedMentions] = (
|
512
509
|
AllowedMentions(data["allowed_mentions"])
|
513
|
-
if
|
510
|
+
if "allowed_mentions" in data
|
514
511
|
else None
|
515
512
|
)
|
516
513
|
self.flags: Optional[int] = data.get("flags") # MessageFlags enum could be used
|
@@ -557,16 +554,22 @@ class InteractionResponsePayload:
|
|
557
554
|
def __init__(
|
558
555
|
self,
|
559
556
|
type: InteractionCallbackType,
|
560
|
-
data: Optional[InteractionCallbackData] = None,
|
557
|
+
data: Optional[Union[InteractionCallbackData, Dict[str, Any]]] = None,
|
561
558
|
):
|
562
|
-
self.type
|
563
|
-
self.data
|
559
|
+
self.type = type
|
560
|
+
self.data = data
|
564
561
|
|
565
562
|
def to_dict(self) -> Dict[str, Any]:
|
566
563
|
payload: Dict[str, Any] = {"type": self.type.value}
|
567
564
|
if self.data:
|
568
|
-
|
565
|
+
if isinstance(self.data, dict):
|
566
|
+
payload["data"] = self.data
|
567
|
+
else:
|
568
|
+
payload["data"] = self.data.to_dict()
|
569
569
|
return payload
|
570
570
|
|
571
571
|
def __repr__(self) -> str:
|
572
572
|
return f"<InteractionResponsePayload type={self.type!r}>"
|
573
|
+
|
574
|
+
def __getitem__(self, key: str) -> Any:
|
575
|
+
return self.to_dict()[key]
|
disagreement/models.py
CHANGED
@@ -5,7 +5,10 @@ Data models for Discord objects.
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import json
|
8
|
-
|
8
|
+
import asyncio
|
9
|
+
import aiohttp # pylint: disable=import-error
|
10
|
+
import asyncio
|
11
|
+
from typing import Any, AsyncIterator, Dict, List, Optional, TYPE_CHECKING, Union
|
9
12
|
|
10
13
|
from .errors import DisagreementException, HTTPException
|
11
14
|
from .enums import ( # These enums will need to be defined in disagreement/enums.py
|
@@ -21,12 +24,15 @@ 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
|
28
|
+
from .color import Color
|
24
29
|
|
25
30
|
|
26
31
|
if TYPE_CHECKING:
|
27
32
|
from .client import Client # For type hinting to avoid circular imports
|
28
33
|
from .enums import OverwriteType # For PermissionOverwrite model
|
29
34
|
from .ui.view import View
|
35
|
+
from .interactions import Snowflake
|
30
36
|
|
31
37
|
# Forward reference Message if it were used in type hints before its definition
|
32
38
|
# from .models import Message # Not needed as Message is defined before its use in TextChannel.send etc.
|
@@ -72,6 +78,7 @@ class Message:
|
|
72
78
|
timestamp (str): When this message was sent (ISO8601 timestamp).
|
73
79
|
components (Optional[List[ActionRow]]): Structured components attached
|
74
80
|
to the message if present.
|
81
|
+
attachments (List[Attachment]): Attachments included with the message.
|
75
82
|
"""
|
76
83
|
|
77
84
|
def __init__(self, data: dict, client_instance: "Client"):
|
@@ -92,6 +99,9 @@ class Message:
|
|
92
99
|
]
|
93
100
|
else:
|
94
101
|
self.components = None
|
102
|
+
self.attachments: List[Attachment] = [
|
103
|
+
Attachment(a) for a in data.get("attachments", [])
|
104
|
+
]
|
95
105
|
# Add other fields as needed, e.g., attachments, embeds, reactions, etc.
|
96
106
|
# self.mentions: List[User] = [User(u) for u in data.get("mentions", [])]
|
97
107
|
# self.mention_roles: List[str] = data.get("mention_roles", [])
|
@@ -192,6 +202,37 @@ class Message:
|
|
192
202
|
view=view,
|
193
203
|
)
|
194
204
|
|
205
|
+
async def add_reaction(self, emoji: str) -> None:
|
206
|
+
"""|coro| Add a reaction to this message."""
|
207
|
+
|
208
|
+
await self._client.add_reaction(self.channel_id, self.id, emoji)
|
209
|
+
|
210
|
+
async def remove_reaction(self, emoji: str) -> None:
|
211
|
+
"""|coro| Remove the bot's reaction from this message."""
|
212
|
+
|
213
|
+
await self._client.remove_reaction(self.channel_id, self.id, emoji)
|
214
|
+
|
215
|
+
async def clear_reactions(self) -> None:
|
216
|
+
"""|coro| Remove all reactions from this message."""
|
217
|
+
|
218
|
+
await self._client.clear_reactions(self.channel_id, self.id)
|
219
|
+
|
220
|
+
async def delete(self, delay: Optional[float] = None) -> None:
|
221
|
+
"""|coro|
|
222
|
+
|
223
|
+
Deletes this message.
|
224
|
+
|
225
|
+
Parameters
|
226
|
+
----------
|
227
|
+
delay:
|
228
|
+
If provided, wait this many seconds before deleting.
|
229
|
+
"""
|
230
|
+
|
231
|
+
if delay is not None:
|
232
|
+
await asyncio.sleep(delay)
|
233
|
+
|
234
|
+
await self._client._http.delete_message(self.channel_id, self.id)
|
235
|
+
|
195
236
|
def __repr__(self) -> str:
|
196
237
|
return f"<Message id='{self.id}' channel_id='{self.channel_id}' author='{self.author!r}'>"
|
197
238
|
|
@@ -287,7 +328,7 @@ class Embed:
|
|
287
328
|
self.description: Optional[str] = data.get("description")
|
288
329
|
self.url: Optional[str] = data.get("url")
|
289
330
|
self.timestamp: Optional[str] = data.get("timestamp") # ISO8601 timestamp
|
290
|
-
self.color
|
331
|
+
self.color = Color.parse(data.get("color"))
|
291
332
|
|
292
333
|
self.footer: Optional[EmbedFooter] = (
|
293
334
|
EmbedFooter(data["footer"]) if data.get("footer") else None
|
@@ -317,7 +358,7 @@ class Embed:
|
|
317
358
|
if self.timestamp:
|
318
359
|
payload["timestamp"] = self.timestamp
|
319
360
|
if self.color is not None:
|
320
|
-
payload["color"] = self.color
|
361
|
+
payload["color"] = self.color.value
|
321
362
|
if self.footer:
|
322
363
|
payload["footer"] = self.footer.to_dict()
|
323
364
|
if self.image:
|
@@ -373,6 +414,14 @@ class Attachment:
|
|
373
414
|
return payload
|
374
415
|
|
375
416
|
|
417
|
+
class File:
|
418
|
+
"""Represents a file to be uploaded."""
|
419
|
+
|
420
|
+
def __init__(self, filename: str, data: bytes):
|
421
|
+
self.filename = filename
|
422
|
+
self.data = data
|
423
|
+
|
424
|
+
|
376
425
|
class AllowedMentions:
|
377
426
|
"""Represents allowed mentions for a message or interaction response."""
|
378
427
|
|
@@ -495,6 +544,12 @@ class Member(User): # Member inherits from User
|
|
495
544
|
def __repr__(self) -> str:
|
496
545
|
return f"<Member id='{self.id}' username='{self.username}' nick='{self.nick}'>"
|
497
546
|
|
547
|
+
@property
|
548
|
+
def display_name(self) -> str:
|
549
|
+
"""Return the nickname if set, otherwise the username."""
|
550
|
+
|
551
|
+
return self.nick or self.username
|
552
|
+
|
498
553
|
async def kick(self, *, reason: Optional[str] = None) -> None:
|
499
554
|
if not self.guild_id or not self._client:
|
500
555
|
raise DisagreementException("Member.kick requires guild_id and client")
|
@@ -527,6 +582,34 @@ class Member(User): # Member inherits from User
|
|
527
582
|
reason=reason,
|
528
583
|
)
|
529
584
|
|
585
|
+
@property
|
586
|
+
def top_role(self) -> Optional["Role"]:
|
587
|
+
"""Return the member's highest role from the guild cache."""
|
588
|
+
|
589
|
+
if not self.guild_id or not self._client:
|
590
|
+
return None
|
591
|
+
|
592
|
+
guild = self._client.get_guild(self.guild_id)
|
593
|
+
if not guild:
|
594
|
+
return None
|
595
|
+
|
596
|
+
if not guild.roles and hasattr(self._client, "fetch_roles"):
|
597
|
+
try:
|
598
|
+
self._client.loop.run_until_complete(
|
599
|
+
self._client.fetch_roles(self.guild_id)
|
600
|
+
)
|
601
|
+
except RuntimeError:
|
602
|
+
future = asyncio.run_coroutine_threadsafe(
|
603
|
+
self._client.fetch_roles(self.guild_id), self._client.loop
|
604
|
+
)
|
605
|
+
future.result()
|
606
|
+
|
607
|
+
role_objects = [r for r in guild.roles if r.id in self.roles]
|
608
|
+
if not role_objects:
|
609
|
+
return None
|
610
|
+
|
611
|
+
return max(role_objects, key=lambda r: r.position)
|
612
|
+
|
530
613
|
|
531
614
|
class PartialEmoji:
|
532
615
|
"""Represents a partial emoji, often used in components or reactions.
|
@@ -853,6 +936,31 @@ class Guild:
|
|
853
936
|
def get_member(self, user_id: str) -> Optional[Member]:
|
854
937
|
return self._members.get(user_id)
|
855
938
|
|
939
|
+
def get_member_named(self, name: str) -> Optional[Member]:
|
940
|
+
"""Retrieve a cached member by username or nickname.
|
941
|
+
|
942
|
+
The lookup is case-insensitive and searches both the username and
|
943
|
+
guild nickname for a match.
|
944
|
+
|
945
|
+
Parameters
|
946
|
+
----------
|
947
|
+
name: str
|
948
|
+
The username or nickname to search for.
|
949
|
+
|
950
|
+
Returns
|
951
|
+
-------
|
952
|
+
Optional[Member]
|
953
|
+
The matching member if found, otherwise ``None``.
|
954
|
+
"""
|
955
|
+
|
956
|
+
lowered = name.lower()
|
957
|
+
for member in self._members.values():
|
958
|
+
if member.username.lower() == lowered:
|
959
|
+
return member
|
960
|
+
if member.nick and member.nick.lower() == lowered:
|
961
|
+
return member
|
962
|
+
return None
|
963
|
+
|
856
964
|
def get_role(self, role_id: str) -> Optional[Role]:
|
857
965
|
return next((role for role in self.roles if role.id == role_id), None)
|
858
966
|
|
@@ -893,6 +1001,78 @@ class Channel:
|
|
893
1001
|
def __repr__(self) -> str:
|
894
1002
|
return f"<Channel id='{self.id}' name='{self.name}' type='{self.type.name if hasattr(self.type, 'name') else self._type_val}'>"
|
895
1003
|
|
1004
|
+
def permission_overwrite_for(
|
1005
|
+
self, target: Union["Role", "Member", str]
|
1006
|
+
) -> Optional["PermissionOverwrite"]:
|
1007
|
+
"""Return the :class:`PermissionOverwrite` for ``target`` if present."""
|
1008
|
+
|
1009
|
+
if isinstance(target, str):
|
1010
|
+
target_id = target
|
1011
|
+
else:
|
1012
|
+
target_id = target.id
|
1013
|
+
for overwrite in self.permission_overwrites:
|
1014
|
+
if overwrite.id == target_id:
|
1015
|
+
return overwrite
|
1016
|
+
return None
|
1017
|
+
|
1018
|
+
@staticmethod
|
1019
|
+
def _apply_overwrite(
|
1020
|
+
perms: Permissions, overwrite: Optional["PermissionOverwrite"]
|
1021
|
+
) -> Permissions:
|
1022
|
+
if overwrite is None:
|
1023
|
+
return perms
|
1024
|
+
|
1025
|
+
perms &= ~Permissions(int(overwrite.deny))
|
1026
|
+
perms |= Permissions(int(overwrite.allow))
|
1027
|
+
return perms
|
1028
|
+
|
1029
|
+
def permissions_for(self, member: "Member") -> Permissions:
|
1030
|
+
"""Resolve channel permissions for ``member``."""
|
1031
|
+
|
1032
|
+
if self.guild_id is None:
|
1033
|
+
return Permissions(~0)
|
1034
|
+
|
1035
|
+
if not hasattr(self._client, "get_guild"):
|
1036
|
+
return Permissions(0)
|
1037
|
+
|
1038
|
+
guild = self._client.get_guild(self.guild_id)
|
1039
|
+
if guild is None:
|
1040
|
+
return Permissions(0)
|
1041
|
+
|
1042
|
+
base = Permissions(0)
|
1043
|
+
|
1044
|
+
everyone = guild.get_role(guild.id)
|
1045
|
+
if everyone is not None:
|
1046
|
+
base |= Permissions(int(everyone.permissions))
|
1047
|
+
|
1048
|
+
for rid in member.roles:
|
1049
|
+
role = guild.get_role(rid)
|
1050
|
+
if role is not None:
|
1051
|
+
base |= Permissions(int(role.permissions))
|
1052
|
+
|
1053
|
+
if base & Permissions.ADMINISTRATOR:
|
1054
|
+
return Permissions(~0)
|
1055
|
+
|
1056
|
+
# Apply @everyone overwrite
|
1057
|
+
base = self._apply_overwrite(base, self.permission_overwrite_for(guild.id))
|
1058
|
+
|
1059
|
+
# Role overwrites
|
1060
|
+
role_allow = Permissions(0)
|
1061
|
+
role_deny = Permissions(0)
|
1062
|
+
for rid in member.roles:
|
1063
|
+
ow = self.permission_overwrite_for(rid)
|
1064
|
+
if ow is not None:
|
1065
|
+
role_allow |= Permissions(int(ow.allow))
|
1066
|
+
role_deny |= Permissions(int(ow.deny))
|
1067
|
+
|
1068
|
+
base &= ~role_deny
|
1069
|
+
base |= role_allow
|
1070
|
+
|
1071
|
+
# Member overwrite
|
1072
|
+
base = self._apply_overwrite(base, self.permission_overwrite_for(member.id))
|
1073
|
+
|
1074
|
+
return base
|
1075
|
+
|
896
1076
|
|
897
1077
|
class TextChannel(Channel):
|
898
1078
|
"""Represents a guild text channel or announcement channel."""
|
@@ -907,6 +1087,20 @@ class TextChannel(Channel):
|
|
907
1087
|
)
|
908
1088
|
self.last_pin_timestamp: Optional[str] = data.get("last_pin_timestamp")
|
909
1089
|
|
1090
|
+
|
1091
|
+
def history(
|
1092
|
+
self,
|
1093
|
+
*,
|
1094
|
+
limit: Optional[int] = None,
|
1095
|
+
before: Optional[str] = None,
|
1096
|
+
after: Optional[str] = None,
|
1097
|
+
) -> AsyncIterator["Message"]:
|
1098
|
+
"""Return an async iterator over this channel's messages."""
|
1099
|
+
|
1100
|
+
from .utils import message_pager
|
1101
|
+
|
1102
|
+
return message_pager(self, limit=limit, before=before, after=after)
|
1103
|
+
|
910
1104
|
async def send(
|
911
1105
|
self,
|
912
1106
|
content: Optional[str] = None,
|
@@ -928,6 +1122,28 @@ class TextChannel(Channel):
|
|
928
1122
|
components=components,
|
929
1123
|
)
|
930
1124
|
|
1125
|
+
async def purge(
|
1126
|
+
self, limit: int, *, before: "Snowflake | None" = None
|
1127
|
+
) -> List["Snowflake"]:
|
1128
|
+
"""Bulk delete messages from this channel."""
|
1129
|
+
|
1130
|
+
params: Dict[str, Union[int, str]] = {"limit": limit}
|
1131
|
+
if before is not None:
|
1132
|
+
params["before"] = before
|
1133
|
+
|
1134
|
+
messages = await self._client._http.request(
|
1135
|
+
"GET", f"/channels/{self.id}/messages", params=params
|
1136
|
+
)
|
1137
|
+
ids = [m["id"] for m in messages]
|
1138
|
+
if not ids:
|
1139
|
+
return []
|
1140
|
+
|
1141
|
+
await self._client._http.bulk_delete_messages(self.id, ids)
|
1142
|
+
for mid in ids:
|
1143
|
+
self._client._messages.pop(mid, None)
|
1144
|
+
return ids
|
1145
|
+
|
1146
|
+
|
931
1147
|
def __repr__(self) -> str:
|
932
1148
|
return f"<TextChannel id='{self.id}' name='{self.name}' guild_id='{self.guild_id}'>"
|
933
1149
|
|
@@ -1045,6 +1261,36 @@ class DMChannel(Channel):
|
|
1045
1261
|
components=components,
|
1046
1262
|
)
|
1047
1263
|
|
1264
|
+
async def history(
|
1265
|
+
self,
|
1266
|
+
*,
|
1267
|
+
limit: Optional[int] = 100,
|
1268
|
+
before: "Snowflake | None" = None,
|
1269
|
+
):
|
1270
|
+
"""An async iterator over messages in this DM."""
|
1271
|
+
|
1272
|
+
params: Dict[str, Union[int, str]] = {}
|
1273
|
+
if before is not None:
|
1274
|
+
params["before"] = before
|
1275
|
+
|
1276
|
+
fetched = 0
|
1277
|
+
while True:
|
1278
|
+
to_fetch = 100 if limit is None else min(100, limit - fetched)
|
1279
|
+
if to_fetch <= 0:
|
1280
|
+
break
|
1281
|
+
params["limit"] = to_fetch
|
1282
|
+
messages = await self._client._http.request(
|
1283
|
+
"GET", f"/channels/{self.id}/messages", params=params.copy()
|
1284
|
+
)
|
1285
|
+
if not messages:
|
1286
|
+
break
|
1287
|
+
params["before"] = messages[-1]["id"]
|
1288
|
+
for msg in messages:
|
1289
|
+
yield Message(msg, self._client)
|
1290
|
+
fetched += 1
|
1291
|
+
if limit is not None and fetched >= limit:
|
1292
|
+
return
|
1293
|
+
|
1048
1294
|
def __repr__(self) -> str:
|
1049
1295
|
recipient_repr = self.recipient.username if self.recipient else "Unknown"
|
1050
1296
|
return f"<DMChannel id='{self.id}' recipient='{recipient_repr}'>"
|
@@ -1090,6 +1336,106 @@ class PartialChannel:
|
|
1090
1336
|
return f"<PartialChannel id='{self.id}' name='{self.name}' type='{type_name}'>"
|
1091
1337
|
|
1092
1338
|
|
1339
|
+
class Webhook:
|
1340
|
+
"""Represents a Discord Webhook."""
|
1341
|
+
|
1342
|
+
def __init__(
|
1343
|
+
self, data: Dict[str, Any], client_instance: Optional["Client"] = None
|
1344
|
+
):
|
1345
|
+
self._client: Optional["Client"] = client_instance
|
1346
|
+
self.id: str = data["id"]
|
1347
|
+
self.type: int = int(data.get("type", 1))
|
1348
|
+
self.guild_id: Optional[str] = data.get("guild_id")
|
1349
|
+
self.channel_id: Optional[str] = data.get("channel_id")
|
1350
|
+
self.name: Optional[str] = data.get("name")
|
1351
|
+
self.avatar: Optional[str] = data.get("avatar")
|
1352
|
+
self.token: Optional[str] = data.get("token")
|
1353
|
+
self.application_id: Optional[str] = data.get("application_id")
|
1354
|
+
self.url: Optional[str] = data.get("url")
|
1355
|
+
self.user: Optional[User] = User(data["user"]) if data.get("user") else None
|
1356
|
+
|
1357
|
+
def __repr__(self) -> str:
|
1358
|
+
return f"<Webhook id='{self.id}' name='{self.name}'>"
|
1359
|
+
|
1360
|
+
@classmethod
|
1361
|
+
def from_url(
|
1362
|
+
cls, url: str, session: Optional[aiohttp.ClientSession] = None
|
1363
|
+
) -> "Webhook":
|
1364
|
+
"""Create a minimal :class:`Webhook` from a webhook URL.
|
1365
|
+
|
1366
|
+
Parameters
|
1367
|
+
----------
|
1368
|
+
url:
|
1369
|
+
The full Discord webhook URL.
|
1370
|
+
session:
|
1371
|
+
Unused for now. Present for API compatibility.
|
1372
|
+
|
1373
|
+
Returns
|
1374
|
+
-------
|
1375
|
+
Webhook
|
1376
|
+
A webhook instance containing only the ``id``, ``token`` and ``url``.
|
1377
|
+
"""
|
1378
|
+
|
1379
|
+
parts = url.rstrip("/").split("/")
|
1380
|
+
if len(parts) < 2:
|
1381
|
+
raise ValueError("Invalid webhook URL")
|
1382
|
+
token = parts[-1]
|
1383
|
+
webhook_id = parts[-2]
|
1384
|
+
|
1385
|
+
return cls({"id": webhook_id, "token": token, "url": url})
|
1386
|
+
|
1387
|
+
async def send(
|
1388
|
+
self,
|
1389
|
+
content: Optional[str] = None,
|
1390
|
+
*,
|
1391
|
+
username: Optional[str] = None,
|
1392
|
+
avatar_url: Optional[str] = None,
|
1393
|
+
tts: bool = False,
|
1394
|
+
embed: Optional["Embed"] = None,
|
1395
|
+
embeds: Optional[List["Embed"]] = None,
|
1396
|
+
components: Optional[List["ActionRow"]] = None,
|
1397
|
+
allowed_mentions: Optional[Dict[str, Any]] = None,
|
1398
|
+
attachments: Optional[List[Any]] = None,
|
1399
|
+
files: Optional[List[Any]] = None,
|
1400
|
+
flags: Optional[int] = None,
|
1401
|
+
) -> "Message":
|
1402
|
+
"""Send a message using this webhook."""
|
1403
|
+
|
1404
|
+
if not self._client:
|
1405
|
+
raise DisagreementException("Webhook is not bound to a Client")
|
1406
|
+
assert self.token is not None, "Webhook token missing"
|
1407
|
+
|
1408
|
+
if embed and embeds:
|
1409
|
+
raise ValueError("Cannot provide both embed and embeds.")
|
1410
|
+
|
1411
|
+
final_embeds_payload: Optional[List[Dict[str, Any]]] = None
|
1412
|
+
if embed:
|
1413
|
+
final_embeds_payload = [embed.to_dict()]
|
1414
|
+
elif embeds:
|
1415
|
+
final_embeds_payload = [e.to_dict() for e in embeds]
|
1416
|
+
|
1417
|
+
components_payload: Optional[List[Dict[str, Any]]] = None
|
1418
|
+
if components:
|
1419
|
+
components_payload = [c.to_dict() for c in components]
|
1420
|
+
|
1421
|
+
message_data = await self._client._http.execute_webhook(
|
1422
|
+
self.id,
|
1423
|
+
self.token,
|
1424
|
+
content=content,
|
1425
|
+
tts=tts,
|
1426
|
+
embeds=final_embeds_payload,
|
1427
|
+
components=components_payload,
|
1428
|
+
allowed_mentions=allowed_mentions,
|
1429
|
+
attachments=attachments,
|
1430
|
+
files=files,
|
1431
|
+
flags=flags,
|
1432
|
+
username=username,
|
1433
|
+
avatar_url=avatar_url,
|
1434
|
+
)
|
1435
|
+
|
1436
|
+
return self._client.parse_message(message_data)
|
1437
|
+
|
1438
|
+
|
1093
1439
|
# --- Message Components ---
|
1094
1440
|
|
1095
1441
|
|
@@ -1433,7 +1779,7 @@ class MediaGallery(Component):
|
|
1433
1779
|
return payload
|
1434
1780
|
|
1435
1781
|
|
1436
|
-
class
|
1782
|
+
class FileComponent(Component):
|
1437
1783
|
"""Represents a file component."""
|
1438
1784
|
|
1439
1785
|
def __init__(
|
@@ -1480,13 +1826,13 @@ class Container(Component):
|
|
1480
1826
|
def __init__(
|
1481
1827
|
self,
|
1482
1828
|
components: List[Component],
|
1483
|
-
accent_color:
|
1829
|
+
accent_color: Color | int | str | None = None,
|
1484
1830
|
spoiler: bool = False,
|
1485
1831
|
id: Optional[int] = None,
|
1486
1832
|
):
|
1487
1833
|
super().__init__(ComponentType.CONTAINER)
|
1488
1834
|
self.components = components
|
1489
|
-
self.accent_color = accent_color
|
1835
|
+
self.accent_color = Color.parse(accent_color)
|
1490
1836
|
self.spoiler = spoiler
|
1491
1837
|
self.id = id
|
1492
1838
|
|
@@ -1494,7 +1840,7 @@ class Container(Component):
|
|
1494
1840
|
payload = super().to_dict()
|
1495
1841
|
payload["components"] = [c.to_dict() for c in self.components]
|
1496
1842
|
if self.accent_color:
|
1497
|
-
payload["accent_color"] = self.accent_color
|
1843
|
+
payload["accent_color"] = self.accent_color.value
|
1498
1844
|
if self.spoiler:
|
1499
1845
|
payload["spoiler"] = self.spoiler
|
1500
1846
|
if self.id is not None:
|
@@ -1614,6 +1960,85 @@ class TypingStart:
|
|
1614
1960
|
return f"<TypingStart channel_id='{self.channel_id}' user_id='{self.user_id}'>"
|
1615
1961
|
|
1616
1962
|
|
1963
|
+
class Reaction:
|
1964
|
+
"""Represents a message reaction event."""
|
1965
|
+
|
1966
|
+
def __init__(
|
1967
|
+
self, data: Dict[str, Any], client_instance: Optional["Client"] = None
|
1968
|
+
):
|
1969
|
+
self._client = client_instance
|
1970
|
+
self.user_id: str = data["user_id"]
|
1971
|
+
self.channel_id: str = data["channel_id"]
|
1972
|
+
self.message_id: str = data["message_id"]
|
1973
|
+
self.guild_id: Optional[str] = data.get("guild_id")
|
1974
|
+
self.member: Optional[Member] = (
|
1975
|
+
Member(data["member"], client_instance) if data.get("member") else None
|
1976
|
+
)
|
1977
|
+
self.emoji: Dict[str, Any] = data.get("emoji", {})
|
1978
|
+
|
1979
|
+
def __repr__(self) -> str:
|
1980
|
+
emoji_value = self.emoji.get("name") or self.emoji.get("id")
|
1981
|
+
return f"<Reaction message_id='{self.message_id}' user_id='{self.user_id}' emoji='{emoji_value}'>"
|
1982
|
+
|
1983
|
+
|
1984
|
+
class GuildMemberRemove:
|
1985
|
+
"""Represents a GUILD_MEMBER_REMOVE event."""
|
1986
|
+
|
1987
|
+
def __init__(
|
1988
|
+
self, data: Dict[str, Any], client_instance: Optional["Client"] = None
|
1989
|
+
):
|
1990
|
+
self._client = client_instance
|
1991
|
+
self.guild_id: str = data["guild_id"]
|
1992
|
+
self.user: User = User(data["user"])
|
1993
|
+
|
1994
|
+
def __repr__(self) -> str:
|
1995
|
+
return (
|
1996
|
+
f"<GuildMemberRemove guild_id='{self.guild_id}' user_id='{self.user.id}'>"
|
1997
|
+
)
|
1998
|
+
|
1999
|
+
|
2000
|
+
class GuildBanAdd:
|
2001
|
+
"""Represents a GUILD_BAN_ADD event."""
|
2002
|
+
|
2003
|
+
def __init__(
|
2004
|
+
self, data: Dict[str, Any], client_instance: Optional["Client"] = None
|
2005
|
+
):
|
2006
|
+
self._client = client_instance
|
2007
|
+
self.guild_id: str = data["guild_id"]
|
2008
|
+
self.user: User = User(data["user"])
|
2009
|
+
|
2010
|
+
def __repr__(self) -> str:
|
2011
|
+
return f"<GuildBanAdd guild_id='{self.guild_id}' user_id='{self.user.id}'>"
|
2012
|
+
|
2013
|
+
|
2014
|
+
class GuildBanRemove:
|
2015
|
+
"""Represents a GUILD_BAN_REMOVE event."""
|
2016
|
+
|
2017
|
+
def __init__(
|
2018
|
+
self, data: Dict[str, Any], client_instance: Optional["Client"] = None
|
2019
|
+
):
|
2020
|
+
self._client = client_instance
|
2021
|
+
self.guild_id: str = data["guild_id"]
|
2022
|
+
self.user: User = User(data["user"])
|
2023
|
+
|
2024
|
+
def __repr__(self) -> str:
|
2025
|
+
return f"<GuildBanRemove guild_id='{self.guild_id}' user_id='{self.user.id}'>"
|
2026
|
+
|
2027
|
+
|
2028
|
+
class GuildRoleUpdate:
|
2029
|
+
"""Represents a GUILD_ROLE_UPDATE event."""
|
2030
|
+
|
2031
|
+
def __init__(
|
2032
|
+
self, data: Dict[str, Any], client_instance: Optional["Client"] = None
|
2033
|
+
):
|
2034
|
+
self._client = client_instance
|
2035
|
+
self.guild_id: str = data["guild_id"]
|
2036
|
+
self.role: Role = Role(data["role"])
|
2037
|
+
|
2038
|
+
def __repr__(self) -> str:
|
2039
|
+
return f"<GuildRoleUpdate guild_id='{self.guild_id}' role_id='{self.role.id}'>"
|
2040
|
+
|
2041
|
+
|
1617
2042
|
def channel_factory(data: Dict[str, Any], client: "Client") -> Channel:
|
1618
2043
|
"""Create a channel object from raw API data."""
|
1619
2044
|
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
|
|