disagreement 0.1.0rc3__py3-none-any.whl → 0.3.0b1__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 -2
- disagreement/audio.py +17 -0
- disagreement/cache.py +31 -1
- disagreement/caching.py +120 -0
- disagreement/client.py +1658 -1349
- disagreement/color.py +100 -1
- disagreement/enums.py +63 -0
- disagreement/event_dispatcher.py +2 -2
- disagreement/ext/app_commands/commands.py +0 -2
- disagreement/ext/app_commands/handler.py +101 -30
- disagreement/ext/commands/__init__.py +65 -57
- disagreement/ext/commands/core.py +690 -513
- disagreement/ext/commands/decorators.py +298 -192
- disagreement/ext/commands/errors.py +8 -0
- disagreement/gateway.py +630 -575
- disagreement/http.py +1120 -875
- disagreement/models.py +2588 -2064
- disagreement/ui/view.py +167 -165
- disagreement/voice_client.py +244 -162
- {disagreement-0.1.0rc3.dist-info → disagreement-0.3.0b1.dist-info}/METADATA +18 -1
- {disagreement-0.1.0rc3.dist-info → disagreement-0.3.0b1.dist-info}/RECORD +24 -23
- {disagreement-0.1.0rc3.dist-info → disagreement-0.3.0b1.dist-info}/WHEEL +0 -0
- {disagreement-0.1.0rc3.dist-info → disagreement-0.3.0b1.dist-info}/licenses/LICENSE +0 -0
- {disagreement-0.1.0rc3.dist-info → disagreement-0.3.0b1.dist-info}/top_level.txt +0 -0
disagreement/color.py
CHANGED
@@ -46,11 +46,110 @@ class Color:
|
|
46
46
|
def blue(cls) -> "Color":
|
47
47
|
return cls(0x0000FF)
|
48
48
|
|
49
|
+
# Discord brand colors
|
50
|
+
@classmethod
|
51
|
+
def blurple(cls) -> "Color":
|
52
|
+
"""Discord brand blurple (#5865F2)."""
|
53
|
+
return cls(0x5865F2)
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def light_blurple(cls) -> "Color":
|
57
|
+
"""Light blurple used by Discord (#E0E3FF)."""
|
58
|
+
return cls(0xE0E3FF)
|
59
|
+
|
60
|
+
@classmethod
|
61
|
+
def legacy_blurple(cls) -> "Color":
|
62
|
+
"""Legacy Discord blurple (#7289DA)."""
|
63
|
+
return cls(0x7289DA)
|
64
|
+
|
65
|
+
# Additional assorted colors
|
66
|
+
@classmethod
|
67
|
+
def teal(cls) -> "Color":
|
68
|
+
return cls(0x1ABC9C)
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
def dark_teal(cls) -> "Color":
|
72
|
+
return cls(0x11806A)
|
73
|
+
|
74
|
+
@classmethod
|
75
|
+
def brand_green(cls) -> "Color":
|
76
|
+
return cls(0x57F287)
|
77
|
+
|
78
|
+
@classmethod
|
79
|
+
def dark_green(cls) -> "Color":
|
80
|
+
return cls(0x206694)
|
81
|
+
|
82
|
+
@classmethod
|
83
|
+
def orange(cls) -> "Color":
|
84
|
+
return cls(0xE67E22)
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def dark_orange(cls) -> "Color":
|
88
|
+
return cls(0xA84300)
|
89
|
+
|
90
|
+
@classmethod
|
91
|
+
def brand_red(cls) -> "Color":
|
92
|
+
return cls(0xED4245)
|
93
|
+
|
94
|
+
@classmethod
|
95
|
+
def dark_red(cls) -> "Color":
|
96
|
+
return cls(0x992D22)
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def magenta(cls) -> "Color":
|
100
|
+
return cls(0xE91E63)
|
101
|
+
|
102
|
+
@classmethod
|
103
|
+
def dark_magenta(cls) -> "Color":
|
104
|
+
return cls(0xAD1457)
|
105
|
+
|
106
|
+
@classmethod
|
107
|
+
def purple(cls) -> "Color":
|
108
|
+
return cls(0x9B59B6)
|
109
|
+
|
110
|
+
@classmethod
|
111
|
+
def dark_purple(cls) -> "Color":
|
112
|
+
return cls(0x71368A)
|
113
|
+
|
114
|
+
@classmethod
|
115
|
+
def yellow(cls) -> "Color":
|
116
|
+
return cls(0xF1C40F)
|
117
|
+
|
118
|
+
@classmethod
|
119
|
+
def dark_gold(cls) -> "Color":
|
120
|
+
return cls(0xC27C0E)
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def light_gray(cls) -> "Color":
|
124
|
+
return cls(0x99AAB5)
|
125
|
+
|
126
|
+
@classmethod
|
127
|
+
def dark_gray(cls) -> "Color":
|
128
|
+
return cls(0x2C2F33)
|
129
|
+
|
130
|
+
@classmethod
|
131
|
+
def lighter_gray(cls) -> "Color":
|
132
|
+
return cls(0xBFBFBF)
|
133
|
+
|
134
|
+
@classmethod
|
135
|
+
def darker_gray(cls) -> "Color":
|
136
|
+
return cls(0x23272A)
|
137
|
+
|
138
|
+
@classmethod
|
139
|
+
def black(cls) -> "Color":
|
140
|
+
return cls(0x000000)
|
141
|
+
|
142
|
+
@classmethod
|
143
|
+
def white(cls) -> "Color":
|
144
|
+
return cls(0xFFFFFF)
|
145
|
+
|
49
146
|
def to_rgb(self) -> tuple[int, int, int]:
|
50
147
|
return ((self.value >> 16) & 0xFF, (self.value >> 8) & 0xFF, self.value & 0xFF)
|
51
148
|
|
52
149
|
@classmethod
|
53
|
-
def parse(
|
150
|
+
def parse(
|
151
|
+
cls, value: "Color | int | str | tuple[int, int, int] | None"
|
152
|
+
) -> "Color | None":
|
54
153
|
"""Convert ``value`` to a :class:`Color` instance.
|
55
154
|
|
56
155
|
Parameters
|
disagreement/enums.py
CHANGED
@@ -278,6 +278,62 @@ class GuildFeature(str, Enum): # Changed from IntEnum to Enum
|
|
278
278
|
return str(value)
|
279
279
|
|
280
280
|
|
281
|
+
# --- Guild Scheduled Event Enums ---
|
282
|
+
|
283
|
+
|
284
|
+
class GuildScheduledEventPrivacyLevel(IntEnum):
|
285
|
+
"""Privacy level for a scheduled event."""
|
286
|
+
|
287
|
+
GUILD_ONLY = 2
|
288
|
+
|
289
|
+
|
290
|
+
class GuildScheduledEventStatus(IntEnum):
|
291
|
+
"""Status of a scheduled event."""
|
292
|
+
|
293
|
+
SCHEDULED = 1
|
294
|
+
ACTIVE = 2
|
295
|
+
COMPLETED = 3
|
296
|
+
CANCELED = 4
|
297
|
+
|
298
|
+
|
299
|
+
class GuildScheduledEventEntityType(IntEnum):
|
300
|
+
"""Entity type for a scheduled event."""
|
301
|
+
|
302
|
+
STAGE_INSTANCE = 1
|
303
|
+
VOICE = 2
|
304
|
+
EXTERNAL = 3
|
305
|
+
|
306
|
+
|
307
|
+
class VoiceRegion(str, Enum):
|
308
|
+
"""Voice region identifier."""
|
309
|
+
|
310
|
+
AMSTERDAM = "amsterdam"
|
311
|
+
BRAZIL = "brazil"
|
312
|
+
DUBAI = "dubai"
|
313
|
+
EU_CENTRAL = "eu-central"
|
314
|
+
EU_WEST = "eu-west"
|
315
|
+
EUROPE = "europe"
|
316
|
+
FRANKFURT = "frankfurt"
|
317
|
+
HONGKONG = "hongkong"
|
318
|
+
INDIA = "india"
|
319
|
+
JAPAN = "japan"
|
320
|
+
RUSSIA = "russia"
|
321
|
+
SINGAPORE = "singapore"
|
322
|
+
SOUTHAFRICA = "southafrica"
|
323
|
+
SOUTH_KOREA = "south-korea"
|
324
|
+
SYDNEY = "sydney"
|
325
|
+
US_CENTRAL = "us-central"
|
326
|
+
US_EAST = "us-east"
|
327
|
+
US_SOUTH = "us-south"
|
328
|
+
US_WEST = "us-west"
|
329
|
+
VIP_US_EAST = "vip-us-east"
|
330
|
+
VIP_US_WEST = "vip-us-west"
|
331
|
+
|
332
|
+
@classmethod
|
333
|
+
def _missing_(cls, value): # type: ignore
|
334
|
+
return str(value)
|
335
|
+
|
336
|
+
|
281
337
|
# --- Channel Enums ---
|
282
338
|
|
283
339
|
|
@@ -305,6 +361,13 @@ class ChannelType(IntEnum):
|
|
305
361
|
GUILD_MEDIA = 16 # (Still in development) a channel that can only contain media
|
306
362
|
|
307
363
|
|
364
|
+
class StageInstancePrivacyLevel(IntEnum):
|
365
|
+
"""Privacy level of a stage instance."""
|
366
|
+
|
367
|
+
PUBLIC = 1
|
368
|
+
GUILD_ONLY = 2
|
369
|
+
|
370
|
+
|
308
371
|
class OverwriteType(IntEnum):
|
309
372
|
"""Type of target for a permission overwrite."""
|
310
373
|
|
disagreement/event_dispatcher.py
CHANGED
@@ -76,7 +76,7 @@ class EventDispatcher:
|
|
76
76
|
"""Parses MESSAGE_DELETE and updates message cache."""
|
77
77
|
message_id = data.get("id")
|
78
78
|
if message_id:
|
79
|
-
self._client._messages.
|
79
|
+
self._client._messages.invalidate(message_id)
|
80
80
|
return data
|
81
81
|
|
82
82
|
def _parse_message_reaction_raw(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
@@ -124,7 +124,7 @@ class EventDispatcher:
|
|
124
124
|
"""Parses GUILD_MEMBER_ADD into a Member object."""
|
125
125
|
|
126
126
|
guild_id = str(data.get("guild_id"))
|
127
|
-
return self._client.parse_member(data, guild_id)
|
127
|
+
return self._client.parse_member(data, guild_id, just_joined=True)
|
128
128
|
|
129
129
|
def _parse_guild_member_remove(self, data: Dict[str, Any]):
|
130
130
|
"""Parses GUILD_MEMBER_REMOVE into a GuildMemberRemove model."""
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# disagreement/ext/app_commands/handler.py
|
2
2
|
|
3
3
|
import inspect
|
4
|
+
import json
|
4
5
|
import logging
|
6
|
+
import os
|
5
7
|
from typing import (
|
6
8
|
TYPE_CHECKING,
|
7
9
|
Dict,
|
@@ -67,6 +69,8 @@ if not TYPE_CHECKING:
|
|
67
69
|
|
68
70
|
logger = logging.getLogger(__name__)
|
69
71
|
|
72
|
+
COMMANDS_CACHE_FILE = ".disagreement_commands.json"
|
73
|
+
|
70
74
|
|
71
75
|
class AppCommandHandler:
|
72
76
|
"""
|
@@ -84,6 +88,33 @@ class AppCommandHandler:
|
|
84
88
|
self._app_command_groups: Dict[str, AppCommandGroup] = {}
|
85
89
|
self._converter_registry: Dict[type, type] = {}
|
86
90
|
|
91
|
+
def _load_cached_ids(self) -> Dict[str, Dict[str, str]]:
|
92
|
+
try:
|
93
|
+
with open(COMMANDS_CACHE_FILE, "r", encoding="utf-8") as fp:
|
94
|
+
return json.load(fp)
|
95
|
+
except FileNotFoundError:
|
96
|
+
return {}
|
97
|
+
except json.JSONDecodeError:
|
98
|
+
logger.warning("Invalid command cache file. Ignoring.")
|
99
|
+
return {}
|
100
|
+
|
101
|
+
def _save_cached_ids(self, data: Dict[str, Dict[str, str]]) -> None:
|
102
|
+
try:
|
103
|
+
with open(COMMANDS_CACHE_FILE, "w", encoding="utf-8") as fp:
|
104
|
+
json.dump(data, fp, indent=2)
|
105
|
+
except Exception as e: # pragma: no cover - logging only
|
106
|
+
logger.error("Failed to write command cache: %s", e)
|
107
|
+
|
108
|
+
def clear_stored_registrations(self) -> None:
|
109
|
+
"""Remove persisted command registration data."""
|
110
|
+
if os.path.exists(COMMANDS_CACHE_FILE):
|
111
|
+
os.remove(COMMANDS_CACHE_FILE)
|
112
|
+
|
113
|
+
def migrate_stored_registrations(self, new_path: str) -> None:
|
114
|
+
"""Move stored registrations to ``new_path``."""
|
115
|
+
if os.path.exists(COMMANDS_CACHE_FILE):
|
116
|
+
os.replace(COMMANDS_CACHE_FILE, new_path)
|
117
|
+
|
87
118
|
def add_command(self, command: Union["AppCommand", "AppCommandGroup"]) -> None:
|
88
119
|
"""Adds an application command or a command group to the handler."""
|
89
120
|
if isinstance(command, AppCommandGroup):
|
@@ -564,11 +595,13 @@ class AppCommandHandler:
|
|
564
595
|
Synchronizes (registers/updates) all application commands with Discord.
|
565
596
|
If guild_id is provided, syncs commands for that guild. Otherwise, syncs global commands.
|
566
597
|
"""
|
567
|
-
|
598
|
+
cache = self._load_cached_ids()
|
599
|
+
scope_key = str(guild_id) if guild_id else "global"
|
600
|
+
stored = cache.get(scope_key, {})
|
568
601
|
|
569
|
-
|
570
|
-
# This needs to be more sophisticated to handle guild_ids on commands/groups
|
602
|
+
current_payloads: Dict[str, Dict[str, Any]] = {}
|
571
603
|
|
604
|
+
# Collect commands based on scope (global or specific guild)
|
572
605
|
source_commands = (
|
573
606
|
list(self._slash_commands.values())
|
574
607
|
+ list(self._user_commands.values())
|
@@ -577,26 +610,22 @@ class AppCommandHandler:
|
|
577
610
|
)
|
578
611
|
|
579
612
|
for cmd_or_group in source_commands:
|
580
|
-
# Determine if this command/group should be synced for the current scope
|
581
613
|
is_guild_specific_command = (
|
582
614
|
cmd_or_group.guild_ids is not None and len(cmd_or_group.guild_ids) > 0
|
583
615
|
)
|
584
616
|
|
585
|
-
if guild_id:
|
586
|
-
# Skip if not a guild-specific command OR if it's for a different guild
|
617
|
+
if guild_id:
|
587
618
|
if not is_guild_specific_command or (
|
588
619
|
cmd_or_group.guild_ids is not None
|
589
620
|
and guild_id not in cmd_or_group.guild_ids
|
590
621
|
):
|
591
622
|
continue
|
592
|
-
else:
|
623
|
+
else:
|
593
624
|
if is_guild_specific_command:
|
594
|
-
continue
|
625
|
+
continue
|
595
626
|
|
596
|
-
# Use the to_dict() method from AppCommand or AppCommandGroup
|
597
627
|
try:
|
598
|
-
|
599
|
-
commands_to_sync.append(payload)
|
628
|
+
current_payloads[cmd_or_group.name] = cmd_or_group.to_dict()
|
600
629
|
except AttributeError:
|
601
630
|
logger.warning(
|
602
631
|
"Command or group '%s' does not have a to_dict() method. Skipping.",
|
@@ -609,32 +638,74 @@ class AppCommandHandler:
|
|
609
638
|
e,
|
610
639
|
)
|
611
640
|
|
612
|
-
if not
|
641
|
+
if not current_payloads:
|
613
642
|
logger.info(
|
614
643
|
"No commands to sync for %s scope.",
|
615
644
|
f"guild {guild_id}" if guild_id else "global",
|
616
645
|
)
|
617
646
|
return
|
618
647
|
|
648
|
+
names_current = set(current_payloads)
|
649
|
+
names_stored = set(stored)
|
650
|
+
|
651
|
+
to_delete = names_stored - names_current
|
652
|
+
to_create = names_current - names_stored
|
653
|
+
to_update = names_current & names_stored
|
654
|
+
|
655
|
+
if not to_delete and not to_create and not to_update:
|
656
|
+
logger.info(
|
657
|
+
"Application commands already up to date for %s scope.", scope_key
|
658
|
+
)
|
659
|
+
return
|
660
|
+
|
619
661
|
try:
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
662
|
+
for name in to_delete:
|
663
|
+
cmd_id = stored[name]
|
664
|
+
if guild_id:
|
665
|
+
await self.client._http.delete_guild_application_command(
|
666
|
+
application_id, guild_id, cmd_id
|
667
|
+
)
|
668
|
+
else:
|
669
|
+
await self.client._http.delete_global_application_command(
|
670
|
+
application_id, cmd_id
|
671
|
+
)
|
672
|
+
|
673
|
+
new_ids: Dict[str, str] = {}
|
674
|
+
for name in to_create:
|
675
|
+
payload = current_payloads[name]
|
676
|
+
if guild_id:
|
677
|
+
result = await self.client._http.create_guild_application_command(
|
678
|
+
application_id, guild_id, payload
|
679
|
+
)
|
680
|
+
else:
|
681
|
+
result = await self.client._http.create_global_application_command(
|
682
|
+
application_id, payload
|
683
|
+
)
|
684
|
+
if result.id:
|
685
|
+
new_ids[name] = str(result.id)
|
686
|
+
|
687
|
+
for name in to_update:
|
688
|
+
payload = current_payloads[name]
|
689
|
+
cmd_id = stored[name]
|
690
|
+
if guild_id:
|
691
|
+
await self.client._http.edit_guild_application_command(
|
692
|
+
application_id, guild_id, cmd_id, payload
|
693
|
+
)
|
694
|
+
else:
|
695
|
+
await self.client._http.edit_global_application_command(
|
696
|
+
application_id, cmd_id, payload
|
697
|
+
)
|
698
|
+
new_ids[name] = cmd_id
|
699
|
+
|
700
|
+
final_ids: Dict[str, str] = {}
|
701
|
+
for name in names_current:
|
702
|
+
if name in new_ids:
|
703
|
+
final_ids[name] = new_ids[name]
|
704
|
+
else:
|
705
|
+
final_ids[name] = stored[name]
|
706
|
+
|
707
|
+
cache[scope_key] = final_ids
|
708
|
+
self._save_cached_ids(cache)
|
637
709
|
logger.info("Command sync successful.")
|
638
710
|
except Exception as e:
|
639
711
|
logger.error("Error syncing application commands: %s", e)
|
640
|
-
# Consider re-raising or specific error handling
|
@@ -1,57 +1,65 @@
|
|
1
|
-
# disagreement/ext/commands/__init__.py
|
2
|
-
|
3
|
-
"""
|
4
|
-
disagreement.ext.commands - A command framework extension for the Disagreement library.
|
5
|
-
"""
|
6
|
-
|
7
|
-
from .cog import Cog
|
8
|
-
from .core import (
|
9
|
-
Command,
|
10
|
-
CommandContext,
|
11
|
-
CommandHandler,
|
12
|
-
) # CommandHandler might be internal
|
13
|
-
from .decorators import (
|
14
|
-
command,
|
15
|
-
listener,
|
16
|
-
check,
|
17
|
-
check_any,
|
18
|
-
cooldown,
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
#
|
41
|
-
"
|
42
|
-
"
|
43
|
-
"
|
44
|
-
|
45
|
-
"
|
46
|
-
"
|
47
|
-
|
48
|
-
"
|
49
|
-
"
|
50
|
-
"
|
51
|
-
"
|
52
|
-
"
|
53
|
-
"
|
54
|
-
|
55
|
-
"
|
56
|
-
"
|
57
|
-
|
1
|
+
# disagreement/ext/commands/__init__.py
|
2
|
+
|
3
|
+
"""
|
4
|
+
disagreement.ext.commands - A command framework extension for the Disagreement library.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .cog import Cog
|
8
|
+
from .core import (
|
9
|
+
Command,
|
10
|
+
CommandContext,
|
11
|
+
CommandHandler,
|
12
|
+
) # CommandHandler might be internal
|
13
|
+
from .decorators import (
|
14
|
+
command,
|
15
|
+
listener,
|
16
|
+
check,
|
17
|
+
check_any,
|
18
|
+
cooldown,
|
19
|
+
max_concurrency,
|
20
|
+
requires_permissions,
|
21
|
+
has_role,
|
22
|
+
has_any_role,
|
23
|
+
)
|
24
|
+
from .errors import (
|
25
|
+
CommandError,
|
26
|
+
CommandNotFound,
|
27
|
+
BadArgument,
|
28
|
+
MissingRequiredArgument,
|
29
|
+
ArgumentParsingError,
|
30
|
+
CheckFailure,
|
31
|
+
CheckAnyFailure,
|
32
|
+
CommandOnCooldown,
|
33
|
+
CommandInvokeError,
|
34
|
+
MaxConcurrencyReached,
|
35
|
+
)
|
36
|
+
|
37
|
+
__all__ = [
|
38
|
+
# Cog
|
39
|
+
"Cog",
|
40
|
+
# Core
|
41
|
+
"Command",
|
42
|
+
"CommandContext",
|
43
|
+
# "CommandHandler", # Usually not part of public API for direct use by bot devs
|
44
|
+
# Decorators
|
45
|
+
"command",
|
46
|
+
"listener",
|
47
|
+
"check",
|
48
|
+
"check_any",
|
49
|
+
"cooldown",
|
50
|
+
"max_concurrency",
|
51
|
+
"requires_permissions",
|
52
|
+
"has_role",
|
53
|
+
"has_any_role",
|
54
|
+
# Errors
|
55
|
+
"CommandError",
|
56
|
+
"CommandNotFound",
|
57
|
+
"BadArgument",
|
58
|
+
"MissingRequiredArgument",
|
59
|
+
"ArgumentParsingError",
|
60
|
+
"CheckFailure",
|
61
|
+
"CheckAnyFailure",
|
62
|
+
"CommandOnCooldown",
|
63
|
+
"CommandInvokeError",
|
64
|
+
"MaxConcurrencyReached",
|
65
|
+
]
|