disagreement 0.0.1__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 +36 -0
- disagreement/cache.py +55 -0
- disagreement/client.py +1144 -0
- disagreement/components.py +166 -0
- disagreement/enums.py +357 -0
- disagreement/error_handler.py +33 -0
- disagreement/errors.py +112 -0
- disagreement/event_dispatcher.py +243 -0
- disagreement/gateway.py +490 -0
- disagreement/http.py +657 -0
- disagreement/hybrid_context.py +32 -0
- disagreement/i18n.py +22 -0
- disagreement/interactions.py +572 -0
- disagreement/logging_config.py +26 -0
- disagreement/models.py +1642 -0
- disagreement/oauth.py +109 -0
- disagreement/permissions.py +99 -0
- disagreement/rate_limiter.py +75 -0
- disagreement/shard_manager.py +65 -0
- disagreement/typing.py +42 -0
- disagreement/ui/__init__.py +17 -0
- disagreement/ui/button.py +99 -0
- disagreement/ui/item.py +38 -0
- disagreement/ui/modal.py +132 -0
- disagreement/ui/select.py +92 -0
- disagreement/ui/view.py +165 -0
- disagreement/voice_client.py +120 -0
- disagreement-0.0.1.dist-info/METADATA +163 -0
- disagreement-0.0.1.dist-info/RECORD +32 -0
- disagreement-0.0.1.dist-info/WHEEL +5 -0
- disagreement-0.0.1.dist-info/licenses/LICENSE +26 -0
- disagreement-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,166 @@
|
|
1
|
+
"""Message component utilities."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import Any, Dict, Optional, TYPE_CHECKING
|
6
|
+
|
7
|
+
from .enums import ComponentType, ButtonStyle, ChannelType, TextInputStyle
|
8
|
+
from .models import (
|
9
|
+
ActionRow,
|
10
|
+
Button,
|
11
|
+
Component,
|
12
|
+
SelectMenu,
|
13
|
+
SelectOption,
|
14
|
+
PartialEmoji,
|
15
|
+
PartialEmoji,
|
16
|
+
Section,
|
17
|
+
TextDisplay,
|
18
|
+
Thumbnail,
|
19
|
+
MediaGallery,
|
20
|
+
MediaGalleryItem,
|
21
|
+
File,
|
22
|
+
Separator,
|
23
|
+
Container,
|
24
|
+
UnfurledMediaItem,
|
25
|
+
)
|
26
|
+
|
27
|
+
if TYPE_CHECKING: # pragma: no cover - optional client for future use
|
28
|
+
from .client import Client
|
29
|
+
|
30
|
+
|
31
|
+
def component_factory(
|
32
|
+
data: Dict[str, Any], client: Optional["Client"] = None
|
33
|
+
) -> "Component":
|
34
|
+
"""Create a component object from raw API data."""
|
35
|
+
ctype = ComponentType(data["type"])
|
36
|
+
|
37
|
+
if ctype == ComponentType.ACTION_ROW:
|
38
|
+
row = ActionRow()
|
39
|
+
for comp in data.get("components", []):
|
40
|
+
row.add_component(component_factory(comp, client))
|
41
|
+
return row
|
42
|
+
|
43
|
+
if ctype == ComponentType.BUTTON:
|
44
|
+
return Button(
|
45
|
+
style=ButtonStyle(data["style"]),
|
46
|
+
label=data.get("label"),
|
47
|
+
emoji=PartialEmoji(data["emoji"]) if data.get("emoji") else None,
|
48
|
+
custom_id=data.get("custom_id"),
|
49
|
+
url=data.get("url"),
|
50
|
+
disabled=data.get("disabled", False),
|
51
|
+
)
|
52
|
+
|
53
|
+
if ctype in {
|
54
|
+
ComponentType.STRING_SELECT,
|
55
|
+
ComponentType.USER_SELECT,
|
56
|
+
ComponentType.ROLE_SELECT,
|
57
|
+
ComponentType.MENTIONABLE_SELECT,
|
58
|
+
ComponentType.CHANNEL_SELECT,
|
59
|
+
}:
|
60
|
+
options = [
|
61
|
+
SelectOption(
|
62
|
+
label=o["label"],
|
63
|
+
value=o["value"],
|
64
|
+
description=o.get("description"),
|
65
|
+
emoji=PartialEmoji(o["emoji"]) if o.get("emoji") else None,
|
66
|
+
default=o.get("default", False),
|
67
|
+
)
|
68
|
+
for o in data.get("options", [])
|
69
|
+
]
|
70
|
+
channel_types = None
|
71
|
+
if ctype == ComponentType.CHANNEL_SELECT and data.get("channel_types"):
|
72
|
+
channel_types = [ChannelType(ct) for ct in data.get("channel_types", [])]
|
73
|
+
|
74
|
+
return SelectMenu(
|
75
|
+
custom_id=data["custom_id"],
|
76
|
+
options=options,
|
77
|
+
placeholder=data.get("placeholder"),
|
78
|
+
min_values=data.get("min_values", 1),
|
79
|
+
max_values=data.get("max_values", 1),
|
80
|
+
disabled=data.get("disabled", False),
|
81
|
+
channel_types=channel_types,
|
82
|
+
type=ctype,
|
83
|
+
)
|
84
|
+
|
85
|
+
if ctype == ComponentType.TEXT_INPUT:
|
86
|
+
from .ui.modal import TextInput
|
87
|
+
|
88
|
+
return TextInput(
|
89
|
+
label=data.get("label", ""),
|
90
|
+
custom_id=data.get("custom_id"),
|
91
|
+
style=TextInputStyle(data.get("style", TextInputStyle.SHORT.value)),
|
92
|
+
placeholder=data.get("placeholder"),
|
93
|
+
required=data.get("required", True),
|
94
|
+
min_length=data.get("min_length"),
|
95
|
+
max_length=data.get("max_length"),
|
96
|
+
)
|
97
|
+
|
98
|
+
if ctype == ComponentType.SECTION:
|
99
|
+
# The components in a section can only be TextDisplay
|
100
|
+
section_components = []
|
101
|
+
for c in data.get("components", []):
|
102
|
+
comp = component_factory(c, client)
|
103
|
+
if isinstance(comp, TextDisplay):
|
104
|
+
section_components.append(comp)
|
105
|
+
|
106
|
+
accessory = None
|
107
|
+
if data.get("accessory"):
|
108
|
+
acc_comp = component_factory(data["accessory"], client)
|
109
|
+
if isinstance(acc_comp, (Thumbnail, Button)):
|
110
|
+
accessory = acc_comp
|
111
|
+
|
112
|
+
return Section(
|
113
|
+
components=section_components,
|
114
|
+
accessory=accessory,
|
115
|
+
id=data.get("id"),
|
116
|
+
)
|
117
|
+
|
118
|
+
if ctype == ComponentType.TEXT_DISPLAY:
|
119
|
+
return TextDisplay(content=data["content"], id=data.get("id"))
|
120
|
+
|
121
|
+
if ctype == ComponentType.THUMBNAIL:
|
122
|
+
return Thumbnail(
|
123
|
+
media=UnfurledMediaItem(**data["media"]),
|
124
|
+
description=data.get("description"),
|
125
|
+
spoiler=data.get("spoiler", False),
|
126
|
+
id=data.get("id"),
|
127
|
+
)
|
128
|
+
|
129
|
+
if ctype == ComponentType.MEDIA_GALLERY:
|
130
|
+
return MediaGallery(
|
131
|
+
items=[
|
132
|
+
MediaGalleryItem(
|
133
|
+
media=UnfurledMediaItem(**i["media"]),
|
134
|
+
description=i.get("description"),
|
135
|
+
spoiler=i.get("spoiler", False),
|
136
|
+
)
|
137
|
+
for i in data.get("items", [])
|
138
|
+
],
|
139
|
+
id=data.get("id"),
|
140
|
+
)
|
141
|
+
|
142
|
+
if ctype == ComponentType.FILE:
|
143
|
+
return File(
|
144
|
+
file=UnfurledMediaItem(**data["file"]),
|
145
|
+
spoiler=data.get("spoiler", False),
|
146
|
+
id=data.get("id"),
|
147
|
+
)
|
148
|
+
|
149
|
+
if ctype == ComponentType.SEPARATOR:
|
150
|
+
return Separator(
|
151
|
+
divider=data.get("divider", True),
|
152
|
+
spacing=data.get("spacing", 1),
|
153
|
+
id=data.get("id"),
|
154
|
+
)
|
155
|
+
|
156
|
+
if ctype == ComponentType.CONTAINER:
|
157
|
+
return Container(
|
158
|
+
components=[
|
159
|
+
component_factory(c, client) for c in data.get("components", [])
|
160
|
+
],
|
161
|
+
accent_color=data.get("accent_color"),
|
162
|
+
spoiler=data.get("spoiler", False),
|
163
|
+
id=data.get("id"),
|
164
|
+
)
|
165
|
+
|
166
|
+
raise ValueError(f"Unsupported component type: {ctype}")
|
disagreement/enums.py
ADDED
@@ -0,0 +1,357 @@
|
|
1
|
+
# disagreement/enums.py
|
2
|
+
|
3
|
+
"""
|
4
|
+
Enums for Discord constants.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from enum import IntEnum, Enum # Import Enum
|
8
|
+
|
9
|
+
|
10
|
+
class GatewayOpcode(IntEnum):
|
11
|
+
"""Represents a Discord Gateway Opcode."""
|
12
|
+
|
13
|
+
DISPATCH = 0
|
14
|
+
HEARTBEAT = 1
|
15
|
+
IDENTIFY = 2
|
16
|
+
PRESENCE_UPDATE = 3
|
17
|
+
VOICE_STATE_UPDATE = 4
|
18
|
+
RESUME = 6
|
19
|
+
RECONNECT = 7
|
20
|
+
REQUEST_GUILD_MEMBERS = 8
|
21
|
+
INVALID_SESSION = 9
|
22
|
+
HELLO = 10
|
23
|
+
HEARTBEAT_ACK = 11
|
24
|
+
|
25
|
+
|
26
|
+
class GatewayIntent(IntEnum):
|
27
|
+
"""Represents a Discord Gateway Intent bit.
|
28
|
+
|
29
|
+
Intents are used to subscribe to specific groups of events from the Gateway.
|
30
|
+
"""
|
31
|
+
|
32
|
+
GUILDS = 1 << 0
|
33
|
+
GUILD_MEMBERS = 1 << 1 # Privileged
|
34
|
+
GUILD_MODERATION = 1 << 2 # Formerly GUILD_BANS
|
35
|
+
GUILD_EMOJIS_AND_STICKERS = 1 << 3
|
36
|
+
GUILD_INTEGRATIONS = 1 << 4
|
37
|
+
GUILD_WEBHOOKS = 1 << 5
|
38
|
+
GUILD_INVITES = 1 << 6
|
39
|
+
GUILD_VOICE_STATES = 1 << 7
|
40
|
+
GUILD_PRESENCES = 1 << 8 # Privileged
|
41
|
+
GUILD_MESSAGES = 1 << 9
|
42
|
+
GUILD_MESSAGE_REACTIONS = 1 << 10
|
43
|
+
GUILD_MESSAGE_TYPING = 1 << 11
|
44
|
+
DIRECT_MESSAGES = 1 << 12
|
45
|
+
DIRECT_MESSAGE_REACTIONS = 1 << 13
|
46
|
+
DIRECT_MESSAGE_TYPING = 1 << 14
|
47
|
+
MESSAGE_CONTENT = 1 << 15 # Privileged (as of Aug 31, 2022)
|
48
|
+
GUILD_SCHEDULED_EVENTS = 1 << 16
|
49
|
+
AUTO_MODERATION_CONFIGURATION = 1 << 20
|
50
|
+
AUTO_MODERATION_EXECUTION = 1 << 21
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def default(cls) -> int:
|
54
|
+
"""Returns default intents (excluding privileged ones like members, presences, message content)."""
|
55
|
+
return (
|
56
|
+
cls.GUILDS
|
57
|
+
| cls.GUILD_MODERATION
|
58
|
+
| cls.GUILD_EMOJIS_AND_STICKERS
|
59
|
+
| cls.GUILD_INTEGRATIONS
|
60
|
+
| cls.GUILD_WEBHOOKS
|
61
|
+
| cls.GUILD_INVITES
|
62
|
+
| cls.GUILD_VOICE_STATES
|
63
|
+
| cls.GUILD_MESSAGES
|
64
|
+
| cls.GUILD_MESSAGE_REACTIONS
|
65
|
+
| cls.GUILD_MESSAGE_TYPING
|
66
|
+
| cls.DIRECT_MESSAGES
|
67
|
+
| cls.DIRECT_MESSAGE_REACTIONS
|
68
|
+
| cls.DIRECT_MESSAGE_TYPING
|
69
|
+
| cls.GUILD_SCHEDULED_EVENTS
|
70
|
+
| cls.AUTO_MODERATION_CONFIGURATION
|
71
|
+
| cls.AUTO_MODERATION_EXECUTION
|
72
|
+
)
|
73
|
+
|
74
|
+
@classmethod
|
75
|
+
def all(cls) -> int:
|
76
|
+
"""Returns all intents, including privileged ones. Use with caution."""
|
77
|
+
val = 0
|
78
|
+
for intent in cls:
|
79
|
+
val |= intent.value
|
80
|
+
return val
|
81
|
+
|
82
|
+
@classmethod
|
83
|
+
def privileged(cls) -> int:
|
84
|
+
"""Returns a bitmask of all privileged intents."""
|
85
|
+
return cls.GUILD_MEMBERS | cls.GUILD_PRESENCES | cls.MESSAGE_CONTENT
|
86
|
+
|
87
|
+
|
88
|
+
# --- Application Command Enums ---
|
89
|
+
|
90
|
+
|
91
|
+
class ApplicationCommandType(IntEnum):
|
92
|
+
"""Type of application command."""
|
93
|
+
|
94
|
+
CHAT_INPUT = 1
|
95
|
+
USER = 2
|
96
|
+
MESSAGE = 3
|
97
|
+
PRIMARY_ENTRY_POINT = 4
|
98
|
+
|
99
|
+
|
100
|
+
class ApplicationCommandOptionType(IntEnum):
|
101
|
+
"""Type of application command option."""
|
102
|
+
|
103
|
+
SUB_COMMAND = 1
|
104
|
+
SUB_COMMAND_GROUP = 2
|
105
|
+
STRING = 3
|
106
|
+
INTEGER = 4 # Any integer between -2^53 and 2^53
|
107
|
+
BOOLEAN = 5
|
108
|
+
USER = 6
|
109
|
+
CHANNEL = 7 # Includes all channel types + categories
|
110
|
+
ROLE = 8
|
111
|
+
MENTIONABLE = 9 # Includes users and roles
|
112
|
+
NUMBER = 10 # Any double between -2^53 and 2^53
|
113
|
+
ATTACHMENT = 11
|
114
|
+
|
115
|
+
|
116
|
+
class InteractionType(IntEnum):
|
117
|
+
"""Type of interaction."""
|
118
|
+
|
119
|
+
PING = 1
|
120
|
+
APPLICATION_COMMAND = 2
|
121
|
+
MESSAGE_COMPONENT = 3
|
122
|
+
APPLICATION_COMMAND_AUTOCOMPLETE = 4
|
123
|
+
MODAL_SUBMIT = 5
|
124
|
+
|
125
|
+
|
126
|
+
class InteractionCallbackType(IntEnum):
|
127
|
+
"""Type of interaction callback."""
|
128
|
+
|
129
|
+
PONG = 1
|
130
|
+
CHANNEL_MESSAGE_WITH_SOURCE = 4
|
131
|
+
DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE = 5
|
132
|
+
DEFERRED_UPDATE_MESSAGE = 6
|
133
|
+
UPDATE_MESSAGE = 7
|
134
|
+
APPLICATION_COMMAND_AUTOCOMPLETE_RESULT = 8
|
135
|
+
MODAL = 9 # Response to send a modal
|
136
|
+
|
137
|
+
|
138
|
+
class IntegrationType(IntEnum):
|
139
|
+
"""
|
140
|
+
Installation context(s) where the command is available,
|
141
|
+
only for globally-scoped commands.
|
142
|
+
"""
|
143
|
+
|
144
|
+
GUILD_INSTALL = (
|
145
|
+
0 # Command is available when the app is installed to a guild (default)
|
146
|
+
)
|
147
|
+
USER_INSTALL = 1 # Command is available when the app is installed to a user
|
148
|
+
|
149
|
+
|
150
|
+
class InteractionContextType(IntEnum):
|
151
|
+
"""
|
152
|
+
Interaction context(s) where the command can be used,
|
153
|
+
only for globally-scoped commands.
|
154
|
+
"""
|
155
|
+
|
156
|
+
GUILD = 0 # Command can be used in guilds
|
157
|
+
BOT_DM = 1 # Command can be used in DMs with the app's bot user
|
158
|
+
PRIVATE_CHANNEL = 2 # Command can be used in Group DMs and DMs (requires USER_INSTALL integration_type)
|
159
|
+
|
160
|
+
|
161
|
+
class MessageFlags(IntEnum):
|
162
|
+
"""Represents the flags of a message."""
|
163
|
+
|
164
|
+
CROSSPOSTED = 1 << 0
|
165
|
+
IS_CROSSPOST = 1 << 1
|
166
|
+
SUPPRESS_EMBEDS = 1 << 2
|
167
|
+
SOURCE_MESSAGE_DELETED = 1 << 3
|
168
|
+
URGENT = 1 << 4
|
169
|
+
HAS_THREAD = 1 << 5
|
170
|
+
EPHEMERAL = 1 << 6
|
171
|
+
LOADING = 1 << 7
|
172
|
+
FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8
|
173
|
+
SUPPRESS_NOTIFICATIONS = (
|
174
|
+
1 << 12
|
175
|
+
) # Discord specific, was previously 1 << 4 (IS_VOICE_MESSAGE)
|
176
|
+
IS_COMPONENTS_V2 = 1 << 15
|
177
|
+
|
178
|
+
|
179
|
+
# --- Guild Enums ---
|
180
|
+
|
181
|
+
|
182
|
+
class VerificationLevel(IntEnum):
|
183
|
+
"""Guild verification level."""
|
184
|
+
|
185
|
+
NONE = 0
|
186
|
+
LOW = 1
|
187
|
+
MEDIUM = 2
|
188
|
+
HIGH = 3
|
189
|
+
VERY_HIGH = 4
|
190
|
+
|
191
|
+
|
192
|
+
class MessageNotificationLevel(IntEnum):
|
193
|
+
"""Default message notification level for a guild."""
|
194
|
+
|
195
|
+
ALL_MESSAGES = 0
|
196
|
+
ONLY_MENTIONS = 1
|
197
|
+
|
198
|
+
|
199
|
+
class ExplicitContentFilterLevel(IntEnum):
|
200
|
+
"""Explicit content filter level for a guild."""
|
201
|
+
|
202
|
+
DISABLED = 0
|
203
|
+
MEMBERS_WITHOUT_ROLES = 1
|
204
|
+
ALL_MEMBERS = 2
|
205
|
+
|
206
|
+
|
207
|
+
class MFALevel(IntEnum):
|
208
|
+
"""Multi-Factor Authentication level for a guild."""
|
209
|
+
|
210
|
+
NONE = 0
|
211
|
+
ELEVATED = 1
|
212
|
+
|
213
|
+
|
214
|
+
class GuildNSFWLevel(IntEnum):
|
215
|
+
"""NSFW level of a guild."""
|
216
|
+
|
217
|
+
DEFAULT = 0
|
218
|
+
EXPLICIT = 1
|
219
|
+
SAFE = 2
|
220
|
+
AGE_RESTRICTED = 3
|
221
|
+
|
222
|
+
|
223
|
+
class PremiumTier(IntEnum):
|
224
|
+
"""Guild premium tier (boost level)."""
|
225
|
+
|
226
|
+
NONE = 0
|
227
|
+
TIER_1 = 1
|
228
|
+
TIER_2 = 2
|
229
|
+
TIER_3 = 3
|
230
|
+
|
231
|
+
|
232
|
+
class GuildFeature(str, Enum): # Changed from IntEnum to Enum
|
233
|
+
"""Features that a guild can have.
|
234
|
+
|
235
|
+
Note: This is not an exhaustive list and Discord may add more.
|
236
|
+
Using str as a base allows for unknown features to be stored as strings.
|
237
|
+
"""
|
238
|
+
|
239
|
+
ANIMATED_BANNER = "ANIMATED_BANNER"
|
240
|
+
ANIMATED_ICON = "ANIMATED_ICON"
|
241
|
+
APPLICATION_COMMAND_PERMISSIONS_V2 = "APPLICATION_COMMAND_PERMISSIONS_V2"
|
242
|
+
AUTO_MODERATION = "AUTO_MODERATION"
|
243
|
+
BANNER = "BANNER"
|
244
|
+
COMMUNITY = "COMMUNITY"
|
245
|
+
CREATOR_MONETIZABLE_PROVISIONAL = "CREATOR_MONETIZABLE_PROVISIONAL"
|
246
|
+
CREATOR_STORE_PAGE = "CREATOR_STORE_PAGE"
|
247
|
+
DEVELOPER_SUPPORT_SERVER = "DEVELOPER_SUPPORT_SERVER"
|
248
|
+
DISCOVERABLE = "DISCOVERABLE"
|
249
|
+
FEATURABLE = "FEATURABLE"
|
250
|
+
INVITES_DISABLED = "INVITES_DISABLED"
|
251
|
+
INVITE_SPLASH = "INVITE_SPLASH"
|
252
|
+
MEMBER_VERIFICATION_GATE_ENABLED = "MEMBER_VERIFICATION_GATE_ENABLED"
|
253
|
+
MORE_STICKERS = "MORE_STICKERS"
|
254
|
+
NEWS = "NEWS"
|
255
|
+
PARTNERED = "PARTNERED"
|
256
|
+
PREVIEW_ENABLED = "PREVIEW_ENABLED"
|
257
|
+
RAID_ALERTS_DISABLED = "RAID_ALERTS_DISABLED"
|
258
|
+
ROLE_ICONS = "ROLE_ICONS"
|
259
|
+
ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE = (
|
260
|
+
"ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE"
|
261
|
+
)
|
262
|
+
ROLE_SUBSCRIPTIONS_ENABLED = "ROLE_SUBSCRIPTIONS_ENABLED"
|
263
|
+
TICKETED_EVENTS_ENABLED = "TICKETED_EVENTS_ENABLED"
|
264
|
+
VANITY_URL = "VANITY_URL"
|
265
|
+
VERIFIED = "VERIFIED"
|
266
|
+
VIP_REGIONS = "VIP_REGIONS"
|
267
|
+
WELCOME_SCREEN_ENABLED = "WELCOME_SCREEN_ENABLED"
|
268
|
+
# Add more as they become known or needed
|
269
|
+
|
270
|
+
# This allows GuildFeature("UNKNOWN_FEATURE_STRING") to work
|
271
|
+
@classmethod
|
272
|
+
def _missing_(cls, value): # type: ignore
|
273
|
+
return str(value)
|
274
|
+
|
275
|
+
|
276
|
+
# --- Channel Enums ---
|
277
|
+
|
278
|
+
|
279
|
+
class ChannelType(IntEnum):
|
280
|
+
"""Type of channel."""
|
281
|
+
|
282
|
+
GUILD_TEXT = 0 # a text channel within a server
|
283
|
+
DM = 1 # a direct message between users
|
284
|
+
GUILD_VOICE = 2 # a voice channel within a server
|
285
|
+
GROUP_DM = 3 # a direct message between multiple users
|
286
|
+
GUILD_CATEGORY = 4 # an organizational category that contains up to 50 channels
|
287
|
+
GUILD_ANNOUNCEMENT = 5 # a channel that users can follow and crosspost into their own server (formerly GUILD_NEWS)
|
288
|
+
ANNOUNCEMENT_THREAD = (
|
289
|
+
10 # a temporary sub-channel within a GUILD_ANNOUNCEMENT channel
|
290
|
+
)
|
291
|
+
PUBLIC_THREAD = (
|
292
|
+
11 # a temporary sub-channel within a GUILD_TEXT or GUILD_ANNOUNCEMENT channel
|
293
|
+
)
|
294
|
+
PRIVATE_THREAD = 12 # a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
|
295
|
+
GUILD_STAGE_VOICE = (
|
296
|
+
13 # a voice channel for hosting events with speakers and audiences
|
297
|
+
)
|
298
|
+
GUILD_DIRECTORY = 14 # a channel in a hub containing the listed servers
|
299
|
+
GUILD_FORUM = 15 # (Still in development) a channel that can only contain threads
|
300
|
+
GUILD_MEDIA = 16 # (Still in development) a channel that can only contain media
|
301
|
+
|
302
|
+
|
303
|
+
class OverwriteType(IntEnum):
|
304
|
+
"""Type of target for a permission overwrite."""
|
305
|
+
|
306
|
+
ROLE = 0
|
307
|
+
MEMBER = 1
|
308
|
+
|
309
|
+
|
310
|
+
# --- Component Enums ---
|
311
|
+
|
312
|
+
|
313
|
+
class ComponentType(IntEnum):
|
314
|
+
"""Type of message component."""
|
315
|
+
|
316
|
+
ACTION_ROW = 1
|
317
|
+
BUTTON = 2
|
318
|
+
STRING_SELECT = 3 # Formerly SELECT_MENU
|
319
|
+
TEXT_INPUT = 4
|
320
|
+
USER_SELECT = 5
|
321
|
+
ROLE_SELECT = 6
|
322
|
+
MENTIONABLE_SELECT = 7
|
323
|
+
CHANNEL_SELECT = 8
|
324
|
+
SECTION = 9
|
325
|
+
TEXT_DISPLAY = 10
|
326
|
+
THUMBNAIL = 11
|
327
|
+
MEDIA_GALLERY = 12
|
328
|
+
FILE = 13
|
329
|
+
SEPARATOR = 14
|
330
|
+
CONTAINER = 17
|
331
|
+
|
332
|
+
|
333
|
+
class ButtonStyle(IntEnum):
|
334
|
+
"""Style of a button component."""
|
335
|
+
|
336
|
+
# Blurple
|
337
|
+
PRIMARY = 1
|
338
|
+
# Grey
|
339
|
+
SECONDARY = 2
|
340
|
+
# Green
|
341
|
+
SUCCESS = 3
|
342
|
+
# Red
|
343
|
+
DANGER = 4
|
344
|
+
# Grey, navigates to a URL
|
345
|
+
LINK = 5
|
346
|
+
|
347
|
+
|
348
|
+
class TextInputStyle(IntEnum):
|
349
|
+
"""Style of a text input component."""
|
350
|
+
|
351
|
+
SHORT = 1
|
352
|
+
PARAGRAPH = 2
|
353
|
+
|
354
|
+
|
355
|
+
# Example of how you might combine intents:
|
356
|
+
# intents = GatewayIntent.GUILDS | GatewayIntent.GUILD_MESSAGES | GatewayIntent.MESSAGE_CONTENT
|
357
|
+
# client = Client(token="YOUR_TOKEN", intents=intents)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import traceback
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from .logging_config import setup_logging
|
7
|
+
|
8
|
+
|
9
|
+
def setup_global_error_handler(
|
10
|
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
11
|
+
) -> None:
|
12
|
+
"""Configure a basic global error handler for the provided loop.
|
13
|
+
|
14
|
+
The handler logs unhandled exceptions so they don't crash the bot.
|
15
|
+
"""
|
16
|
+
if loop is None:
|
17
|
+
loop = asyncio.get_event_loop()
|
18
|
+
|
19
|
+
if not logging.getLogger().hasHandlers():
|
20
|
+
setup_logging(logging.ERROR)
|
21
|
+
|
22
|
+
def handle_exception(loop: asyncio.AbstractEventLoop, context: dict) -> None:
|
23
|
+
exception = context.get("exception")
|
24
|
+
if exception:
|
25
|
+
logging.error("Unhandled exception in event loop: %s", exception)
|
26
|
+
traceback.print_exception(
|
27
|
+
type(exception), exception, exception.__traceback__
|
28
|
+
)
|
29
|
+
else:
|
30
|
+
message = context.get("message")
|
31
|
+
logging.error("Event loop error: %s", message)
|
32
|
+
|
33
|
+
loop.set_exception_handler(handle_exception)
|
disagreement/errors.py
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# disagreement/errors.py
|
2
|
+
|
3
|
+
"""
|
4
|
+
Custom exceptions for the Disagreement library.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Optional, Any # Add Optional and Any here
|
8
|
+
|
9
|
+
|
10
|
+
class DisagreementException(Exception):
|
11
|
+
"""Base exception class for all errors raised by this library."""
|
12
|
+
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
class HTTPException(DisagreementException):
|
17
|
+
"""Exception raised for HTTP-related errors.
|
18
|
+
|
19
|
+
Attributes:
|
20
|
+
response: The aiohttp response object, if available.
|
21
|
+
status: The HTTP status code.
|
22
|
+
text: The response text, if available.
|
23
|
+
error_code: Discord specific error code, if available.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(
|
27
|
+
self, response=None, message=None, *, status=None, text=None, error_code=None
|
28
|
+
):
|
29
|
+
self.response = response
|
30
|
+
self.status = status or (response.status if response else None)
|
31
|
+
self.text = text or (
|
32
|
+
response.text if response else None
|
33
|
+
) # Or await response.text() if in async context
|
34
|
+
self.error_code = error_code
|
35
|
+
|
36
|
+
full_message = f"HTTP {self.status}"
|
37
|
+
if message:
|
38
|
+
full_message += f": {message}"
|
39
|
+
elif self.text:
|
40
|
+
full_message += f": {self.text}"
|
41
|
+
if self.error_code:
|
42
|
+
full_message += f" (Discord Error Code: {self.error_code})"
|
43
|
+
|
44
|
+
super().__init__(full_message)
|
45
|
+
|
46
|
+
|
47
|
+
class GatewayException(DisagreementException):
|
48
|
+
"""Exception raised for errors related to the Discord Gateway connection or protocol."""
|
49
|
+
|
50
|
+
pass
|
51
|
+
|
52
|
+
|
53
|
+
class AuthenticationError(DisagreementException):
|
54
|
+
"""Exception raised for authentication failures (e.g., invalid token)."""
|
55
|
+
|
56
|
+
pass
|
57
|
+
|
58
|
+
|
59
|
+
class RateLimitError(HTTPException):
|
60
|
+
"""
|
61
|
+
Exception raised when a rate limit is encountered.
|
62
|
+
|
63
|
+
Attributes:
|
64
|
+
retry_after (float): The number of seconds to wait before retrying.
|
65
|
+
is_global (bool): Whether this is a global rate limit.
|
66
|
+
"""
|
67
|
+
|
68
|
+
def __init__(
|
69
|
+
self, response, message=None, *, retry_after: float, is_global: bool = False
|
70
|
+
):
|
71
|
+
self.retry_after = retry_after
|
72
|
+
self.is_global = is_global
|
73
|
+
super().__init__(
|
74
|
+
response,
|
75
|
+
message
|
76
|
+
or f"Rate limited. Retry after: {retry_after}s. Global: {is_global}",
|
77
|
+
)
|
78
|
+
|
79
|
+
|
80
|
+
# You can add more specific exceptions as needed, e.g.:
|
81
|
+
# class NotFound(HTTPException):
|
82
|
+
# """Raised for 404 Not Found errors."""
|
83
|
+
# pass
|
84
|
+
|
85
|
+
# class Forbidden(HTTPException):
|
86
|
+
# """Raised for 403 Forbidden errors."""
|
87
|
+
# pass
|
88
|
+
|
89
|
+
|
90
|
+
class AppCommandError(DisagreementException):
|
91
|
+
"""Base exception for application command related errors."""
|
92
|
+
|
93
|
+
pass
|
94
|
+
|
95
|
+
|
96
|
+
class AppCommandOptionConversionError(AppCommandError):
|
97
|
+
"""Exception raised when an application command option fails to convert."""
|
98
|
+
|
99
|
+
def __init__(
|
100
|
+
self,
|
101
|
+
message: str,
|
102
|
+
option_name: Optional[str] = None,
|
103
|
+
original_value: Any = None,
|
104
|
+
):
|
105
|
+
self.option_name = option_name
|
106
|
+
self.original_value = original_value
|
107
|
+
full_message = message
|
108
|
+
if option_name:
|
109
|
+
full_message = f"Failed to convert option '{option_name}': {message}"
|
110
|
+
if original_value is not None:
|
111
|
+
full_message += f" (Original value: '{original_value}')"
|
112
|
+
super().__init__(full_message)
|