1 | # Copyright Pincer 2021-Present |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
2 | # Full MIT License can be found in `LICENSE` at the project root. |
||
3 | |||
4 | from __future__ import annotations |
||
5 | |||
6 | import logging |
||
7 | import signal |
||
8 | from asyncio import ( |
||
9 | iscoroutinefunction, |
||
10 | ensure_future, |
||
11 | create_task, |
||
12 | get_event_loop, |
||
13 | ) |
||
14 | from collections import defaultdict |
||
15 | from functools import partial |
||
16 | from inspect import isasyncgenfunction |
||
17 | from typing import ( |
||
18 | Any, |
||
19 | Dict, |
||
20 | List, |
||
21 | Optional, |
||
22 | Iterable, |
||
23 | OrderedDict, |
||
24 | Tuple, |
||
25 | Union, |
||
26 | overload, |
||
27 | TYPE_CHECKING, |
||
28 | ) |
||
29 | |||
30 | |||
31 | from .cog import CogManager |
||
32 | from .commands.interactable import Interactable |
||
33 | from .objects.app.command import InteractableStructure |
||
34 | |||
35 | from . import __package__ |
||
36 | from .commands import ChatCommandHandler |
||
37 | from .core import HTTPClient |
||
38 | from .core.gateway import GatewayInfo, Gateway |
||
39 | |||
40 | from .exceptions import InvalidEventName, GatewayConnectionError |
||
41 | |||
42 | from .middleware import middleware |
||
43 | from .objects import ( |
||
44 | Role, |
||
45 | Channel, |
||
46 | DefaultThrottleHandler, |
||
47 | User, |
||
48 | Guild, |
||
49 | Intents, |
||
50 | GuildTemplate, |
||
51 | StickerPack, |
||
52 | UserMessage, |
||
53 | Connection, |
||
54 | File, |
||
55 | StageInstance, |
||
56 | PrivacyLevel, |
||
57 | ) |
||
58 | from .objects.guild.channel import GroupDMChannel |
||
59 | from .utils import APIObject |
||
60 | from .utils.conversion import remove_none |
||
61 | from .utils.event_mgr import EventMgr |
||
62 | from .utils.extraction import get_index |
||
63 | from .utils.insertion import should_pass_cls, should_pass_gateway |
||
64 | from .utils.shards import calculate_shard_id |
||
65 | from .utils.types import CheckFunction |
||
66 | from .utils.types import Coro |
||
67 | |||
68 | if TYPE_CHECKING: |
||
69 | from .utils.snowflake import Snowflake |
||
70 | from .core.dispatch import GatewayDispatch |
||
71 | from .objects.app.throttling import ThrottleInterface |
||
72 | from .objects.guild import Webhook |
||
73 | |||
74 | from collections.abc import AsyncIterator |
||
0 ignored issues
–
show
|
|||
75 | |||
76 | _log = logging.getLogger(__package__) |
||
77 | |||
78 | MiddlewareType = Optional[Union[Coro, Tuple[str, List[Any], Dict[str, Any]]]] |
||
79 | |||
80 | _event = Union[str, InteractableStructure[None]] |
||
81 | _events: Dict[str, Optional[Union[List[_event], _event]]] = defaultdict(list) |
||
82 | |||
83 | |||
84 | def event_middleware(call: str, *, override: bool = False): |
||
85 | """Middleware are methods which can be registered with this decorator. |
||
86 | These methods are invoked before any ``on_`` event. |
||
87 | As the ``on_`` event is the final call. |
||
88 | |||
89 | A default call exists for all events, but some might already be in |
||
90 | use by the library. |
||
91 | |||
92 | If you know what you are doing, you can override these default |
||
93 | middleware methods by passing the override parameter. |
||
94 | |||
95 | The method to which this decorator is registered must be a coroutine, |
||
96 | and it must return a tuple with the following format: |
||
97 | |||
98 | .. code-block:: python |
||
99 | |||
100 | tuple( |
||
101 | key for next middleware or final event [str], |
||
102 | args for next middleware/event which will be passed as *args |
||
103 | [list(Any)], |
||
104 | kwargs for next middleware/event which will be passed as |
||
105 | **kwargs [dict(Any)] |
||
106 | ) |
||
107 | |||
108 | Two parameters are passed to the middleware. The first parameter is |
||
109 | the current socket connection with and the second one is the payload |
||
110 | parameter which is of type :class:`~.core.dispatch.GatewayDispatch`. |
||
111 | This contains the response from the discord API. |
||
112 | |||
113 | :Implementation example: |
||
114 | |||
115 | .. code-block:: python3 |
||
116 | |||
117 | @event_middleware("ready", override=True) |
||
118 | async def custom_ready(_, payload: GatewayDispatch): |
||
119 | return "on_ready", [ |
||
120 | User.from_dict(payload.data.get("user")) |
||
121 | ] |
||
122 | |||
123 | @Client.event |
||
124 | async def on_ready(bot: User): |
||
125 | print(f"Signed in as {bot}") |
||
126 | |||
127 | Parameters |
||
128 | ---------- |
||
129 | call : :class:`str` |
||
130 | The call that the function should tie to |
||
131 | override : :class:`bool` |
||
132 | If it should override default middleware, |
||
133 | usually shouldn't be used |default| :data:`False` |
||
134 | """ |
||
135 | |||
136 | def decorator(func: Coro): |
||
137 | if override: |
||
138 | _log.warning( |
||
139 | "Middleware overriding has been enabled for `%s`." |
||
140 | " This might cause unexpected behavior.", |
||
141 | call, |
||
142 | ) |
||
143 | |||
144 | if not override and callable(_events.get(call)): |
||
145 | raise RuntimeError( |
||
146 | f"Middleware event with call `{call}` has " |
||
147 | "already been registered" |
||
148 | ) |
||
149 | |||
150 | async def wrapper(cls, gateway: Gateway, payload: GatewayDispatch): |
||
151 | _log.debug("`%s` middleware has been invoked", call) |
||
152 | |||
153 | return await func(cls, gateway, payload) |
||
154 | |||
155 | _events[call] = wrapper |
||
156 | return wrapper |
||
157 | |||
158 | return decorator |
||
159 | |||
160 | |||
161 | for event, middleware_ in middleware.items(): |
||
162 | event_middleware(event)(middleware_) |
||
163 | |||
164 | |||
165 | class Client(Interactable, CogManager): |
||
0 ignored issues
–
show
|
|||
166 | """The client is the main instance which is between the programmer |
||
167 | and the discord API. |
||
168 | |||
169 | This client represents your bot. |
||
170 | |||
171 | Attributes |
||
172 | ---------- |
||
173 | bot: :class:`~objects.user.user.User` |
||
174 | The user object of the client |
||
175 | received_message: :class:`str` |
||
176 | The default message which will be sent when no response is given. |
||
177 | http: :class:`~core.http.HTTPClient` |
||
178 | The http client used to communicate with the discord API |
||
179 | |||
180 | Parameters |
||
181 | ---------- |
||
182 | token : :class:`str` |
||
183 | The token to login with your bot from the developer portal |
||
184 | received : Optional[:class:`str`] |
||
185 | The default message which will be sent when no response is given. |
||
186 | |default| :data:`None` |
||
187 | intents : Optional[Union[Iterable[:class:`~objects.app.intents.Intents`], :class:`~objects.app.intents.Intents`]] |
||
0 ignored issues
–
show
|
|||
188 | The discord intents to use |default| :data:`Intents.all()` |
||
189 | throttler : Optional[:class:`~objects.app.throttling.ThrottleInterface`] |
||
190 | The cooldown handler for your client, |
||
191 | defaults to :class:`~.objects.app.throttling.DefaultThrottleHandler` |
||
192 | *(which uses the WindowSliding technique)*. |
||
193 | Custom throttlers must derive from |
||
194 | :class:`~pincer.objects.app.throttling.ThrottleInterface`. |
||
195 | |default| :class:`~pincer.objects.app.throttling.DefaultThrottleHandler` |
||
196 | """ # noqa: E501 |
||
197 | |||
198 | def __init__( |
||
199 | self, |
||
0 ignored issues
–
show
|
|||
200 | token: str, |
||
0 ignored issues
–
show
|
|||
201 | *, |
||
0 ignored issues
–
show
|
|||
202 | received: str = None, |
||
0 ignored issues
–
show
|
|||
203 | intents: Intents = None, |
||
0 ignored issues
–
show
|
|||
204 | throttler: ThrottleInterface = DefaultThrottleHandler, |
||
0 ignored issues
–
show
|
|||
205 | reconnect: bool = True, |
||
0 ignored issues
–
show
|
|||
206 | ): |
||
207 | def sigint_handler(_signal, _frame): |
||
208 | _log.info("SIGINT received, shutting down...") |
||
209 | |||
210 | # A print statement to make sure the user sees the message |
||
211 | print("Closing the client loop, this can take a few seconds...") |
||
212 | |||
213 | create_task(self.http.close()) |
||
214 | if self.loop.is_running(): |
||
215 | self.loop.stop() |
||
216 | |||
217 | signal.signal(signal.SIGINT, sigint_handler) |
||
218 | |||
219 | if isinstance(intents, Iterable): |
||
0 ignored issues
–
show
|
|||
220 | intents = sum(intents) |
||
221 | |||
222 | if intents is None: |
||
223 | intents = Intents.all() |
||
224 | |||
225 | self.intents = intents |
||
226 | self.reconnect = reconnect |
||
227 | self.token = token |
||
228 | |||
229 | self.bot: Optional[User] = None |
||
230 | self.received_message = received or "Command arrived successfully!" |
||
231 | self.http = HTTPClient(token) |
||
232 | APIObject.bind_client(self) |
||
233 | |||
234 | self.throttler = throttler |
||
235 | |||
236 | async def get_gateway(): |
||
237 | return GatewayInfo.from_dict(await self.http.get("gateway/bot")) |
||
238 | |||
239 | self.loop = get_event_loop() |
||
240 | self.event_mgr = EventMgr(self.loop) |
||
241 | |||
242 | self.gateway: GatewayInfo = self.loop.run_until_complete(get_gateway()) |
||
243 | self.shards: OrderedDict[int, Gateway] = OrderedDict() |
||
244 | |||
245 | # The guild and channel value is only registered if the Client has the GUILDS |
||
246 | # intent. |
||
247 | self.guilds: Dict[Snowflake, Optional[Guild]] = {} |
||
248 | self.channels: Dict[Snowflake, Optional[Channel]] = {} |
||
249 | |||
250 | ChatCommandHandler.managers.append(self) |
||
251 | |||
252 | super().__init__() |
||
253 | |||
254 | @property |
||
255 | def chat_commands(self) -> List[str]: |
||
256 | """List[:class:`str`]: List of chat commands |
||
257 | |||
258 | Get a list of chat command calls which have been registered in |
||
259 | the :class:`~pincer.commands.ChatCommandHandler`\\. |
||
260 | """ |
||
261 | return [ |
||
262 | cmd.metadata.name for cmd in ChatCommandHandler.register.values() |
||
263 | ] |
||
264 | |||
265 | @property |
||
266 | def guild_ids(self) -> List[Snowflake]: |
||
267 | """ |
||
268 | Returns a list of Guilds that the client is a part of |
||
269 | |||
270 | Returns |
||
271 | ------- |
||
272 | List[:class:`pincer.utils.snowflake.Snowflake`] |
||
273 | """ |
||
274 | return self.guilds.keys() |
||
275 | |||
276 | @staticmethod |
||
277 | def event(coroutine: Coro): |
||
278 | """A decorator to register a Discord gateway event listener. |
||
279 | This event will get called when the client receives a new event |
||
280 | update from Discord which matches the event name. |
||
281 | |||
282 | The event name gets pulled from your method name, and this must |
||
283 | start with ``on_``. |
||
284 | This forces you to write clean and consistent code. |
||
285 | |||
286 | This decorator can be used in and out of a class, and all |
||
287 | event methods must be coroutines. *(async)* |
||
288 | |||
289 | :Example usage: |
||
290 | |||
291 | .. code-block:: python3 |
||
292 | |||
293 | # Function based |
||
294 | from pincer import Client |
||
295 | |||
296 | client = Client("token") |
||
297 | |||
298 | @client.event |
||
299 | async def on_ready(): |
||
300 | print(f"Signed in as {client.bot}") |
||
301 | |||
302 | if __name__ == "__main__": |
||
303 | client.run() |
||
304 | |||
305 | .. code-block :: python3 |
||
306 | |||
307 | # Class based |
||
308 | from pincer import Client |
||
309 | |||
310 | class MyClient(Client): |
||
311 | @Client.event |
||
312 | async def on_ready(self): |
||
313 | print(f"Signed in as {self.bot}") |
||
314 | |||
315 | if __name__ == "__main__": |
||
316 | MyClient("token").run() |
||
317 | |||
318 | Raises |
||
319 | ------ |
||
320 | TypeError |
||
321 | If the function is not a coro |
||
322 | InvalidEventName |
||
323 | If the function name is not a valid event (on_x) |
||
324 | """ |
||
325 | if not iscoroutinefunction(coroutine) and not isasyncgenfunction( |
||
326 | coroutine |
||
0 ignored issues
–
show
|
|||
327 | ): |
||
328 | raise TypeError( |
||
329 | "Any event which is registered must be a coroutine function" |
||
330 | ) |
||
331 | |||
332 | name: str = coroutine.__name__.lower() |
||
333 | |||
334 | if not name.startswith("on_"): |
||
335 | raise InvalidEventName( |
||
336 | f"The event named `{name}` must start with `on_`" |
||
337 | ) |
||
338 | |||
339 | if name == "on_command_error" and _events.get(name): |
||
340 | raise InvalidEventName( |
||
341 | f"The `{name}` event can only exist once. This is because " |
||
342 | "it gets treated as a command and can have a response." |
||
343 | ) |
||
344 | |||
345 | event = InteractableStructure(call=coroutine) |
||
0 ignored issues
–
show
event is re-defining a name which is already available in the outer-scope (previously defined on line 161 ).
It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior: param = 5
class Foo:
def __init__(self, param): # "param" would be flagged here
self.param = param
![]() |
|||
346 | |||
347 | _events[name].append(event) |
||
348 | return event |
||
349 | |||
350 | @staticmethod |
||
351 | def get_event_coro( |
||
352 | name: str, |
||
0 ignored issues
–
show
|
|||
353 | ) -> List[Optional[InteractableStructure[None]]]: |
||
354 | """get the coroutine for an event |
||
355 | |||
356 | Parameters |
||
357 | ---------- |
||
358 | name : :class:`str` |
||
359 | name of the event |
||
360 | |||
361 | Returns |
||
362 | ------- |
||
363 | List[Optional[:class:`~pincer.objects.app.command.InteractableStructure`[None]]] |
||
364 | """ |
||
365 | calls = _events.get(name.strip().lower()) |
||
366 | |||
367 | return ( |
||
368 | [] |
||
369 | if not calls |
||
370 | else [ |
||
371 | call |
||
372 | for call in calls |
||
373 | if isinstance(call, InteractableStructure) |
||
374 | ] |
||
375 | ) |
||
376 | |||
377 | @staticmethod |
||
378 | def execute_event( |
||
379 | events: List[InteractableStructure], gateway: Gateway, *args, **kwargs |
||
0 ignored issues
–
show
|
|||
380 | ): |
||
381 | """Invokes an event. |
||
382 | |||
383 | Parameters |
||
384 | ---------- |
||
385 | calls: List[:class:`~pincer.objects.app.command.InteractableStructure`] |
||
386 | The call (method) to which the event is registered. |
||
387 | |||
388 | \\*args: |
||
389 | The arguments for the event. |
||
390 | |||
391 | \\*\\*kwargs: |
||
392 | The named arguments for the event. |
||
393 | """ |
||
394 | |||
395 | for event in events: |
||
0 ignored issues
–
show
event is re-defining a name which is already available in the outer-scope (previously defined on line 161 ).
It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior: param = 5
class Foo:
def __init__(self, param): # "param" would be flagged here
self.param = param
![]() |
|||
396 | call_args = args |
||
397 | if should_pass_cls(event.call): |
||
398 | call_args = ( |
||
399 | event.manager, |
||
400 | *remove_none(args), |
||
401 | ) |
||
402 | |||
403 | if should_pass_gateway(event.call): |
||
404 | call_args = (call_args[0], gateway, *call_args[1:]) |
||
405 | |||
406 | ensure_future(event.call(*call_args, **kwargs)) |
||
407 | |||
408 | def run(self): |
||
409 | """Start the bot.""" |
||
410 | ensure_future(self.start_shard(0, 1), loop=self.loop) |
||
411 | self.loop.run_forever() |
||
412 | |||
413 | def run_autosharded(self): |
||
414 | """ |
||
415 | Runs the bot with the amount of shards specified by the Discord gateway. |
||
416 | """ |
||
417 | num_shards = self.gateway.shards |
||
418 | return self.run_shards(range(num_shards), num_shards) |
||
419 | |||
420 | def run_shards(self, shards: Iterable, num_shards: int): |
||
421 | """ |
||
422 | Runs shards that you specify. |
||
423 | |||
424 | shards: Iterable |
||
425 | The shards to run. |
||
426 | num_shards: int |
||
427 | The total amount of shards. |
||
428 | """ |
||
429 | for shard in shards: |
||
430 | ensure_future(self.start_shard(shard, num_shards), loop=self.loop) |
||
431 | |||
432 | self.loop.run_forever() |
||
433 | |||
434 | async def start_shard(self, shard: int, num_shards: int): |
||
435 | """|coro| |
||
436 | Starts a shard |
||
437 | This should not be run most of the time. ``run_shards`` and ``run_autosharded`` |
||
438 | will likely do what you want. |
||
439 | |||
440 | shard : int |
||
441 | The number of the shard to start. |
||
442 | num_shards : int |
||
443 | The total number of shards. |
||
444 | """ |
||
445 | |||
446 | gateway = Gateway( |
||
447 | self.token, |
||
448 | intents=self.intents, |
||
449 | url=self.gateway.url, |
||
450 | shard=shard, |
||
451 | num_shards=num_shards, |
||
452 | ) |
||
453 | await gateway.init_session() |
||
454 | |||
455 | gateway.append_handlers( |
||
456 | { |
||
457 | # Gets triggered on all events |
||
458 | -1: partial(self.payload_event_handler, gateway), |
||
459 | # Use this event handler for opcode 0. |
||
460 | 0: partial(self.event_handler, gateway), |
||
461 | } |
||
462 | ) |
||
463 | |||
464 | self.shards[gateway.shard] = gateway |
||
465 | create_task(gateway.start_loop()) |
||
466 | |||
467 | def get_shard( |
||
468 | self, |
||
0 ignored issues
–
show
|
|||
469 | guild_id: Optional[Snowflake] = None, |
||
0 ignored issues
–
show
|
|||
470 | num_shards: Optional[int] = None, |
||
0 ignored issues
–
show
|
|||
471 | ) -> Gateway: |
||
472 | """ |
||
473 | Returns the shard receiving events for a specified guild_id. |
||
474 | |||
475 | ``num_shards`` is inferred from the num_shards value for the first started |
||
476 | shard. If your shards do not all have the same ``num_shard`` value, you must |
||
477 | specify value to get the expected result. |
||
478 | |||
479 | Parameters |
||
480 | ---------- |
||
481 | guild_id : Optional[:class:`~pincer.utils.snowflake.Snowflake`] |
||
482 | The guild_id of the shard to look for. If no guild id is provided, the |
||
483 | shard that receives dms will be returned. |default| :data:`None` |
||
484 | num_shards : Optional[:class:`int`] |
||
485 | The number of shards. If no number is provided, the value will default to |
||
486 | the num_shards for the first started shard. |default| :data:`None` |
||
487 | """ |
||
488 | if not self.shards: |
||
489 | raise GatewayConnectionError( |
||
490 | "The client has never connected to a gateway" |
||
491 | ) |
||
492 | if guild_id is None: |
||
493 | return self.shards[0] |
||
494 | if num_shards is None: |
||
495 | num_shards = next(iter(self.shards.values())).num_shards |
||
496 | return self.shards[calculate_shard_id(guild_id, num_shards)] |
||
497 | |||
498 | @property |
||
499 | def is_closed(self) -> bool: |
||
500 | """ |
||
501 | Returns |
||
502 | ------- |
||
503 | bool |
||
504 | Whether the bot is closed. |
||
505 | """ |
||
506 | return self.loop.is_running() |
||
507 | |||
508 | def close(self): |
||
509 | """ |
||
510 | Ensure close of the http client. |
||
511 | Allow for script execution to continue. |
||
512 | """ |
||
513 | if hasattr(self, "http"): |
||
514 | create_task(self.http.close()) |
||
515 | |||
516 | self.loop.stop() |
||
517 | |||
518 | def __del__(self): |
||
519 | if self.loop.is_running(): |
||
520 | self.loop.stop() |
||
521 | |||
522 | if not self.loop.is_closed(): |
||
523 | self.close() |
||
524 | |||
525 | async def handle_middleware( |
||
526 | self, |
||
0 ignored issues
–
show
|
|||
527 | payload: GatewayDispatch, |
||
0 ignored issues
–
show
|
|||
528 | key: str, |
||
0 ignored issues
–
show
|
|||
529 | gateway: Gateway, |
||
0 ignored issues
–
show
|
|||
530 | *args, |
||
0 ignored issues
–
show
|
|||
531 | **kwargs, |
||
0 ignored issues
–
show
|
|||
532 | ) -> Tuple[Optional[Coro], List[Any], Dict[str, Any]]: |
||
533 | """|coro| |
||
534 | |||
535 | Handles all middleware recursively. Stops when it has found an |
||
536 | event name which starts with ``on_``. |
||
537 | |||
538 | Returns a tuple where the first element is the final executor |
||
539 | (so the event) its index in ``_events``. |
||
540 | |||
541 | The second and third element are the ``*args`` |
||
542 | and ``**kwargs`` for the event. |
||
543 | |||
544 | Parameters |
||
545 | ---------- |
||
546 | payload : :class:`~pincer.core.dispatch.GatewayDispatch` |
||
547 | The original payload for the event |
||
548 | key : :class:`str` |
||
549 | The index of the middleware in ``_events`` |
||
550 | |||
551 | Raises |
||
552 | ------ |
||
553 | RuntimeError |
||
554 | The return type must be a tuple |
||
555 | RuntimeError |
||
556 | Middleware has not been registered |
||
557 | """ |
||
558 | ware: MiddlewareType = _events.get(key) |
||
559 | next_call, arguments, params = ware, [], {} |
||
560 | |||
561 | if iscoroutinefunction(ware): |
||
562 | extractable = await ware(self, gateway, payload, *args, **kwargs) |
||
563 | |||
564 | if not isinstance(extractable, tuple): |
||
565 | raise RuntimeError( |
||
566 | f"Return type from `{key}` middleware must be tuple. " |
||
567 | ) |
||
568 | |||
569 | next_call = get_index(extractable, 0, "") |
||
570 | ret_object = get_index(extractable, 1) |
||
571 | |||
572 | if next_call is None: |
||
573 | raise RuntimeError(f"Middleware `{key}` has not been registered.") |
||
574 | |||
575 | if next_call.startswith("on_"): |
||
576 | return (next_call, ret_object) |
||
577 | |||
578 | return await self.handle_middleware( |
||
579 | payload, next_call, gateway, *arguments, **params |
||
580 | ) |
||
581 | |||
582 | async def execute_error( |
||
0 ignored issues
–
show
|
|||
583 | self, |
||
0 ignored issues
–
show
|
|||
584 | error: Exception, |
||
0 ignored issues
–
show
|
|||
585 | gateway: Gateway, |
||
0 ignored issues
–
show
|
|||
586 | name: str = "on_error", |
||
0 ignored issues
–
show
|
|||
587 | *args, |
||
0 ignored issues
–
show
|
|||
588 | **kwargs, |
||
0 ignored issues
–
show
|
|||
589 | ): |
||
590 | """|coro| |
||
591 | |||
592 | Raises an error if no appropriate error event has been found. |
||
593 | |||
594 | Parameters |
||
595 | ---------- |
||
596 | error : :class:`Exception` |
||
597 | The error that should be passed to the event |
||
598 | name : :class:`str` |
||
599 | the name of the event |default| ``on_error`` |
||
600 | |||
601 | Raises |
||
602 | ------ |
||
603 | error |
||
604 | if ``call := self.get_event_coro(name)`` is :data:`False` |
||
605 | """ |
||
606 | if calls := self.get_event_coro(name): |
||
607 | self.execute_event(calls, gateway, error, *args, **kwargs) |
||
608 | else: |
||
609 | raise error |
||
610 | |||
611 | async def process_event( |
||
612 | self, name: str, payload: GatewayDispatch, gateway: Gateway |
||
0 ignored issues
–
show
|
|||
613 | ): |
||
614 | """|coro| |
||
615 | |||
616 | Processes and invokes an event and its middleware |
||
617 | |||
618 | Parameters |
||
619 | ---------- |
||
620 | name : :class:`str` |
||
621 | The name of the event, this is also the filename in the |
||
622 | middleware directory. |
||
623 | payload : :class:`~pincer.core.dispatch.GatewayDispatch` |
||
624 | The payload sent from the Discord gateway, this contains the |
||
625 | required data for the client to know what event it is and |
||
626 | what specifically happened. |
||
627 | """ |
||
628 | try: |
||
629 | key, args = await self.handle_middleware(payload, name, gateway) |
||
630 | self.event_mgr.process_events(key, args) |
||
631 | |||
632 | if calls := self.get_event_coro(key): |
||
633 | self.execute_event(calls, gateway, args) |
||
634 | |||
635 | except Exception as e: |
||
0 ignored issues
–
show
Catching very general exceptions such as
Exception is usually not recommended.
Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed. So, unless you specifically plan to handle any error, consider adding a more specific exception. ![]() |
|||
636 | await self.execute_error(e, gateway) |
||
637 | |||
638 | async def event_handler(self, gateway: Gateway, payload: GatewayDispatch): |
||
639 | """|coro| |
||
640 | |||
641 | Handles all payload events with opcode 0. |
||
642 | |||
643 | Parameters |
||
644 | ---------- |
||
645 | payload : :class:`~pincer.core.dispatch.GatewayDispatch` |
||
646 | The payload sent from the Discord gateway, this contains the |
||
647 | required data for the client to know what event it is and |
||
648 | what specifically happened. |
||
649 | """ |
||
650 | await self.process_event(payload.event_name.lower(), payload, gateway) |
||
651 | |||
652 | async def payload_event_handler( |
||
653 | self, gateway: Gateway, payload: GatewayDispatch |
||
0 ignored issues
–
show
|
|||
654 | ): |
||
655 | """|coro| |
||
656 | |||
657 | Special event which activates the on_payload event. |
||
658 | |||
659 | Parameters |
||
660 | ---------- |
||
661 | payload : :class:`~pincer.core.dispatch.GatewayDispatch` |
||
662 | The payload sent from the Discord gateway, this contains the |
||
663 | required data for the client to know what event it is and |
||
664 | what specifically happened. |
||
665 | """ |
||
666 | await self.process_event("payload", payload, gateway) |
||
667 | |||
668 | @overload |
||
669 | async def create_guild( |
||
670 | self, |
||
0 ignored issues
–
show
|
|||
671 | *, |
||
0 ignored issues
–
show
|
|||
672 | name: str, |
||
0 ignored issues
–
show
|
|||
673 | region: Optional[str] = None, |
||
0 ignored issues
–
show
|
|||
674 | icon: Optional[str] = None, |
||
0 ignored issues
–
show
|
|||
675 | verification_level: Optional[int] = None, |
||
0 ignored issues
–
show
|
|||
676 | default_message_notifications: Optional[int] = None, |
||
0 ignored issues
–
show
|
|||
677 | explicit_content_filter: Optional[int] = None, |
||
0 ignored issues
–
show
|
|||
678 | roles: Optional[List[Role]] = None, |
||
0 ignored issues
–
show
|
|||
679 | channels: Optional[List[Channel]] = None, |
||
0 ignored issues
–
show
|
|||
680 | afk_channel_id: Optional[Snowflake] = None, |
||
0 ignored issues
–
show
|
|||
681 | afk_timeout: Optional[int] = None, |
||
0 ignored issues
–
show
|
|||
682 | system_channel_id: Optional[Snowflake] = None, |
||
0 ignored issues
–
show
|
|||
683 | system_channel_flags: Optional[int] = None, |
||
0 ignored issues
–
show
|
|||
684 | ) -> Guild: |
||
685 | """Creates a guild. |
||
686 | |||
687 | Parameters |
||
688 | ---------- |
||
689 | name : :class:`str` |
||
690 | Name of the guild (2-100 characters) |
||
691 | region : Optional[:class:`str`] |
||
692 | Voice region id (deprecated) |default| :data:`None` |
||
693 | icon : Optional[:class:`str`] |
||
694 | base64 128x128 image for the guild icon |default| :data:`None` |
||
695 | verification_level : Optional[:class:`int`] |
||
696 | Verification level |default| :data:`None` |
||
697 | default_message_notifications : Optional[:class:`int`] |
||
698 | Default message notification level |default| :data:`None` |
||
699 | explicit_content_filter : Optional[:class:`int`] |
||
700 | Explicit content filter level |default| :data:`None` |
||
701 | roles : Optional[List[:class:`~pincer.objects.guild.role.Role`]] |
||
702 | New guild roles |default| :data:`None` |
||
703 | channels : Optional[List[:class:`~pincer.objects.guild.channel.Channel`]] |
||
704 | New guild's channels |default| :data:`None` |
||
705 | afk_channel_id : Optional[:class:`~pincer.utils.snowflake.Snowflake`] |
||
706 | ID for AFK channel |default| :data:`None` |
||
707 | afk_timeout : Optional[:class:`int`] |
||
708 | AFK timeout in seconds |default| :data:`None` |
||
709 | system_channel_id : Optional[:class:`~pincer.utils.snowflake.Snowflake`] |
||
710 | The ID of the channel where guild notices such as welcome |
||
711 | messages and boost events are posted |default| :data:`None` |
||
712 | system_channel_flags : Optional[:class:`int`] |
||
713 | System channel flags |default| :data:`None` |
||
714 | |||
715 | Returns |
||
716 | ------- |
||
717 | :class:`~pincer.objects.guild.guild.Guild` |
||
718 | The created guild |
||
719 | """ |
||
720 | ... |
||
721 | |||
722 | async def create_guild(self, name: str, **kwargs) -> Guild: |
||
0 ignored issues
–
show
|
|||
723 | g = await self.http.post("guilds", data={"name": name, **kwargs}) |
||
724 | return await self.get_guild(g["id"]) |
||
725 | |||
726 | async def get_guild_template(self, code: str) -> GuildTemplate: |
||
727 | """|coro| |
||
728 | Retrieves a guild template by its code. |
||
729 | |||
730 | Parameters |
||
731 | ---------- |
||
732 | code : :class:`str` |
||
733 | The code of the guild template |
||
734 | |||
735 | Returns |
||
736 | ------- |
||
737 | :class:`~pincer.objects.guild.template.GuildTemplate` |
||
738 | The guild template |
||
739 | """ |
||
740 | return GuildTemplate.from_dict( |
||
741 | await self.http.get(f"guilds/templates/{code}") |
||
742 | ) |
||
743 | |||
744 | async def create_guild_from_template( |
||
745 | self, template: GuildTemplate, name: str, icon: Optional[str] = None |
||
0 ignored issues
–
show
|
|||
746 | ) -> Guild: |
||
747 | """|coro| |
||
748 | Creates a guild from a template. |
||
749 | |||
750 | Parameters |
||
751 | ---------- |
||
752 | template : :class:`~pincer.objects.guild.template.GuildTemplate` |
||
753 | The guild template |
||
754 | name : :class:`str` |
||
755 | Name of the guild (2-100 characters) |
||
756 | icon : Optional[:class:`str`] |
||
757 | base64 128x128 image for the guild icon |default| :data:`None` |
||
758 | |||
759 | Returns |
||
760 | ------- |
||
761 | :class:`~pincer.objects.guild.guild.Guild` |
||
762 | The created guild |
||
763 | """ |
||
764 | return Guild.from_dict( |
||
765 | await self.http.post( |
||
766 | f"guilds/templates/{template.code}", |
||
767 | data={"name": name, "icon": icon}, |
||
768 | ) |
||
769 | ) |
||
770 | |||
771 | async def wait_for( |
||
772 | self, |
||
0 ignored issues
–
show
|
|||
773 | event_name: str, |
||
0 ignored issues
–
show
|
|||
774 | check: CheckFunction = None, |
||
0 ignored issues
–
show
|
|||
775 | timeout: Optional[float] = None, |
||
0 ignored issues
–
show
|
|||
776 | ): |
||
777 | """ |
||
778 | Parameters |
||
779 | ---------- |
||
780 | event_name : str |
||
781 | The type of event. It should start with `on_`. This is the same |
||
782 | name that is used for @Client.event. |
||
783 | check : CheckFunction |
||
784 | This function only returns a value if this return true. |
||
785 | timeout: Optional[float] |
||
786 | Amount of seconds before timeout. |
||
787 | |||
788 | Returns |
||
789 | ------ |
||
790 | Any |
||
791 | What the Discord API returns for this event. |
||
792 | """ |
||
793 | return await self.event_mgr.wait_for(event_name, check, timeout) |
||
794 | |||
795 | def loop_for( |
||
796 | self, |
||
0 ignored issues
–
show
|
|||
797 | event_name: str, |
||
0 ignored issues
–
show
|
|||
798 | check: CheckFunction = None, |
||
0 ignored issues
–
show
|
|||
799 | iteration_timeout: Optional[float] = None, |
||
0 ignored issues
–
show
|
|||
800 | loop_timeout: Optional[float] = None, |
||
0 ignored issues
–
show
|
|||
801 | ): |
||
802 | """ |
||
803 | Parameters |
||
804 | ---------- |
||
805 | event_name : str |
||
806 | The type of event. It should start with `on_`. This is the same |
||
807 | name that is used for @Client.event. |
||
808 | check : Callable[[Any], bool], default=None |
||
809 | This function only returns a value if this return true. |
||
810 | iteration_timeout: Union[float, None] |
||
811 | Amount of seconds before timeout. Timeouts are for each loop. |
||
812 | loop_timeout: Union[float, None] |
||
813 | Amount of seconds before the entire loop times out. The generator |
||
814 | will only raise a timeout error while it is waiting for an event. |
||
815 | |||
816 | Yields |
||
817 | ------ |
||
818 | Any |
||
819 | What the Discord API returns for this event. |
||
820 | """ |
||
821 | return self.event_mgr.loop_for( |
||
822 | event_name, check, iteration_timeout, loop_timeout |
||
823 | ) |
||
824 | |||
825 | async def get_guild(self, guild_id: int, with_count: bool = False) -> Guild: |
||
826 | """|coro| |
||
827 | |||
828 | Fetch a guild object by the guild identifier. |
||
829 | |||
830 | Parameters |
||
831 | ---------- |
||
832 | with_count: :class:bool |
||
833 | Whether to include the member count in the guild object. |
||
834 | Default to `False` |
||
835 | |||
836 | guild_id : :class:`int` |
||
837 | The id of the guild which should be fetched from the Discord |
||
838 | gateway. |
||
839 | |||
840 | Returns |
||
841 | ------- |
||
842 | :class:`~pincer.objects.guild.guild.Guild` |
||
843 | The guild object. |
||
844 | """ |
||
845 | return await Guild.from_id(self, guild_id, with_count) |
||
846 | |||
847 | async def get_user(self, _id: int) -> User: |
||
848 | """|coro| |
||
849 | |||
850 | Fetch a User from its identifier |
||
851 | |||
852 | Parameters |
||
853 | ---------- |
||
854 | _id : :class:`int` |
||
855 | The id of the user which should be fetched from the Discord |
||
856 | gateway. |
||
857 | |||
858 | Returns |
||
859 | ------- |
||
860 | :class:`~pincer.objects.user.user.User` |
||
861 | The user object. |
||
862 | """ |
||
863 | return await User.from_id(self, _id) |
||
864 | |||
865 | async def get_role(self, guild_id: int, role_id: int) -> Role: |
||
866 | """|coro| |
||
867 | |||
868 | Fetch a role object by the role identifier. |
||
869 | |||
870 | guild_id: :class:`int` |
||
871 | The guild in which the role resides. |
||
872 | |||
873 | role_id: :class:`int` |
||
874 | The id of the guild which should be fetched from the Discord |
||
875 | gateway. |
||
876 | |||
877 | Returns |
||
878 | ------- |
||
879 | :class:`~pincer.objects.guild.role.Role` |
||
880 | A Role object. |
||
881 | """ |
||
882 | return await Role.from_id(self, guild_id, role_id) |
||
883 | |||
884 | async def get_channel(self, _id: int) -> Channel: |
||
885 | """|coro| |
||
886 | Fetch a Channel from its identifier. The ``get_dm_channel`` method from |
||
887 | :class:`~pincer.objects.user.user.User` should be used if you need to |
||
888 | create a dm_channel; using the ``send()`` method from |
||
889 | :class:`~pincer.objects.user.user.User` is preferred. |
||
890 | |||
891 | Parameters |
||
892 | ---------- |
||
893 | _id: :class:`int` |
||
894 | The id of the user which should be fetched from the Discord |
||
895 | gateway. |
||
896 | |||
897 | Returns |
||
898 | ------- |
||
899 | :class:`~pincer.objects.guild.channel.Channel` |
||
900 | A Channel object. |
||
901 | """ |
||
902 | return await Channel.from_id(self, _id) |
||
903 | |||
904 | async def get_message( |
||
905 | self, _id: Snowflake, channel_id: Snowflake |
||
0 ignored issues
–
show
|
|||
906 | ) -> UserMessage: |
||
907 | """|coro| |
||
908 | Creates a UserMessage object |
||
909 | |||
910 | Parameters |
||
911 | ---------- |
||
912 | _id: :class:`~pincer.utils.snowflake.Snowflake` |
||
913 | ID of the message that is wanted. |
||
914 | channel_id : int |
||
915 | ID of the channel the message is in. |
||
916 | |||
917 | Returns |
||
918 | ------- |
||
919 | :class:`~pincer.objects.message.user_message.UserMessage` |
||
920 | The message object. |
||
921 | """ |
||
922 | |||
923 | return await UserMessage.from_id(self, _id, channel_id) |
||
924 | |||
925 | async def get_webhook( |
||
926 | self, id: Snowflake, token: Optional[str] = None |
||
0 ignored issues
–
show
|
|||
927 | ) -> Webhook: |
||
928 | """|coro| |
||
929 | Fetch a Webhook from its identifier. |
||
930 | |||
931 | Parameters |
||
932 | ---------- |
||
933 | id: :class:`int` |
||
934 | The id of the webhook which should be |
||
935 | fetched from the Discord gateway. |
||
936 | token: Optional[:class:`str`] |
||
937 | The webhook token. |
||
938 | |||
939 | Returns |
||
940 | ------- |
||
941 | :class:`~pincer.objects.guild.webhook.Webhook` |
||
942 | A Webhook object. |
||
943 | """ |
||
944 | return await Webhook.from_id(self, id, token) |
||
945 | |||
946 | async def get_current_user(self) -> User: |
||
947 | """|coro| |
||
948 | The user object of the requester's account. |
||
949 | |||
950 | For OAuth2, this requires the ``identify`` scope, |
||
951 | which will return the object *without* an email, |
||
952 | and optionally the ``email`` scope, |
||
953 | which returns the object *with* an email. |
||
954 | |||
955 | Returns |
||
956 | ------- |
||
957 | :class:`~pincer.objects.user.user.User` |
||
958 | """ |
||
959 | return User.from_dict(await self.http.get("users/@me")) |
||
960 | |||
961 | async def modify_current_user( |
||
962 | self, username: Optional[str] = None, avatar: Optional[File] = None |
||
0 ignored issues
–
show
|
|||
963 | ) -> User: |
||
964 | """|coro| |
||
965 | Modify the requester's user account settings |
||
966 | |||
967 | Parameters |
||
968 | ---------- |
||
969 | username : Optional[:class:`str`] |
||
970 | user's username, |
||
971 | if changed may cause the user's discriminator to be randomized. |
||
972 | |default| :data:`None` |
||
973 | avatar : Optional[:class:`File`] |
||
974 | if passed, modifies the user's avatar |
||
975 | a data URI scheme of JPG, GIF or PNG |
||
976 | |default| :data:`None` |
||
977 | |||
978 | Returns |
||
979 | ------- |
||
980 | :class:`~pincer.objects.user.user.User` |
||
981 | Current modified user |
||
982 | """ |
||
983 | |||
984 | avatar = avatar.uri if avatar else avatar |
||
985 | |||
986 | user = await self.http.patch( |
||
987 | "users/@me", remove_none({"username": username, "avatar": avatar}) |
||
988 | ) |
||
989 | return User.from_dict(user) |
||
990 | |||
991 | async def get_current_user_guilds( |
||
992 | self, |
||
0 ignored issues
–
show
|
|||
993 | before: Optional[Snowflake] = None, |
||
0 ignored issues
–
show
|
|||
994 | after: Optional[Snowflake] = None, |
||
0 ignored issues
–
show
|
|||
995 | limit: Optional[int] = None, |
||
0 ignored issues
–
show
|
|||
996 | ) -> AsyncIterator[Guild]: |
||
0 ignored issues
–
show
|
|||
997 | """|coro| |
||
998 | Returns a list of partial guild objects the current user is a member of. |
||
999 | Requires the ``guilds`` OAuth2 scope. |
||
1000 | |||
1001 | Parameters |
||
1002 | ---------- |
||
1003 | before : Optional[:class:`~pincer.utils.snowflake.Snowflake`] |
||
1004 | get guilds before this guild ID |
||
1005 | after : Optional[:class:`~pincer.utils.snowflake.Snowflake`] |
||
1006 | get guilds after this guild ID |
||
1007 | limit : Optional[:class:`int`] |
||
1008 | max number of guilds to return (1-200) |default| :data:`200` |
||
1009 | |||
1010 | Yields |
||
1011 | ------ |
||
1012 | :class:`~pincer.objects.guild.guild.Guild` |
||
1013 | A Partial Guild that the user is in |
||
1014 | """ |
||
1015 | guilds = await self.http.get( |
||
1016 | "users/@me/guilds?" |
||
1017 | + (f"{before=}&" if before else "") |
||
1018 | + (f"{after=}&" if after else "") |
||
1019 | + (f"{limit=}&" if limit else "") |
||
1020 | ) |
||
1021 | |||
1022 | for guild in guilds: |
||
1023 | yield Guild.from_dict(guild) |
||
1024 | |||
1025 | async def leave_guild(self, _id: Snowflake): |
||
1026 | """|coro| |
||
1027 | Leave a guild. |
||
1028 | |||
1029 | Parameters |
||
1030 | ---------- |
||
1031 | _id : :class:`~pincer.utils.snowflake.Snowflake` |
||
1032 | the id of the guild that the bot will leave |
||
1033 | """ |
||
1034 | await self.http.delete(f"users/@me/guilds/{_id}") |
||
1035 | self._client.guilds.pop(_id, None) |
||
0 ignored issues
–
show
|
|||
1036 | |||
1037 | async def create_group_dm( |
||
1038 | self, access_tokens: List[str], nicks: Dict[Snowflake, str] |
||
0 ignored issues
–
show
|
|||
1039 | ) -> GroupDMChannel: |
||
1040 | """|coro| |
||
1041 | Create a new group DM channel with multiple users. |
||
1042 | DMs created with this endpoint will not be shown in the Discord client |
||
1043 | |||
1044 | Parameters |
||
1045 | ---------- |
||
1046 | access_tokens : List[:class:`str`] |
||
1047 | access tokens of users that have |
||
1048 | granted your app the ``gdm.join`` scope |
||
1049 | |||
1050 | nicks : Dict[:class:`~pincer.utils.snowflake.Snowflake`, :class:`str`] |
||
1051 | a dictionary of user ids to their respective nicknames |
||
1052 | |||
1053 | Returns |
||
1054 | ------- |
||
1055 | :class:`~pincer.objects.guild.channel.GroupDMChannel` |
||
1056 | group DM channel created |
||
1057 | """ |
||
1058 | channel = await self.http.post( |
||
1059 | "users/@me/channels", |
||
1060 | {"access_tokens": access_tokens, "nicks": nicks}, |
||
1061 | ) |
||
1062 | |||
1063 | return GroupDMChannel.from_dict(channel) |
||
1064 | |||
1065 | async def get_connections(self) -> AsyncIterator[Connection]: |
||
0 ignored issues
–
show
|
|||
1066 | """|coro| |
||
1067 | Returns a list of connection objects. |
||
1068 | Requires the ``connections`` OAuth2 scope. |
||
1069 | |||
1070 | Yields |
||
1071 | ------- |
||
1072 | :class:`~pincer.objects.user.connection.Connections` |
||
1073 | the connection objects |
||
1074 | """ |
||
1075 | connections = await self.http.get("users/@me/connections") |
||
1076 | for conn in connections: |
||
1077 | yield Connection.from_dict(conn) |
||
1078 | |||
1079 | async def sticker_packs(self) -> AsyncIterator[StickerPack]: |
||
0 ignored issues
–
show
|
|||
1080 | """|coro| |
||
1081 | Yields sticker packs available to Nitro subscribers. |
||
1082 | |||
1083 | Yields |
||
1084 | ------ |
||
1085 | :class:`~pincer.objects.message.sticker.StickerPack` |
||
1086 | a sticker pack |
||
1087 | """ |
||
1088 | packs = await self.http.get("sticker-packs") |
||
1089 | for pack in packs: |
||
1090 | yield StickerPack.from_dict(pack) |
||
1091 | |||
1092 | async def create_stage( |
||
1093 | self, |
||
0 ignored issues
–
show
|
|||
1094 | channel_id: int, |
||
0 ignored issues
–
show
|
|||
1095 | topic: str, |
||
0 ignored issues
–
show
|
|||
1096 | privacy_level: Optional[PrivacyLevel] = None, |
||
0 ignored issues
–
show
|
|||
1097 | reason: Optional[str] = None, |
||
0 ignored issues
–
show
|
|||
1098 | ) -> StageInstance: |
||
1099 | """|coro| |
||
1100 | |||
1101 | Parameters |
||
1102 | ---------- |
||
1103 | channel_id : :class:`int` |
||
1104 | The id of the Stage channel |
||
1105 | topic : :class:`str` |
||
1106 | The topic of the Stage instance (1-120 characters) |
||
1107 | privacy_level : Optional[:class:`~pincer.objects.guild.stage.PrivacyLevel`] |
||
1108 | The privacy level of the Stage instance (default :data:`GUILD_ONLY`) |
||
1109 | reason : Optional[:class:`str`] |
||
1110 | The reason for creating the Stage instance |
||
1111 | |||
1112 | Returns |
||
1113 | ------- |
||
1114 | :class:`~pincer.objects.guild.stage.StageInstance` |
||
1115 | The Stage instance created |
||
1116 | """ |
||
1117 | |||
1118 | data = { |
||
1119 | "channel_id": channel_id, |
||
1120 | "topic": topic, |
||
1121 | "privacy_level": privacy_level, |
||
1122 | } |
||
1123 | |||
1124 | return await self.http.post( # type: ignore |
||
1125 | "stage-instances", remove_none(data), headers={"reason": reason} |
||
1126 | ) |
||
1127 | |||
1128 | async def get_stage(self, _id: int) -> StageInstance: |
||
1129 | """|coro| |
||
1130 | Gets the stage instance associated with the Stage channel, if it exists |
||
1131 | |||
1132 | Parameters |
||
1133 | ---------- |
||
1134 | _id : int |
||
1135 | The ID of the stage to get |
||
1136 | |||
1137 | Returns |
||
1138 | ------- |
||
1139 | :class:`~pincer.objects.guild.stage.StageInstance` |
||
1140 | The stage instance |
||
1141 | """ |
||
1142 | return await StageInstance.from_id(self, _id) |
||
1143 | |||
1144 | async def modify_stage( |
||
1145 | self, |
||
0 ignored issues
–
show
|
|||
1146 | _id: int, |
||
0 ignored issues
–
show
|
|||
1147 | topic: Optional[str] = None, |
||
0 ignored issues
–
show
|
|||
1148 | privacy_level: Optional[PrivacyLevel] = None, |
||
0 ignored issues
–
show
|
|||
1149 | reason: Optional[str] = None, |
||
0 ignored issues
–
show
|
|||
1150 | ): |
||
1151 | """|coro| |
||
1152 | Updates fields of an existing Stage instance. |
||
1153 | Requires the user to be a moderator of the Stage channel. |
||
1154 | |||
1155 | Parameters |
||
1156 | ---------- |
||
1157 | _id : int |
||
1158 | The ID of the stage to modify |
||
1159 | topic : Optional[:class:`str`] |
||
1160 | The topic of the Stage instance (1-120 characters) |
||
1161 | privacy_level : Optional[:class:`~pincer.objects.guild.stage.PrivacyLevel`] |
||
1162 | The privacy level of the Stage instance |
||
1163 | reason : Optional[:class:`str`] |
||
1164 | The reason for the modification |
||
1165 | """ |
||
1166 | |||
1167 | await self.http.patch( |
||
1168 | f"stage-instances/{_id}", |
||
1169 | remove_none({"topic": topic, "privacy_level": privacy_level}), |
||
1170 | headers={"reason": reason}, |
||
1171 | ) |
||
1172 | |||
1173 | async def delete_stage(self, _id: int, reason: Optional[str] = None): |
||
1174 | """|coro| |
||
1175 | Deletes the Stage instance. |
||
1176 | Requires the user to be a moderator of the Stage channel. |
||
1177 | |||
1178 | Parameters |
||
1179 | ---------- |
||
1180 | _id : int |
||
1181 | The ID of the stage to delete |
||
1182 | reason : Optional[:class:`str`] |
||
1183 | The reason for the deletion |
||
1184 | """ |
||
1185 | await self.http.delete( |
||
1186 | f"stage-instances/{_id}", headers={"reason": reason} |
||
1187 | ) |
||
1188 | |||
1189 | async def crosspost_message( |
||
1190 | self, channel_id: int, message_id: int |
||
0 ignored issues
–
show
|
|||
1191 | ) -> UserMessage: |
||
1192 | """|coro| |
||
1193 | Crosspost a message in a News Channel to following channels. |
||
1194 | |||
1195 | This endpoint requires the ``SEND_MESSAGES`` permission, |
||
1196 | if the current user sent the message, or additionally the |
||
1197 | ``MANAGE_MESSAGES`` permission, for all other messages, |
||
1198 | to be present for the current user. |
||
1199 | |||
1200 | Parameters |
||
1201 | ---------- |
||
1202 | channel_id : int |
||
1203 | ID of the news channel that the message is in. |
||
1204 | message_id : int |
||
1205 | ID of the message to crosspost. |
||
1206 | |||
1207 | Returns |
||
1208 | ------- |
||
1209 | :class:`~pincer.objects.message.UserMessage` |
||
1210 | The crossposted message |
||
1211 | """ |
||
1212 | |||
1213 | return await self._http.post( |
||
0 ignored issues
–
show
|
|||
1214 | f"channels/{channel_id}/{message_id}/crosspost" |
||
1215 | ) |
||
1216 | |||
1217 | |||
1218 | Bot = Client |
||
1219 |