disagreement 0.3.0b1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- disagreement/__init__.py +2 -4
- disagreement/audio.py +25 -5
- disagreement/cache.py +12 -3
- disagreement/caching.py +15 -14
- disagreement/client.py +86 -52
- disagreement/enums.py +10 -3
- disagreement/error_handler.py +5 -1
- disagreement/errors.py +1341 -3
- disagreement/event_dispatcher.py +1 -3
- disagreement/ext/__init__.py +1 -0
- disagreement/ext/app_commands/__init__.py +0 -2
- disagreement/ext/app_commands/commands.py +0 -2
- disagreement/ext/app_commands/context.py +0 -2
- disagreement/ext/app_commands/converters.py +2 -4
- disagreement/ext/app_commands/decorators.py +5 -7
- disagreement/ext/app_commands/handler.py +1 -3
- disagreement/ext/app_commands/hybrid.py +0 -2
- disagreement/ext/commands/__init__.py +0 -2
- disagreement/ext/commands/cog.py +0 -2
- disagreement/ext/commands/converters.py +16 -5
- disagreement/ext/commands/core.py +52 -14
- disagreement/ext/commands/decorators.py +3 -7
- disagreement/ext/commands/errors.py +0 -2
- disagreement/ext/commands/help.py +0 -2
- disagreement/ext/commands/view.py +1 -3
- disagreement/gateway.py +27 -25
- disagreement/http.py +264 -22
- disagreement/interactions.py +0 -2
- disagreement/models.py +199 -105
- disagreement/shard_manager.py +0 -2
- disagreement/ui/view.py +2 -2
- disagreement/voice_client.py +20 -1
- {disagreement-0.3.0b1.dist-info → disagreement-0.4.0.dist-info}/METADATA +32 -6
- disagreement-0.4.0.dist-info/RECORD +55 -0
- disagreement-0.3.0b1.dist-info/RECORD +0 -55
- {disagreement-0.3.0b1.dist-info → disagreement-0.4.0.dist-info}/WHEEL +0 -0
- {disagreement-0.3.0b1.dist-info → disagreement-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {disagreement-0.3.0b1.dist-info → disagreement-0.4.0.dist-info}/top_level.txt +0 -0
disagreement/models.py
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
# disagreement/models.py
|
2
|
-
|
3
1
|
"""
|
4
2
|
Data models for Discord objects.
|
5
3
|
"""
|
6
4
|
|
7
5
|
import asyncio
|
8
6
|
import json
|
7
|
+
import re
|
9
8
|
from dataclasses import dataclass
|
10
9
|
from typing import Any, AsyncIterator, Dict, List, Optional, TYPE_CHECKING, Union, cast
|
11
10
|
|
@@ -24,6 +23,7 @@ from .enums import ( # These enums will need to be defined in disagreement/enum
|
|
24
23
|
PremiumTier,
|
25
24
|
GuildFeature,
|
26
25
|
ChannelType,
|
26
|
+
AutoArchiveDuration,
|
27
27
|
ComponentType,
|
28
28
|
ButtonStyle, # Added for Button
|
29
29
|
GuildScheduledEventPrivacyLevel,
|
@@ -39,6 +39,7 @@ if TYPE_CHECKING:
|
|
39
39
|
from .enums import OverwriteType # For PermissionOverwrite model
|
40
40
|
from .ui.view import View
|
41
41
|
from .interactions import Snowflake
|
42
|
+
from .typing import Typing
|
42
43
|
|
43
44
|
# Forward reference Message if it were used in type hints before its definition
|
44
45
|
# from .models import Message # Not needed as Message is defined before its use in TextChannel.send etc.
|
@@ -114,31 +115,39 @@ class Message:
|
|
114
115
|
# self.mention_roles: List[str] = data.get("mention_roles", [])
|
115
116
|
# self.mention_everyone: bool = data.get("mention_everyone", False)
|
116
117
|
|
118
|
+
@property
|
119
|
+
def clean_content(self) -> str:
|
120
|
+
"""Returns message content without user, role, or channel mentions."""
|
121
|
+
|
122
|
+
pattern = re.compile(r"<@!?\d+>|<#\d+>|<@&\d+>")
|
123
|
+
cleaned = pattern.sub("", self.content)
|
124
|
+
return " ".join(cleaned.split())
|
125
|
+
|
117
126
|
async def pin(self) -> None:
|
118
|
-
|
127
|
+
"""|coro|
|
119
128
|
|
120
|
-
|
129
|
+
Pins this message to its channel.
|
121
130
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
131
|
+
Raises
|
132
|
+
------
|
133
|
+
HTTPException
|
134
|
+
Pinning the message failed.
|
135
|
+
"""
|
136
|
+
await self._client._http.pin_message(self.channel_id, self.id)
|
137
|
+
self.pinned = True
|
129
138
|
|
130
139
|
async def unpin(self) -> None:
|
131
|
-
|
140
|
+
"""|coro|
|
132
141
|
|
133
|
-
|
142
|
+
Unpins this message from its channel.
|
134
143
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
144
|
+
Raises
|
145
|
+
------
|
146
|
+
HTTPException
|
147
|
+
Unpinning the message failed.
|
148
|
+
"""
|
149
|
+
await self._client._http.unpin_message(self.channel_id, self.id)
|
150
|
+
self.pinned = False
|
142
151
|
|
143
152
|
async def reply(
|
144
153
|
self,
|
@@ -241,16 +250,16 @@ class Message:
|
|
241
250
|
await self._client.add_reaction(self.channel_id, self.id, emoji)
|
242
251
|
|
243
252
|
async def remove_reaction(self, emoji: str, member: Optional[User] = None) -> None:
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
253
|
+
"""|coro|
|
254
|
+
Removes a reaction from this message.
|
255
|
+
If no ``member`` is provided, removes the bot's own reaction.
|
256
|
+
"""
|
257
|
+
if member:
|
258
|
+
await self._client._http.delete_user_reaction(
|
259
|
+
self.channel_id, self.id, emoji, member.id
|
260
|
+
)
|
261
|
+
else:
|
262
|
+
await self._client.remove_reaction(self.channel_id, self.id, emoji)
|
254
263
|
|
255
264
|
async def clear_reactions(self) -> None:
|
256
265
|
"""|coro| Remove all reactions from this message."""
|
@@ -280,7 +289,7 @@ class Message:
|
|
280
289
|
self,
|
281
290
|
name: str,
|
282
291
|
*,
|
283
|
-
auto_archive_duration: Optional[
|
292
|
+
auto_archive_duration: Optional[AutoArchiveDuration] = None,
|
284
293
|
rate_limit_per_user: Optional[int] = None,
|
285
294
|
reason: Optional[str] = None,
|
286
295
|
) -> "Thread":
|
@@ -292,9 +301,9 @@ class Message:
|
|
292
301
|
----------
|
293
302
|
name: str
|
294
303
|
The name of the thread.
|
295
|
-
auto_archive_duration: Optional[
|
296
|
-
|
297
|
-
|
304
|
+
auto_archive_duration: Optional[AutoArchiveDuration]
|
305
|
+
How long before the thread is automatically archived after recent activity.
|
306
|
+
See :class:`AutoArchiveDuration` for allowed values.
|
298
307
|
rate_limit_per_user: Optional[int]
|
299
308
|
The number of seconds a user has to wait before sending another message.
|
300
309
|
reason: Optional[str]
|
@@ -307,7 +316,7 @@ class Message:
|
|
307
316
|
"""
|
308
317
|
payload: Dict[str, Any] = {"name": name}
|
309
318
|
if auto_archive_duration is not None:
|
310
|
-
payload["auto_archive_duration"] = auto_archive_duration
|
319
|
+
payload["auto_archive_duration"] = int(auto_archive_duration)
|
311
320
|
if rate_limit_per_user is not None:
|
312
321
|
payload["rate_limit_per_user"] = rate_limit_per_user
|
313
322
|
|
@@ -530,8 +539,42 @@ class Embed:
|
|
530
539
|
payload["fields"] = [f.to_dict() for f in self.fields]
|
531
540
|
return payload
|
532
541
|
|
533
|
-
# Convenience methods
|
534
|
-
|
542
|
+
# Convenience methods mirroring ``discord.py``'s ``Embed`` API
|
543
|
+
|
544
|
+
def set_author(
|
545
|
+
self, *, name: str, url: Optional[str] = None, icon_url: Optional[str] = None
|
546
|
+
) -> "Embed":
|
547
|
+
"""Set the embed author and return ``self`` for chaining."""
|
548
|
+
|
549
|
+
data: Dict[str, Any] = {"name": name}
|
550
|
+
if url:
|
551
|
+
data["url"] = url
|
552
|
+
if icon_url:
|
553
|
+
data["icon_url"] = icon_url
|
554
|
+
self.author = EmbedAuthor(data)
|
555
|
+
return self
|
556
|
+
|
557
|
+
def add_field(self, *, name: str, value: str, inline: bool = False) -> "Embed":
|
558
|
+
"""Add a field to the embed."""
|
559
|
+
|
560
|
+
field = EmbedField({"name": name, "value": value, "inline": inline})
|
561
|
+
self.fields.append(field)
|
562
|
+
return self
|
563
|
+
|
564
|
+
def set_footer(self, *, text: str, icon_url: Optional[str] = None) -> "Embed":
|
565
|
+
"""Set the embed footer."""
|
566
|
+
|
567
|
+
data: Dict[str, Any] = {"text": text}
|
568
|
+
if icon_url:
|
569
|
+
data["icon_url"] = icon_url
|
570
|
+
self.footer = EmbedFooter(data)
|
571
|
+
return self
|
572
|
+
|
573
|
+
def set_image(self, url: str) -> "Embed":
|
574
|
+
"""Set the embed image."""
|
575
|
+
|
576
|
+
self.image = EmbedImage({"url": url})
|
577
|
+
return self
|
535
578
|
|
536
579
|
|
537
580
|
class Attachment:
|
@@ -669,7 +712,7 @@ class Member(User): # Member inherits from User
|
|
669
712
|
# We'd need to construct a partial user from top-level member fields if 'user' is missing.
|
670
713
|
# For now, assume 'user' object is present for full Member hydration.
|
671
714
|
# If 'user' is missing, the User part might be incomplete.
|
672
|
-
pass
|
715
|
+
pass
|
673
716
|
|
674
717
|
super().__init__(
|
675
718
|
user_data if user_data else data
|
@@ -1088,7 +1131,9 @@ class Guild:
|
|
1088
1131
|
|
1089
1132
|
# Internal caches, populated by events or specific fetches
|
1090
1133
|
self._channels: ChannelCache = ChannelCache()
|
1091
|
-
self._members: MemberCache = MemberCache(
|
1134
|
+
self._members: MemberCache = MemberCache(
|
1135
|
+
getattr(client_instance, "member_cache_flags", MemberCacheFlags())
|
1136
|
+
)
|
1092
1137
|
self._threads: Dict[str, "Thread"] = {}
|
1093
1138
|
|
1094
1139
|
def get_channel(self, channel_id: str) -> Optional["Channel"]:
|
@@ -1128,6 +1173,16 @@ class Guild:
|
|
1128
1173
|
def __repr__(self) -> str:
|
1129
1174
|
return f"<Guild id='{self.id}' name='{self.name}'>"
|
1130
1175
|
|
1176
|
+
async def fetch_widget(self) -> Dict[str, Any]:
|
1177
|
+
"""|coro| Fetch this guild's widget settings."""
|
1178
|
+
|
1179
|
+
return await self._client.fetch_widget(self.id)
|
1180
|
+
|
1181
|
+
async def edit_widget(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
1182
|
+
"""|coro| Edit this guild's widget settings."""
|
1183
|
+
|
1184
|
+
return await self._client.edit_widget(self.id, payload)
|
1185
|
+
|
1131
1186
|
async def fetch_members(self, *, limit: Optional[int] = None) -> List["Member"]:
|
1132
1187
|
"""|coro|
|
1133
1188
|
|
@@ -1278,7 +1333,45 @@ class Channel:
|
|
1278
1333
|
return base
|
1279
1334
|
|
1280
1335
|
|
1281
|
-
class
|
1336
|
+
class Messageable:
|
1337
|
+
"""Mixin for channels that can send messages and show typing."""
|
1338
|
+
|
1339
|
+
_client: "Client"
|
1340
|
+
id: str
|
1341
|
+
|
1342
|
+
async def send(
|
1343
|
+
self,
|
1344
|
+
content: Optional[str] = None,
|
1345
|
+
*,
|
1346
|
+
embed: Optional["Embed"] = None,
|
1347
|
+
embeds: Optional[List["Embed"]] = None,
|
1348
|
+
components: Optional[List["ActionRow"]] = None,
|
1349
|
+
) -> "Message":
|
1350
|
+
if not hasattr(self._client, "send_message"):
|
1351
|
+
raise NotImplementedError(
|
1352
|
+
"Client.send_message is required for Messageable.send"
|
1353
|
+
)
|
1354
|
+
|
1355
|
+
return await self._client.send_message(
|
1356
|
+
channel_id=self.id,
|
1357
|
+
content=content,
|
1358
|
+
embed=embed,
|
1359
|
+
embeds=embeds,
|
1360
|
+
components=components,
|
1361
|
+
)
|
1362
|
+
|
1363
|
+
async def trigger_typing(self) -> None:
|
1364
|
+
await self._client._http.trigger_typing(self.id)
|
1365
|
+
|
1366
|
+
def typing(self) -> "Typing":
|
1367
|
+
if not hasattr(self._client, "typing"):
|
1368
|
+
raise NotImplementedError(
|
1369
|
+
"Client.typing is required for Messageable.typing"
|
1370
|
+
)
|
1371
|
+
return self._client.typing(self.id)
|
1372
|
+
|
1373
|
+
|
1374
|
+
class TextChannel(Channel, Messageable):
|
1282
1375
|
"""Represents a guild text channel or announcement channel."""
|
1283
1376
|
|
1284
1377
|
def __init__(self, data: Dict[str, Any], client_instance: "Client"):
|
@@ -1304,27 +1397,6 @@ class TextChannel(Channel):
|
|
1304
1397
|
|
1305
1398
|
return message_pager(self, limit=limit, before=before, after=after)
|
1306
1399
|
|
1307
|
-
async def send(
|
1308
|
-
self,
|
1309
|
-
content: Optional[str] = None,
|
1310
|
-
*,
|
1311
|
-
embed: Optional[Embed] = None,
|
1312
|
-
embeds: Optional[List[Embed]] = None,
|
1313
|
-
components: Optional[List["ActionRow"]] = None, # Added components
|
1314
|
-
) -> "Message": # Forward reference Message
|
1315
|
-
if not hasattr(self._client, "send_message"):
|
1316
|
-
raise NotImplementedError(
|
1317
|
-
"Client.send_message is required for TextChannel.send"
|
1318
|
-
)
|
1319
|
-
|
1320
|
-
return await self._client.send_message(
|
1321
|
-
channel_id=self.id,
|
1322
|
-
content=content,
|
1323
|
-
embed=embed,
|
1324
|
-
embeds=embeds,
|
1325
|
-
components=components,
|
1326
|
-
)
|
1327
|
-
|
1328
1400
|
async def purge(
|
1329
1401
|
self, limit: int, *, before: "Snowflake | None" = None
|
1330
1402
|
) -> List["Snowflake"]:
|
@@ -1347,41 +1419,41 @@ class TextChannel(Channel):
|
|
1347
1419
|
return ids
|
1348
1420
|
|
1349
1421
|
def get_partial_message(self, id: int) -> "PartialMessage":
|
1350
|
-
|
1422
|
+
"""Returns a :class:`PartialMessage` for the given ID.
|
1351
1423
|
|
1352
|
-
|
1424
|
+
This allows performing actions on a message without fetching it first.
|
1353
1425
|
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1426
|
+
Parameters
|
1427
|
+
----------
|
1428
|
+
id: int
|
1429
|
+
The ID of the message to get a partial instance of.
|
1358
1430
|
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1431
|
+
Returns
|
1432
|
+
-------
|
1433
|
+
PartialMessage
|
1434
|
+
The partial message instance.
|
1435
|
+
"""
|
1436
|
+
return PartialMessage(id=str(id), channel=self)
|
1365
1437
|
|
1366
1438
|
def __repr__(self) -> str:
|
1367
1439
|
return f"<TextChannel id='{self.id}' name='{self.name}' guild_id='{self.guild_id}'>"
|
1368
1440
|
|
1369
1441
|
async def pins(self) -> List["Message"]:
|
1370
1442
|
"""|coro|
|
1371
|
-
|
1443
|
+
|
1372
1444
|
Fetches all pinned messages in this channel.
|
1373
|
-
|
1445
|
+
|
1374
1446
|
Returns
|
1375
1447
|
-------
|
1376
1448
|
List[Message]
|
1377
1449
|
The pinned messages.
|
1378
|
-
|
1450
|
+
|
1379
1451
|
Raises
|
1380
1452
|
------
|
1381
1453
|
HTTPException
|
1382
1454
|
Fetching the pinned messages failed.
|
1383
1455
|
"""
|
1384
|
-
|
1456
|
+
|
1385
1457
|
messages_data = await self._client._http.get_pinned_messages(self.id)
|
1386
1458
|
return [self._client.parse_message(m) for m in messages_data]
|
1387
1459
|
|
@@ -1390,7 +1462,7 @@ class TextChannel(Channel):
|
|
1390
1462
|
name: str,
|
1391
1463
|
*,
|
1392
1464
|
type: ChannelType = ChannelType.PUBLIC_THREAD,
|
1393
|
-
auto_archive_duration: Optional[
|
1465
|
+
auto_archive_duration: Optional[AutoArchiveDuration] = None,
|
1394
1466
|
invitable: Optional[bool] = None,
|
1395
1467
|
rate_limit_per_user: Optional[int] = None,
|
1396
1468
|
reason: Optional[str] = None,
|
@@ -1406,8 +1478,8 @@ class TextChannel(Channel):
|
|
1406
1478
|
type: ChannelType
|
1407
1479
|
The type of thread to create. Defaults to PUBLIC_THREAD.
|
1408
1480
|
Can be PUBLIC_THREAD, PRIVATE_THREAD, or ANNOUNCEMENT_THREAD.
|
1409
|
-
auto_archive_duration: Optional[
|
1410
|
-
|
1481
|
+
auto_archive_duration: Optional[AutoArchiveDuration]
|
1482
|
+
How long before the thread is automatically archived after recent activity.
|
1411
1483
|
invitable: Optional[bool]
|
1412
1484
|
Whether non-moderators can invite other non-moderators to a private thread.
|
1413
1485
|
Only applicable to private threads.
|
@@ -1426,7 +1498,7 @@ class TextChannel(Channel):
|
|
1426
1498
|
"type": type.value,
|
1427
1499
|
}
|
1428
1500
|
if auto_archive_duration is not None:
|
1429
|
-
payload["auto_archive_duration"] = auto_archive_duration
|
1501
|
+
payload["auto_archive_duration"] = int(auto_archive_duration)
|
1430
1502
|
if invitable is not None and type == ChannelType.PRIVATE_THREAD:
|
1431
1503
|
payload["invitable"] = invitable
|
1432
1504
|
if rate_limit_per_user is not None:
|
@@ -1606,7 +1678,9 @@ class Thread(TextChannel): # Threads are a specialized TextChannel
|
|
1606
1678
|
"""
|
1607
1679
|
await self._client._http.leave_thread(self.id)
|
1608
1680
|
|
1609
|
-
async def archive(
|
1681
|
+
async def archive(
|
1682
|
+
self, locked: bool = False, *, reason: Optional[str] = None
|
1683
|
+
) -> "Thread":
|
1610
1684
|
"""|coro|
|
1611
1685
|
|
1612
1686
|
Archives this thread.
|
@@ -1631,7 +1705,7 @@ class Thread(TextChannel): # Threads are a specialized TextChannel
|
|
1631
1705
|
return cast("Thread", self._client.parse_channel(data))
|
1632
1706
|
|
1633
1707
|
|
1634
|
-
class DMChannel(Channel):
|
1708
|
+
class DMChannel(Channel, Messageable):
|
1635
1709
|
"""Represents a Direct Message channel."""
|
1636
1710
|
|
1637
1711
|
def __init__(self, data: Dict[str, Any], client_instance: "Client"):
|
@@ -1645,27 +1719,6 @@ class DMChannel(Channel):
|
|
1645
1719
|
def recipient(self) -> Optional[User]:
|
1646
1720
|
return self.recipients[0] if self.recipients else None
|
1647
1721
|
|
1648
|
-
async def send(
|
1649
|
-
self,
|
1650
|
-
content: Optional[str] = None,
|
1651
|
-
*,
|
1652
|
-
embed: Optional[Embed] = None,
|
1653
|
-
embeds: Optional[List[Embed]] = None,
|
1654
|
-
components: Optional[List["ActionRow"]] = None, # Added components
|
1655
|
-
) -> "Message":
|
1656
|
-
if not hasattr(self._client, "send_message"):
|
1657
|
-
raise NotImplementedError(
|
1658
|
-
"Client.send_message is required for DMChannel.send"
|
1659
|
-
)
|
1660
|
-
|
1661
|
-
return await self._client.send_message(
|
1662
|
-
channel_id=self.id,
|
1663
|
-
content=content,
|
1664
|
-
embed=embed,
|
1665
|
-
embeds=embeds,
|
1666
|
-
components=components,
|
1667
|
-
)
|
1668
|
-
|
1669
1722
|
async def history(
|
1670
1723
|
self,
|
1671
1724
|
*,
|
@@ -2356,6 +2409,37 @@ class ThreadMember:
|
|
2356
2409
|
return f"<ThreadMember user_id='{self.user_id}' thread_id='{self.id}'>"
|
2357
2410
|
|
2358
2411
|
|
2412
|
+
class Activity:
|
2413
|
+
"""Represents a user's presence activity."""
|
2414
|
+
|
2415
|
+
def __init__(self, name: str, type: int) -> None:
|
2416
|
+
self.name = name
|
2417
|
+
self.type = type
|
2418
|
+
|
2419
|
+
def to_dict(self) -> Dict[str, Any]:
|
2420
|
+
return {"name": self.name, "type": self.type}
|
2421
|
+
|
2422
|
+
|
2423
|
+
class Game(Activity):
|
2424
|
+
"""Represents a playing activity."""
|
2425
|
+
|
2426
|
+
def __init__(self, name: str) -> None:
|
2427
|
+
super().__init__(name, 0)
|
2428
|
+
|
2429
|
+
|
2430
|
+
class Streaming(Activity):
|
2431
|
+
"""Represents a streaming activity."""
|
2432
|
+
|
2433
|
+
def __init__(self, name: str, url: str) -> None:
|
2434
|
+
super().__init__(name, 1)
|
2435
|
+
self.url = url
|
2436
|
+
|
2437
|
+
def to_dict(self) -> Dict[str, Any]:
|
2438
|
+
payload = super().to_dict()
|
2439
|
+
payload["url"] = self.url
|
2440
|
+
return payload
|
2441
|
+
|
2442
|
+
|
2359
2443
|
class PresenceUpdate:
|
2360
2444
|
"""Represents a PRESENCE_UPDATE event."""
|
2361
2445
|
|
@@ -2366,7 +2450,17 @@ class PresenceUpdate:
|
|
2366
2450
|
self.user = User(data["user"])
|
2367
2451
|
self.guild_id: Optional[str] = data.get("guild_id")
|
2368
2452
|
self.status: Optional[str] = data.get("status")
|
2369
|
-
self.activities: List[
|
2453
|
+
self.activities: List[Activity] = []
|
2454
|
+
for activity in data.get("activities", []):
|
2455
|
+
act_type = activity.get("type", 0)
|
2456
|
+
name = activity.get("name", "")
|
2457
|
+
if act_type == 0:
|
2458
|
+
obj = Game(name)
|
2459
|
+
elif act_type == 1:
|
2460
|
+
obj = Streaming(name, activity.get("url", ""))
|
2461
|
+
else:
|
2462
|
+
obj = Activity(name, act_type)
|
2463
|
+
self.activities.append(obj)
|
2370
2464
|
self.client_status: Dict[str, Any] = data.get("client_status", {})
|
2371
2465
|
|
2372
2466
|
def __repr__(self) -> str:
|
disagreement/shard_manager.py
CHANGED
disagreement/ui/view.py
CHANGED
@@ -72,7 +72,7 @@ class View:
|
|
72
72
|
rows: List[ActionRow] = []
|
73
73
|
|
74
74
|
for item in self.children:
|
75
|
-
|
75
|
+
rows.append(ActionRow(components=[item]))
|
76
76
|
|
77
77
|
return rows
|
78
78
|
|
@@ -147,7 +147,7 @@ class View:
|
|
147
147
|
|
148
148
|
async def on_timeout(self):
|
149
149
|
"""Called when the view times out."""
|
150
|
-
pass
|
150
|
+
pass
|
151
151
|
|
152
152
|
async def _start(self, client: Client):
|
153
153
|
"""Starts the view's internal listener."""
|
disagreement/voice_client.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# disagreement/voice_client.py
|
2
1
|
"""Voice gateway and UDP audio client."""
|
3
2
|
|
4
3
|
from __future__ import annotations
|
@@ -7,9 +6,26 @@ import asyncio
|
|
7
6
|
import contextlib
|
8
7
|
import socket
|
9
8
|
import threading
|
9
|
+
from array import array
|
10
|
+
|
11
|
+
|
12
|
+
def _apply_volume(data: bytes, volume: float) -> bytes:
|
13
|
+
samples = array("h")
|
14
|
+
samples.frombytes(data)
|
15
|
+
for i, sample in enumerate(samples):
|
16
|
+
scaled = int(sample * volume)
|
17
|
+
if scaled > 32767:
|
18
|
+
scaled = 32767
|
19
|
+
elif scaled < -32768:
|
20
|
+
scaled = -32768
|
21
|
+
samples[i] = scaled
|
22
|
+
return samples.tobytes()
|
23
|
+
|
24
|
+
|
10
25
|
from typing import TYPE_CHECKING, Optional, Sequence
|
11
26
|
|
12
27
|
import aiohttp
|
28
|
+
|
13
29
|
# The following import is correct, but may be flagged by Pylance if the virtual
|
14
30
|
# environment is not configured correctly.
|
15
31
|
from nacl.secret import SecretBox
|
@@ -180,6 +196,9 @@ class VoiceClient:
|
|
180
196
|
data = await self._current_source.read()
|
181
197
|
if not data:
|
182
198
|
break
|
199
|
+
volume = getattr(self._current_source, "volume", 1.0)
|
200
|
+
if volume != 1.0:
|
201
|
+
data = _apply_volume(data, volume)
|
183
202
|
await self.send_audio_frame(data)
|
184
203
|
finally:
|
185
204
|
await self._current_source.close()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: disagreement
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: A Python library for the Discord API.
|
5
5
|
Author-email: Slipstream <me@slipstreamm.dev>
|
6
6
|
License: BSD 3-Clause
|
@@ -38,6 +38,9 @@ A Python library for interacting with the Discord API, with a focus on bot devel
|
|
38
38
|
|
39
39
|
## Features
|
40
40
|
|
41
|
+
- Internationalization helpers
|
42
|
+
- Hybrid context for commands
|
43
|
+
- Built-in rate limiting
|
41
44
|
- Asynchronous design using `aiohttp`
|
42
45
|
- Gateway and HTTP API clients
|
43
46
|
- Slash command framework
|
@@ -57,6 +60,13 @@ pip install -e .
|
|
57
60
|
|
58
61
|
Requires Python 3.10 or newer.
|
59
62
|
|
63
|
+
To run the example scripts, you'll need the `python-dotenv` package to load
|
64
|
+
environment variables. Install the development extras with:
|
65
|
+
|
66
|
+
```bash
|
67
|
+
pip install "disagreement[dev]"
|
68
|
+
```
|
69
|
+
|
60
70
|
## Basic Usage
|
61
71
|
|
62
72
|
```python
|
@@ -65,6 +75,8 @@ import os
|
|
65
75
|
|
66
76
|
import disagreement
|
67
77
|
from disagreement.ext import commands
|
78
|
+
from dotenv import load_dotenv
|
79
|
+
load_dotenv()
|
68
80
|
|
69
81
|
|
70
82
|
class Basics(commands.Cog):
|
@@ -73,18 +85,17 @@ class Basics(commands.Cog):
|
|
73
85
|
|
74
86
|
@commands.command()
|
75
87
|
async def ping(self, ctx: commands.CommandContext) -> None:
|
76
|
-
await ctx.reply("Pong!")
|
88
|
+
await ctx.reply(f"Pong! Gateway Latency: {self.client.latency_ms} ms.")
|
77
89
|
|
78
90
|
|
79
91
|
token = os.getenv("DISCORD_BOT_TOKEN")
|
80
92
|
if not token:
|
81
93
|
raise RuntimeError("DISCORD_BOT_TOKEN environment variable not set")
|
82
94
|
|
83
|
-
|
84
|
-
client.
|
85
|
-
|
86
|
-
|
95
|
+
intents = disagreement.GatewayIntent.default() | disagreement.GatewayIntent.MESSAGE_CONTENT
|
96
|
+
client = disagreement.Client(token=token, command_prefix="!", intents=intents, mention_replies=True)
|
87
97
|
async def main() -> None:
|
98
|
+
client.add_cog(Basics(client))
|
88
99
|
await client.run()
|
89
100
|
|
90
101
|
|
@@ -136,6 +147,20 @@ These options are forwarded to ``HTTPClient`` when it creates the underlying
|
|
136
147
|
``aiohttp.ClientSession``. You can specify a custom ``connector`` or any other
|
137
148
|
session parameter supported by ``aiohttp``.
|
138
149
|
|
150
|
+
### Default Allowed Mentions
|
151
|
+
|
152
|
+
Specify default mention behaviour for all outgoing messages when constructing the client:
|
153
|
+
|
154
|
+
```python
|
155
|
+
client = disagreement.Client(
|
156
|
+
token=token,
|
157
|
+
allowed_mentions={"parse": [], "replied_user": False},
|
158
|
+
)
|
159
|
+
```
|
160
|
+
|
161
|
+
This dictionary is used whenever ``send_message`` is called without an explicit
|
162
|
+
``allowed_mentions`` argument.
|
163
|
+
|
139
164
|
### Defining Subcommands with `AppCommandGroup`
|
140
165
|
|
141
166
|
```python
|
@@ -154,6 +179,7 @@ async def show(ctx: AppCommandContext, key: str):
|
|
154
179
|
@slash_command(name="set", description="Update a setting.", parent=admin_group)
|
155
180
|
async def set_setting(ctx: AppCommandContext, key: str, value: str):
|
156
181
|
...
|
182
|
+
```
|
157
183
|
## Fetching Guilds
|
158
184
|
|
159
185
|
Use `Client.fetch_guild` to retrieve a guild from the Discord API if it
|