scurrypy 0.4__py3-none-any.whl → 0.6.6__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.
- scurrypy/__init__.py +429 -0
- scurrypy/client.py +335 -0
- {discord → scurrypy}/client_like.py +8 -1
- scurrypy/dispatch/command_dispatcher.py +205 -0
- {discord → scurrypy}/dispatch/event_dispatcher.py +21 -21
- {discord → scurrypy}/dispatch/prefix_dispatcher.py +31 -12
- {discord → scurrypy}/error.py +6 -18
- {discord → scurrypy}/events/channel_events.py +2 -1
- scurrypy/events/gateway_events.py +31 -0
- {discord → scurrypy}/events/guild_events.py +2 -1
- {discord → scurrypy}/events/interaction_events.py +28 -13
- {discord → scurrypy}/events/message_events.py +8 -5
- {discord → scurrypy}/events/reaction_events.py +1 -2
- {discord → scurrypy}/events/ready_event.py +1 -3
- scurrypy/gateway.py +183 -0
- scurrypy/http.py +310 -0
- {discord → scurrypy}/intents.py +5 -7
- {discord → scurrypy}/logger.py +14 -61
- scurrypy/model.py +71 -0
- scurrypy/models.py +258 -0
- scurrypy/parts/channel.py +42 -0
- scurrypy/parts/command.py +90 -0
- scurrypy/parts/components.py +224 -0
- scurrypy/parts/components_v2.py +144 -0
- scurrypy/parts/embed.py +83 -0
- scurrypy/parts/message.py +134 -0
- scurrypy/parts/modal.py +16 -0
- {discord → scurrypy}/parts/role.py +2 -14
- {discord → scurrypy}/resources/application.py +1 -2
- {discord → scurrypy}/resources/bot_emojis.py +1 -1
- {discord → scurrypy}/resources/channel.py +9 -8
- {discord → scurrypy}/resources/guild.py +14 -16
- {discord → scurrypy}/resources/interaction.py +50 -43
- {discord → scurrypy}/resources/message.py +15 -16
- {discord → scurrypy}/resources/user.py +3 -4
- scurrypy-0.6.6.dist-info/METADATA +108 -0
- scurrypy-0.6.6.dist-info/RECORD +47 -0
- {scurrypy-0.4.dist-info → scurrypy-0.6.6.dist-info}/licenses/LICENSE +1 -1
- scurrypy-0.6.6.dist-info/top_level.txt +1 -0
- discord/__init__.py +0 -223
- discord/client.py +0 -375
- discord/dispatch/command_dispatcher.py +0 -163
- discord/gateway.py +0 -155
- discord/http.py +0 -280
- discord/model.py +0 -90
- discord/models/__init__.py +0 -1
- discord/models/application.py +0 -37
- discord/models/emoji.py +0 -34
- discord/models/guild.py +0 -35
- discord/models/integration.py +0 -23
- discord/models/interaction.py +0 -26
- discord/models/member.py +0 -27
- discord/models/role.py +0 -53
- discord/models/user.py +0 -15
- discord/parts/action_row.py +0 -208
- discord/parts/channel.py +0 -20
- discord/parts/command.py +0 -102
- discord/parts/components_v2.py +0 -353
- discord/parts/embed.py +0 -154
- discord/parts/message.py +0 -194
- discord/parts/modal.py +0 -21
- scurrypy-0.4.dist-info/METADATA +0 -130
- scurrypy-0.4.dist-info/RECORD +0 -54
- scurrypy-0.4.dist-info/top_level.txt +0 -1
- {discord → scurrypy}/config.py +0 -0
- {discord → scurrypy}/dispatch/__init__.py +0 -0
- {discord → scurrypy}/events/__init__.py +0 -0
- {discord → scurrypy}/events/hello_event.py +0 -0
- {discord → scurrypy}/parts/__init__.py +0 -0
- {discord → scurrypy}/parts/component_types.py +0 -0
- {discord → scurrypy}/resources/__init__.py +0 -0
- {scurrypy-0.4.dist-info → scurrypy-0.6.6.dist-info}/WHEEL +0 -0
discord/http.py
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import aiohttp
|
|
2
|
-
import aiofiles
|
|
3
|
-
import asyncio
|
|
4
|
-
import time
|
|
5
|
-
import json
|
|
6
|
-
import ssl
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
from urllib.parse import urlencode
|
|
9
|
-
|
|
10
|
-
from .logger import Logger
|
|
11
|
-
from .error import DiscordError
|
|
12
|
-
|
|
13
|
-
ssl_ctx = ssl.create_default_context()
|
|
14
|
-
ssl_ctx.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 # Disable old SSL
|
|
15
|
-
|
|
16
|
-
DISCORD_HTTP_CODES = {
|
|
17
|
-
200: "Successful Request",
|
|
18
|
-
201: "Successful Creation",
|
|
19
|
-
204: "No Content",
|
|
20
|
-
304: "Not Modified",
|
|
21
|
-
400: "Bad Request",
|
|
22
|
-
401: "Missing Authorization",
|
|
23
|
-
403: "Missing Permission",
|
|
24
|
-
404: "Resource Not Found",
|
|
25
|
-
405: "Invalid Method",
|
|
26
|
-
429: "Rate Limited",
|
|
27
|
-
502: "Gateway Unavailable"
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
@dataclass
|
|
31
|
-
class RequestItem:
|
|
32
|
-
"""Data container representing an HTTP request to Discord's API.
|
|
33
|
-
Used internally by HTTPClient for queuing and processing requests.
|
|
34
|
-
"""
|
|
35
|
-
method: str
|
|
36
|
-
"""HTTP method (e.g., GET, POST, DELETE, PUT, PATCH)"""
|
|
37
|
-
|
|
38
|
-
url: str
|
|
39
|
-
"""Fully qualifying URL for this request."""
|
|
40
|
-
|
|
41
|
-
endpoint: str
|
|
42
|
-
"""Endpoint of the URL for this request."""
|
|
43
|
-
|
|
44
|
-
data: dict | None
|
|
45
|
-
"""Relevant data for this request."""
|
|
46
|
-
|
|
47
|
-
files: list | None
|
|
48
|
-
"""Relevant files for this request."""
|
|
49
|
-
|
|
50
|
-
future: asyncio.Future
|
|
51
|
-
"""Track the result of this request."""
|
|
52
|
-
|
|
53
|
-
class RouteQueue:
|
|
54
|
-
"""Represents a queue of requests for a single rate-limit bucket.
|
|
55
|
-
Manages task worker that processes requests for that bucket.
|
|
56
|
-
"""
|
|
57
|
-
def __init__(self):
|
|
58
|
-
self.queue = asyncio.Queue()
|
|
59
|
-
"""Queue holding RequestItem for this bucket."""
|
|
60
|
-
|
|
61
|
-
self.worker = None
|
|
62
|
-
"""Process for executing request for this bucket."""
|
|
63
|
-
|
|
64
|
-
class HTTPClient:
|
|
65
|
-
"""Handles all HTTP communication with Discord's API
|
|
66
|
-
including rate-limiting by bucket and globally, async request handling,
|
|
67
|
-
multipart/form-data file uploads, and error handling/retries.
|
|
68
|
-
"""
|
|
69
|
-
def __init__(self, token: str, logger: Logger):
|
|
70
|
-
self.token = token
|
|
71
|
-
"""The bot's token."""
|
|
72
|
-
|
|
73
|
-
self._logger = logger
|
|
74
|
-
"""Logger instance to log events."""
|
|
75
|
-
|
|
76
|
-
self.session: aiohttp.ClientSession = None
|
|
77
|
-
"""Client session instance."""
|
|
78
|
-
|
|
79
|
-
self.global_reset = 0
|
|
80
|
-
"""Global rete limit cooldown if a global rate limit is active."""
|
|
81
|
-
|
|
82
|
-
self.global_lock: asyncio.Lock = None
|
|
83
|
-
"""Lock on queues to avoid race conditions."""
|
|
84
|
-
|
|
85
|
-
self.pending_queue: asyncio.Queue = None
|
|
86
|
-
"""Queue for requests not yet assigned to bucket."""
|
|
87
|
-
|
|
88
|
-
self.pending_worker: asyncio.Task = None
|
|
89
|
-
"""Task processing for the pending queue."""
|
|
90
|
-
|
|
91
|
-
self.endpoint_to_bucket: dict[str, str] = {}
|
|
92
|
-
"""Maps endpoints to rate-limit buckets"""
|
|
93
|
-
|
|
94
|
-
self.bucket_queues: dict[str, RouteQueue] = {}
|
|
95
|
-
"""Maps endpoints to RouteQueue objects."""
|
|
96
|
-
|
|
97
|
-
self._sentinel = object()
|
|
98
|
-
"""Sentinel to terminate session."""
|
|
99
|
-
|
|
100
|
-
self.base_url = "https://discord.com/api/v10"
|
|
101
|
-
"""Base URL for discord's API requests."""
|
|
102
|
-
|
|
103
|
-
async def start_session(self):
|
|
104
|
-
"""Initializes aiohttp session, queues, locks, and starting pending worker."""
|
|
105
|
-
self.session = aiohttp.ClientSession()
|
|
106
|
-
self.pending_queue = asyncio.Queue()
|
|
107
|
-
self.global_lock = asyncio.Lock()
|
|
108
|
-
self.pending_worker = asyncio.create_task(self._pending_worker())
|
|
109
|
-
self._logger.log_debug("Session started.")
|
|
110
|
-
|
|
111
|
-
async def request(self, method: str, endpoint: str, data=None, params=None, files=None):
|
|
112
|
-
"""Enqueues request WRT rate-limit buckets.
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
method (str): HTTP method (e.g., POST, GET, DELETE, PATCH, etc.)
|
|
116
|
-
endpoint (str): Discord endpoint (e.g., /channels/123/messages)
|
|
117
|
-
data (dict, optional): relevant data
|
|
118
|
-
files (list[str], optional): relevant files
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
(Future): future with response
|
|
122
|
-
"""
|
|
123
|
-
url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}" # normalize for single slashes
|
|
124
|
-
if params:
|
|
125
|
-
url += f"?{urlencode(params)}"
|
|
126
|
-
|
|
127
|
-
future = asyncio.get_event_loop().create_future()
|
|
128
|
-
|
|
129
|
-
if endpoint in self.endpoint_to_bucket:
|
|
130
|
-
bucket = self.endpoint_to_bucket[endpoint]
|
|
131
|
-
if bucket not in self.bucket_queues:
|
|
132
|
-
self.bucket_queues[bucket] = RouteQueue()
|
|
133
|
-
self.bucket_queues[bucket].worker = asyncio.create_task(
|
|
134
|
-
self._route_worker(bucket)
|
|
135
|
-
)
|
|
136
|
-
await self.bucket_queues[bucket].queue.put(RequestItem(method, url, endpoint, data, files, future))
|
|
137
|
-
else:
|
|
138
|
-
await self.pending_queue.put(RequestItem(method, url, endpoint, data, files, future))
|
|
139
|
-
|
|
140
|
-
return await future
|
|
141
|
-
|
|
142
|
-
async def _pending_worker(self):
|
|
143
|
-
"""Processes requests from global pending queue."""
|
|
144
|
-
while True:
|
|
145
|
-
item = await self.pending_queue.get()
|
|
146
|
-
if item is self._sentinel:
|
|
147
|
-
self.pending_queue.task_done()
|
|
148
|
-
break
|
|
149
|
-
await self._process_request(item)
|
|
150
|
-
self.pending_queue.task_done()
|
|
151
|
-
|
|
152
|
-
async def _route_worker(self, bucket: str):
|
|
153
|
-
"""Processes request from specific rate-limit bucket.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
bucket (str): endpoint
|
|
157
|
-
"""
|
|
158
|
-
queue = self.bucket_queues[bucket].queue
|
|
159
|
-
while True:
|
|
160
|
-
item = await queue.get()
|
|
161
|
-
if item is self._sentinel:
|
|
162
|
-
queue.task_done()
|
|
163
|
-
break
|
|
164
|
-
await self._process_request(item)
|
|
165
|
-
queue.task_done()
|
|
166
|
-
|
|
167
|
-
async def _process_request(self, item: RequestItem):
|
|
168
|
-
"""Core request execution. Handles headers, payload, files, retries, and bucket assignment.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
item (RequestItem): incoming request
|
|
172
|
-
|
|
173
|
-
Raises:
|
|
174
|
-
DiscordError: discord error object
|
|
175
|
-
"""
|
|
176
|
-
try:
|
|
177
|
-
await self._check_global_limit()
|
|
178
|
-
|
|
179
|
-
headers = {"Authorization": f"Bot {self.token}"}
|
|
180
|
-
|
|
181
|
-
# Build multipart if files exist
|
|
182
|
-
request_kwargs = {'headers': headers, 'ssl': ssl_ctx}
|
|
183
|
-
|
|
184
|
-
if item.files: # only create FormData if files exist
|
|
185
|
-
form = aiohttp.FormData()
|
|
186
|
-
form.add_field('payload_json', json.dumps(item.data))
|
|
187
|
-
|
|
188
|
-
for idx, file_path in enumerate(item.files):
|
|
189
|
-
try:
|
|
190
|
-
async with aiofiles.open(file_path, 'rb') as f:
|
|
191
|
-
data = await f.read()
|
|
192
|
-
form.add_field(
|
|
193
|
-
f'files[{idx}]',
|
|
194
|
-
data,
|
|
195
|
-
filename=file_path.split('/')[-1],
|
|
196
|
-
content_type='application/octet-stream'
|
|
197
|
-
)
|
|
198
|
-
except FileNotFoundError:
|
|
199
|
-
self._logger.log_warn(f"File '{file_path}' could not be found.")
|
|
200
|
-
break
|
|
201
|
-
|
|
202
|
-
request_kwargs['data'] = form
|
|
203
|
-
|
|
204
|
-
elif item.data is not None:
|
|
205
|
-
request_kwargs['json'] = item.data # aiohttp sets Content-Type automatically
|
|
206
|
-
|
|
207
|
-
async with self.session.request(item.method, item.url, **request_kwargs) as resp:
|
|
208
|
-
self._logger.log_debug(f"{item.method} {item.endpoint}: {resp.status} {DISCORD_HTTP_CODES.get(resp.status, 'Unknown Status')}")
|
|
209
|
-
|
|
210
|
-
# if triggered rate-limit
|
|
211
|
-
if resp.status == 429:
|
|
212
|
-
data = await resp.json()
|
|
213
|
-
retry_after = float(data.get("retry_after", 1))
|
|
214
|
-
is_global = data.get("global")
|
|
215
|
-
if is_global:
|
|
216
|
-
self.global_reset = time.time() + retry_after
|
|
217
|
-
|
|
218
|
-
try:
|
|
219
|
-
self._logger.log_warn( f"You are being rate limited on {item.endpoint}. Retrying in {retry_after}s (global={is_global})")
|
|
220
|
-
await asyncio.sleep(retry_after + 0.5)
|
|
221
|
-
except asyncio.CancelledError:
|
|
222
|
-
# shutdown is happening
|
|
223
|
-
raise
|
|
224
|
-
|
|
225
|
-
# retry the request
|
|
226
|
-
await self._process_request(item)
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
# if response failed (either lethal or recoverable)
|
|
230
|
-
elif resp.status not in [200, 201, 204]:
|
|
231
|
-
raise DiscordError(resp.status, await resp.json())
|
|
232
|
-
|
|
233
|
-
await self._handle_response(item, resp)
|
|
234
|
-
|
|
235
|
-
# Handle rate-limit bucket headers
|
|
236
|
-
bucket = resp.headers.get("X-RateLimit-Bucket")
|
|
237
|
-
if bucket and item.endpoint not in self.endpoint_to_bucket:
|
|
238
|
-
self.endpoint_to_bucket[item.endpoint] = bucket
|
|
239
|
-
if bucket not in self.bucket_queues:
|
|
240
|
-
self.bucket_queues[bucket] = RouteQueue()
|
|
241
|
-
self.bucket_queues[bucket].worker = asyncio.create_task(
|
|
242
|
-
self._route_worker(bucket)
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
except Exception as e:
|
|
246
|
-
if not item.future.done():
|
|
247
|
-
item.future.set_exception(e)
|
|
248
|
-
|
|
249
|
-
async def _handle_response(self, item: RequestItem, resp: aiohttp.ClientResponse):
|
|
250
|
-
"""Resolves future with parsed JSON/text response.
|
|
251
|
-
|
|
252
|
-
Args:
|
|
253
|
-
item (RequestItem): request data to handle
|
|
254
|
-
resp (aiohttp.ClientResponse): response for item
|
|
255
|
-
"""
|
|
256
|
-
if resp.status == 204:
|
|
257
|
-
item.future.set_result((None, 204))
|
|
258
|
-
else:
|
|
259
|
-
try:
|
|
260
|
-
result = await resp.json()
|
|
261
|
-
except aiohttp.ContentTypeError:
|
|
262
|
-
result = await resp.text()
|
|
263
|
-
item.future.set_result(result)
|
|
264
|
-
|
|
265
|
-
async def _check_global_limit(self):
|
|
266
|
-
"""Waits if the global rate-limit is in effect."""
|
|
267
|
-
async with self.global_lock:
|
|
268
|
-
now = time.time()
|
|
269
|
-
if now < self.global_reset:
|
|
270
|
-
await asyncio.sleep(self.global_reset - now)
|
|
271
|
-
|
|
272
|
-
async def close_session(self):
|
|
273
|
-
"""Gracefully shuts down all workes and closes aiohttp session."""
|
|
274
|
-
# Stop workers
|
|
275
|
-
for q in self.bucket_queues.values():
|
|
276
|
-
await q.queue.put(self._sentinel)
|
|
277
|
-
await self.pending_queue.put(self._sentinel)
|
|
278
|
-
|
|
279
|
-
if self.session and not self.session.closed:
|
|
280
|
-
await self.session.close()
|
discord/model.py
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass, fields, is_dataclass
|
|
2
|
-
from typing import get_args, get_origin, Union
|
|
3
|
-
|
|
4
|
-
from .http import HTTPClient
|
|
5
|
-
|
|
6
|
-
@dataclass
|
|
7
|
-
class DataModel:
|
|
8
|
-
"""DataModel is a base class for Discord JSONs that provides hydration from raw dicts,
|
|
9
|
-
optional field defaults, and access to HTTP-bound methods.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
@classmethod
|
|
13
|
-
def from_dict(cls, data: dict, http: HTTPClient = None):
|
|
14
|
-
"""Hydrates the given data into the dataclass child.
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
data (dict): JSON data
|
|
18
|
-
http (HTTPClient, optional): HTTP session for requests
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
(dataclass): hydrated dataclass
|
|
22
|
-
"""
|
|
23
|
-
kwargs = {}
|
|
24
|
-
|
|
25
|
-
def unwrap_optional(typ):
|
|
26
|
-
"""Remove NoneType from Optional or leave Union as-is."""
|
|
27
|
-
if get_origin(typ) is Union:
|
|
28
|
-
args = tuple(a for a in get_args(typ) if a is not type(None))
|
|
29
|
-
if len(args) == 1:
|
|
30
|
-
return args[0] # single type left
|
|
31
|
-
else:
|
|
32
|
-
return Union[args] # multi-type union remains
|
|
33
|
-
return typ
|
|
34
|
-
|
|
35
|
-
for field in fields(cls):
|
|
36
|
-
# property must be in given json!
|
|
37
|
-
value = data.get(field.name)
|
|
38
|
-
|
|
39
|
-
inner_type = unwrap_optional(field.type)
|
|
40
|
-
|
|
41
|
-
# Handle None
|
|
42
|
-
if value is None:
|
|
43
|
-
kwargs[field.name] = None
|
|
44
|
-
# Integers stored as strings
|
|
45
|
-
elif isinstance(value, str) and value.isdigit():
|
|
46
|
-
kwargs[field.name] = int(value)
|
|
47
|
-
# Nested dataclass
|
|
48
|
-
elif is_dataclass(inner_type):
|
|
49
|
-
kwargs[field.name] = inner_type.from_dict(value, http)
|
|
50
|
-
# List type
|
|
51
|
-
elif get_origin(inner_type) is list:
|
|
52
|
-
list_type = get_args(inner_type)[0]
|
|
53
|
-
kwargs[field.name] = [
|
|
54
|
-
list_type.from_dict(v, http) if is_dataclass(list_type) else v
|
|
55
|
-
for v in value
|
|
56
|
-
]
|
|
57
|
-
# Everything else (primitive, Union of primitives)
|
|
58
|
-
else:
|
|
59
|
-
kwargs[field.name] = value
|
|
60
|
-
|
|
61
|
-
instance = cls(**kwargs)
|
|
62
|
-
|
|
63
|
-
# attach HTTP if given
|
|
64
|
-
if http:
|
|
65
|
-
instance._http = http
|
|
66
|
-
|
|
67
|
-
return instance
|
|
68
|
-
|
|
69
|
-
def _to_dict(self):
|
|
70
|
-
"""Recursively turns the dataclass into a dictionary and drops empty fields.
|
|
71
|
-
|
|
72
|
-
Returns:
|
|
73
|
-
(dict): serialized dataclasss
|
|
74
|
-
"""
|
|
75
|
-
def serialize(value):
|
|
76
|
-
if isinstance(value, list):
|
|
77
|
-
return [serialize(v) for v in value]
|
|
78
|
-
if isinstance(value, DataModel):
|
|
79
|
-
return value._to_dict()
|
|
80
|
-
return value
|
|
81
|
-
|
|
82
|
-
result = {}
|
|
83
|
-
for f in fields(self):
|
|
84
|
-
if f.name.startswith('_'): # ignore private fields
|
|
85
|
-
continue
|
|
86
|
-
value = getattr(self, f.name)
|
|
87
|
-
if value not in (None, [], {}, "", 0):
|
|
88
|
-
result[f.name] = serialize(value)
|
|
89
|
-
|
|
90
|
-
return result
|
discord/models/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# discord/models
|
discord/models/application.py
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from ..model import DataModel
|
|
3
|
-
from .user import UserModel
|
|
4
|
-
from .guild import GuildModel
|
|
5
|
-
|
|
6
|
-
@dataclass
|
|
7
|
-
class ApplicationModel(DataModel):
|
|
8
|
-
"""Represents a bot application object."""
|
|
9
|
-
id: int
|
|
10
|
-
"""ID of the app."""
|
|
11
|
-
|
|
12
|
-
name: str
|
|
13
|
-
"""Name of the app."""
|
|
14
|
-
|
|
15
|
-
icon: str
|
|
16
|
-
"""Icon hash of the app."""
|
|
17
|
-
|
|
18
|
-
description: str
|
|
19
|
-
"""Description of the app."""
|
|
20
|
-
|
|
21
|
-
bot_public: bool
|
|
22
|
-
"""If other users can add this app to a guild."""
|
|
23
|
-
|
|
24
|
-
bot: UserModel
|
|
25
|
-
"""Partial user obhect for the bot user associated with the app."""
|
|
26
|
-
|
|
27
|
-
owner: UserModel
|
|
28
|
-
"""Partial user object for the owner of the app."""
|
|
29
|
-
|
|
30
|
-
guild_id: int
|
|
31
|
-
"""Guild ID associated with the app (e.g., a support server)."""
|
|
32
|
-
|
|
33
|
-
guild: GuildModel
|
|
34
|
-
"""Partial guild object of the associated guild."""
|
|
35
|
-
|
|
36
|
-
approximate_guild_count: int
|
|
37
|
-
"""Approximate guild member count."""
|
discord/models/emoji.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from ..model import DataModel
|
|
3
|
-
|
|
4
|
-
from urllib.parse import quote
|
|
5
|
-
|
|
6
|
-
@dataclass
|
|
7
|
-
class EmojiModel(DataModel):
|
|
8
|
-
"""Represents a Discord emoji."""
|
|
9
|
-
name: str
|
|
10
|
-
"""Name of emoji."""
|
|
11
|
-
|
|
12
|
-
id: int = 0
|
|
13
|
-
"""ID of the emoji (if custom)."""
|
|
14
|
-
|
|
15
|
-
animated: bool = False
|
|
16
|
-
"""If the emoji is animated. Defaults to `False`."""
|
|
17
|
-
|
|
18
|
-
@property
|
|
19
|
-
def mention(self) -> str:
|
|
20
|
-
"""For use in message content."""
|
|
21
|
-
return f"<a:{self.name}:{self.id}>" if self.animated else f"<:{self.name}:{self.id}>"
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def api_code(self) -> str:
|
|
25
|
-
"""Return the correct API code for this emoji (URL-safe)."""
|
|
26
|
-
if not self.id:
|
|
27
|
-
# unicode emoji
|
|
28
|
-
return quote(self.name)
|
|
29
|
-
|
|
30
|
-
# custom emoji
|
|
31
|
-
if self.animated:
|
|
32
|
-
return quote(f"a:{self.name}:{self.id}")
|
|
33
|
-
|
|
34
|
-
return quote(f"{self.name}:{self.id}")
|
discord/models/guild.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
3
|
-
from ..model import DataModel
|
|
4
|
-
from .emoji import EmojiModel
|
|
5
|
-
|
|
6
|
-
@dataclass
|
|
7
|
-
class ReadyGuildModel(DataModel):
|
|
8
|
-
"""Guild info from Ready event."""
|
|
9
|
-
id: int
|
|
10
|
-
"""ID of the associated guild."""
|
|
11
|
-
|
|
12
|
-
unavailable: bool
|
|
13
|
-
"""If the guild is offline."""
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class GuildModel(DataModel):
|
|
17
|
-
"""Represents a Discord guild."""
|
|
18
|
-
|
|
19
|
-
id: int
|
|
20
|
-
"""ID of the guild."""
|
|
21
|
-
|
|
22
|
-
name: str
|
|
23
|
-
"""Name of the guild."""
|
|
24
|
-
|
|
25
|
-
icon: Optional[str] = None
|
|
26
|
-
"""Icon hash of the guild."""
|
|
27
|
-
|
|
28
|
-
emojis: list[EmojiModel] = None
|
|
29
|
-
"""List of emojis reigstered in the guild."""
|
|
30
|
-
|
|
31
|
-
approximate_member_count: Optional[int] = None
|
|
32
|
-
"""Approximate member count."""
|
|
33
|
-
|
|
34
|
-
description: str = None
|
|
35
|
-
"""Description of the guild."""
|
discord/models/integration.py
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
3
|
-
from ..model import DataModel
|
|
4
|
-
from .application import ApplicationModel
|
|
5
|
-
|
|
6
|
-
@dataclass
|
|
7
|
-
class IntegrationModel(DataModel):
|
|
8
|
-
"""Represents a guild integration."""
|
|
9
|
-
|
|
10
|
-
id: int
|
|
11
|
-
"""ID of the integration."""
|
|
12
|
-
|
|
13
|
-
name: str
|
|
14
|
-
"""Name of the integration."""
|
|
15
|
-
|
|
16
|
-
type: str
|
|
17
|
-
"""Type of integration (e.g., twitch, youtube, discord, or guild_subscription)."""
|
|
18
|
-
|
|
19
|
-
enabled: bool
|
|
20
|
-
"""If the integration is enabled."""
|
|
21
|
-
|
|
22
|
-
application: Optional[ApplicationModel] = None
|
|
23
|
-
"""The bot aaplication for Discord integrations."""
|
discord/models/interaction.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from ..model import DataModel
|
|
3
|
-
|
|
4
|
-
@dataclass
|
|
5
|
-
class InteractionCallbackDataModel(DataModel):
|
|
6
|
-
id: int
|
|
7
|
-
"""ID of the interaction."""
|
|
8
|
-
|
|
9
|
-
type: int
|
|
10
|
-
"""Type of interaction."""
|
|
11
|
-
|
|
12
|
-
activity_instance_id: str
|
|
13
|
-
"""Instance ID of activity if an activity was launched or joined."""
|
|
14
|
-
|
|
15
|
-
response_message_id: int
|
|
16
|
-
"""ID of the message created by the interaction."""
|
|
17
|
-
|
|
18
|
-
response_message_loading: bool
|
|
19
|
-
"""If the interaction is in a loading state."""
|
|
20
|
-
|
|
21
|
-
response_message_ephemeral: bool
|
|
22
|
-
"""If the interaction is ephemeral."""
|
|
23
|
-
|
|
24
|
-
@dataclass
|
|
25
|
-
class InteractionCallbackModel(DataModel):
|
|
26
|
-
interaction: InteractionCallbackDataModel
|
discord/models/member.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from ..model import DataModel
|
|
3
|
-
from ..models.user import UserModel
|
|
4
|
-
|
|
5
|
-
@dataclass
|
|
6
|
-
class MemberModel(DataModel):
|
|
7
|
-
"""Represents a guild member."""
|
|
8
|
-
|
|
9
|
-
roles: list[int]
|
|
10
|
-
"""List of roles registered to the guild member."""
|
|
11
|
-
|
|
12
|
-
user: UserModel
|
|
13
|
-
"""User data associated with the guild member."""
|
|
14
|
-
|
|
15
|
-
nick: str
|
|
16
|
-
"""Server nickname of the guild member."""
|
|
17
|
-
|
|
18
|
-
avatar: str
|
|
19
|
-
"""Server avatar hash of the guild mmeber."""
|
|
20
|
-
|
|
21
|
-
joined_at: str
|
|
22
|
-
"""ISO8601 timestamp of when the guild member joined server."""
|
|
23
|
-
deaf: bool
|
|
24
|
-
"""If the member is deaf in a VC (input)."""
|
|
25
|
-
|
|
26
|
-
mute: bool
|
|
27
|
-
"""If the member is muted in VC (output)."""
|
discord/models/role.py
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
3
|
-
from ..model import DataModel
|
|
4
|
-
|
|
5
|
-
@dataclass
|
|
6
|
-
class RoleColors(DataModel):
|
|
7
|
-
"""Role color data."""
|
|
8
|
-
|
|
9
|
-
primary_color: int
|
|
10
|
-
"""Primary color of the role."""
|
|
11
|
-
|
|
12
|
-
secondary_color: int
|
|
13
|
-
"""Secondary color of the role. Creates a gradient."""
|
|
14
|
-
|
|
15
|
-
tertiary_color: int
|
|
16
|
-
"""Tertiary color of the role. Creates a holographic style."""
|
|
17
|
-
|
|
18
|
-
@dataclass
|
|
19
|
-
class RoleModel(DataModel):
|
|
20
|
-
"""Represents a Discord role."""
|
|
21
|
-
|
|
22
|
-
id: int
|
|
23
|
-
"""ID of the role."""
|
|
24
|
-
|
|
25
|
-
name: str
|
|
26
|
-
"""Name of the role."""
|
|
27
|
-
|
|
28
|
-
colors: RoleColors
|
|
29
|
-
"""Colors of the role."""
|
|
30
|
-
|
|
31
|
-
hoist: bool
|
|
32
|
-
"""If the role is pinned in user listing."""
|
|
33
|
-
|
|
34
|
-
position: int
|
|
35
|
-
"""Position of the role."""
|
|
36
|
-
|
|
37
|
-
permissions: str
|
|
38
|
-
"""Permission bit set."""
|
|
39
|
-
|
|
40
|
-
managed: bool
|
|
41
|
-
"""If the role is managed by an integration."""
|
|
42
|
-
|
|
43
|
-
mentionable: bool
|
|
44
|
-
"""If the role is mentionable."""
|
|
45
|
-
|
|
46
|
-
flags: int
|
|
47
|
-
"""Role flags combined as a bitfield."""
|
|
48
|
-
|
|
49
|
-
icon: Optional[str] = None
|
|
50
|
-
"""Icon hash of the role."""
|
|
51
|
-
|
|
52
|
-
unicode_emoji: Optional[str] = None
|
|
53
|
-
"""Unicode emoji of the role."""
|
discord/models/user.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
|
|
3
|
-
from ..model import DataModel
|
|
4
|
-
|
|
5
|
-
@dataclass
|
|
6
|
-
class UserModel(DataModel):
|
|
7
|
-
"""Describes the User object."""
|
|
8
|
-
id: int
|
|
9
|
-
"""ID of the user."""
|
|
10
|
-
|
|
11
|
-
username: str
|
|
12
|
-
"""Username of the user."""
|
|
13
|
-
|
|
14
|
-
avatar: str
|
|
15
|
-
"""Avatar hash of the user."""
|