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,243 @@
|
|
1
|
+
# disagreement/event_dispatcher.py
|
2
|
+
|
3
|
+
"""
|
4
|
+
Event dispatcher for handling Discord Gateway events.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import asyncio
|
8
|
+
import inspect
|
9
|
+
from collections import defaultdict
|
10
|
+
from typing import (
|
11
|
+
Callable,
|
12
|
+
Coroutine,
|
13
|
+
Any,
|
14
|
+
Dict,
|
15
|
+
List,
|
16
|
+
Set,
|
17
|
+
TYPE_CHECKING,
|
18
|
+
Awaitable,
|
19
|
+
Optional,
|
20
|
+
)
|
21
|
+
|
22
|
+
from .models import Message, User # Assuming User might be part of other events
|
23
|
+
from .errors import DisagreementException
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from .client import Client # For type hinting to avoid circular imports
|
27
|
+
from .interactions import Interaction
|
28
|
+
|
29
|
+
# Type alias for an event listener
|
30
|
+
EventListener = Callable[..., Awaitable[None]]
|
31
|
+
|
32
|
+
|
33
|
+
class EventDispatcher:
|
34
|
+
"""
|
35
|
+
Manages registration and dispatching of event listeners.
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__(self, client_instance: "Client"):
|
39
|
+
self._client: "Client" = client_instance
|
40
|
+
self._listeners: Dict[str, List[EventListener]] = defaultdict(list)
|
41
|
+
self._waiters: Dict[
|
42
|
+
str, List[tuple[asyncio.Future, Optional[Callable[[Any], bool]]]]
|
43
|
+
] = defaultdict(list)
|
44
|
+
self.on_dispatch_error: Optional[
|
45
|
+
Callable[[str, Exception, EventListener], Awaitable[None]]
|
46
|
+
] = None
|
47
|
+
# Pre-defined parsers for specific event types to convert raw data to models
|
48
|
+
self._event_parsers: Dict[str, Callable[[Dict[str, Any]], Any]] = {
|
49
|
+
"MESSAGE_CREATE": self._parse_message_create,
|
50
|
+
"INTERACTION_CREATE": self._parse_interaction_create,
|
51
|
+
"GUILD_CREATE": self._parse_guild_create,
|
52
|
+
"CHANNEL_CREATE": self._parse_channel_create,
|
53
|
+
"PRESENCE_UPDATE": self._parse_presence_update,
|
54
|
+
"TYPING_START": self._parse_typing_start,
|
55
|
+
}
|
56
|
+
|
57
|
+
def _parse_message_create(self, data: Dict[str, Any]) -> Message:
|
58
|
+
"""Parses raw MESSAGE_CREATE data into a Message object."""
|
59
|
+
return self._client.parse_message(data)
|
60
|
+
|
61
|
+
def _parse_interaction_create(self, data: Dict[str, Any]) -> "Interaction":
|
62
|
+
"""Parses raw INTERACTION_CREATE data into an Interaction object."""
|
63
|
+
from .interactions import Interaction
|
64
|
+
|
65
|
+
return Interaction(data=data, client_instance=self._client)
|
66
|
+
|
67
|
+
def _parse_guild_create(self, data: Dict[str, Any]):
|
68
|
+
"""Parses raw GUILD_CREATE data into a Guild object."""
|
69
|
+
|
70
|
+
return self._client.parse_guild(data)
|
71
|
+
|
72
|
+
def _parse_channel_create(self, data: Dict[str, Any]):
|
73
|
+
"""Parses raw CHANNEL_CREATE data into a Channel object."""
|
74
|
+
|
75
|
+
return self._client.parse_channel(data)
|
76
|
+
|
77
|
+
def _parse_presence_update(self, data: Dict[str, Any]):
|
78
|
+
"""Parses raw PRESENCE_UPDATE data into a PresenceUpdate object."""
|
79
|
+
|
80
|
+
from .models import PresenceUpdate
|
81
|
+
|
82
|
+
return PresenceUpdate(data, client_instance=self._client)
|
83
|
+
|
84
|
+
def _parse_typing_start(self, data: Dict[str, Any]):
|
85
|
+
"""Parses raw TYPING_START data into a TypingStart object."""
|
86
|
+
|
87
|
+
from .models import TypingStart
|
88
|
+
|
89
|
+
return TypingStart(data, client_instance=self._client)
|
90
|
+
|
91
|
+
# Potentially add _parse_user for events that directly provide a full user object
|
92
|
+
# def _parse_user_update(self, data: Dict[str, Any]) -> User:
|
93
|
+
# return User(data=data)
|
94
|
+
|
95
|
+
def register(self, event_name: str, coro: EventListener):
|
96
|
+
"""
|
97
|
+
Registers a coroutine function to listen for a specific event.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
event_name (str): The name of the event (e.g., 'MESSAGE_CREATE').
|
101
|
+
coro (Callable): The coroutine function to call when the event occurs.
|
102
|
+
It should accept arguments appropriate for the event.
|
103
|
+
|
104
|
+
Raises:
|
105
|
+
TypeError: If the provided callback is not a coroutine function.
|
106
|
+
"""
|
107
|
+
if not inspect.iscoroutinefunction(coro):
|
108
|
+
raise TypeError(
|
109
|
+
f"Event listener for '{event_name}' must be a coroutine function (async def)."
|
110
|
+
)
|
111
|
+
|
112
|
+
# Normalize event name, e.g., 'on_message' -> 'MESSAGE_CREATE'
|
113
|
+
# For now, we assume event_name is already the Discord event type string.
|
114
|
+
# If using decorators like @client.on_message, the decorator would handle this mapping.
|
115
|
+
self._listeners[event_name.upper()].append(coro)
|
116
|
+
|
117
|
+
def unregister(self, event_name: str, coro: EventListener):
|
118
|
+
"""
|
119
|
+
Unregisters a coroutine function from an event.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
event_name (str): The name of the event.
|
123
|
+
coro (Callable): The coroutine function to unregister.
|
124
|
+
"""
|
125
|
+
event_name_upper = event_name.upper()
|
126
|
+
if event_name_upper in self._listeners:
|
127
|
+
try:
|
128
|
+
self._listeners[event_name_upper].remove(coro)
|
129
|
+
except ValueError:
|
130
|
+
pass # Listener not in list
|
131
|
+
|
132
|
+
def add_waiter(
|
133
|
+
self,
|
134
|
+
event_name: str,
|
135
|
+
future: asyncio.Future,
|
136
|
+
check: Optional[Callable[[Any], bool]] = None,
|
137
|
+
) -> None:
|
138
|
+
self._waiters[event_name.upper()].append((future, check))
|
139
|
+
|
140
|
+
def remove_waiter(self, event_name: str, future: asyncio.Future) -> None:
|
141
|
+
waiters = self._waiters.get(event_name.upper())
|
142
|
+
if not waiters:
|
143
|
+
return
|
144
|
+
self._waiters[event_name.upper()] = [
|
145
|
+
(f, c) for f, c in waiters if f is not future
|
146
|
+
]
|
147
|
+
if not self._waiters[event_name.upper()]:
|
148
|
+
self._waiters.pop(event_name.upper(), None)
|
149
|
+
|
150
|
+
def _resolve_waiters(self, event_name: str, data: Any) -> None:
|
151
|
+
waiters = self._waiters.get(event_name)
|
152
|
+
if not waiters:
|
153
|
+
return
|
154
|
+
to_remove: List[tuple[asyncio.Future, Optional[Callable[[Any], bool]]]] = []
|
155
|
+
for future, check in waiters:
|
156
|
+
if future.cancelled():
|
157
|
+
to_remove.append((future, check))
|
158
|
+
continue
|
159
|
+
try:
|
160
|
+
if check is None or check(data):
|
161
|
+
future.set_result(data)
|
162
|
+
to_remove.append((future, check))
|
163
|
+
except Exception as exc:
|
164
|
+
future.set_exception(exc)
|
165
|
+
to_remove.append((future, check))
|
166
|
+
for item in to_remove:
|
167
|
+
if item in waiters:
|
168
|
+
waiters.remove(item)
|
169
|
+
if not waiters:
|
170
|
+
self._waiters.pop(event_name, None)
|
171
|
+
|
172
|
+
async def dispatch(self, event_name: str, raw_data: Dict[str, Any]):
|
173
|
+
"""
|
174
|
+
Dispatches an event to all registered listeners.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
event_name (str): The name of the event (e.g., 'MESSAGE_CREATE').
|
178
|
+
raw_data (Dict[str, Any]): The raw data payload from the Discord Gateway for this event.
|
179
|
+
"""
|
180
|
+
event_name_upper = event_name.upper()
|
181
|
+
listeners = self._listeners.get(event_name_upper)
|
182
|
+
|
183
|
+
if not listeners:
|
184
|
+
# print(f"No listeners for event {event_name_upper}")
|
185
|
+
return
|
186
|
+
|
187
|
+
parsed_data: Any = raw_data
|
188
|
+
if event_name_upper in self._event_parsers:
|
189
|
+
try:
|
190
|
+
parser = self._event_parsers[event_name_upper]
|
191
|
+
parsed_data = parser(raw_data)
|
192
|
+
except Exception as e:
|
193
|
+
print(f"Error parsing event data for {event_name_upper}: {e}")
|
194
|
+
# Optionally, dispatch with raw_data or raise, or log more formally
|
195
|
+
# For now, we'll proceed to dispatch with raw_data if parsing fails,
|
196
|
+
# or just log and return if parsed_data is critical.
|
197
|
+
# Let's assume if a parser exists, its output is critical.
|
198
|
+
return
|
199
|
+
|
200
|
+
self._resolve_waiters(event_name_upper, parsed_data)
|
201
|
+
# print(f"Dispatching event {event_name_upper} with data: {parsed_data} to {len(listeners)} listeners.")
|
202
|
+
for listener in listeners:
|
203
|
+
try:
|
204
|
+
# Inspect the listener to see how many arguments it expects
|
205
|
+
sig = inspect.signature(listener)
|
206
|
+
num_params = len(sig.parameters)
|
207
|
+
|
208
|
+
if num_params == 0: # Listener takes no arguments
|
209
|
+
await listener()
|
210
|
+
elif (
|
211
|
+
num_params == 1
|
212
|
+
): # Listener takes one argument (the parsed data or model)
|
213
|
+
await listener(parsed_data)
|
214
|
+
# elif num_params == 2 and event_name_upper == "MESSAGE_CREATE": # Special case for (client, message)
|
215
|
+
# await listener(self._client, parsed_data) # This might be too specific here
|
216
|
+
else:
|
217
|
+
# Fallback or error if signature doesn't match expected patterns
|
218
|
+
# For now, assume one arg is the most common for parsed data.
|
219
|
+
# Or, if you want to be strict:
|
220
|
+
print(
|
221
|
+
f"Warning: Listener {listener.__name__} for {event_name_upper} has an unhandled number of parameters ({num_params}). Skipping or attempting with one arg."
|
222
|
+
)
|
223
|
+
if num_params > 0: # Try with one arg if it takes any
|
224
|
+
await listener(parsed_data)
|
225
|
+
|
226
|
+
except Exception as e:
|
227
|
+
callback = self.on_dispatch_error
|
228
|
+
if callback is not None:
|
229
|
+
try:
|
230
|
+
await callback(event_name_upper, e, listener)
|
231
|
+
|
232
|
+
except Exception as hook_error:
|
233
|
+
print(f"Error in on_dispatch_error hook itself: {hook_error}")
|
234
|
+
else:
|
235
|
+
# Default error handling if no hook is set
|
236
|
+
print(
|
237
|
+
f"Error in event listener {listener.__name__} for {event_name_upper}: {e}"
|
238
|
+
)
|
239
|
+
if hasattr(self._client, "on_error"):
|
240
|
+
try:
|
241
|
+
await self._client.on_error(event_name_upper, e, listener)
|
242
|
+
except Exception as client_err_e:
|
243
|
+
print(f"Error in client.on_error itself: {client_err_e}")
|