Issues (1445)

pincer/client.py (88 issues)

1
# Copyright Pincer 2021-Present
0 ignored issues
show
Missing module docstring
Loading history...
Too many lines in module (1218/1000)
Loading history...
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
Imports from package collections are not grouped
Loading history...
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
Too many instance attributes (13/7)
Loading history...
Too many public methods (40/20)
Loading history...
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
This line is too long as per the coding-style (117/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
200
        token: str,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
201
        *,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
202
        received: str = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
203
        intents: Intents = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
204
        throttler: ThrottleInterface = DefaultThrottleHandler,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
205
        reconnect: bool = True,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Second argument of isinstance is not a type
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Comprehensibility Bug introduced by
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
Loading history...
346
347
        _events[name].append(event)
348
        return event
349
350
    @staticmethod
351
    def get_event_coro(
352
        name: str,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Comprehensibility Bug introduced by
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
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
469
        guild_id: Optional[Snowflake] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
470
        num_shards: Optional[int] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
527
        payload: GatewayDispatch,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
528
        key: str,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
529
        gateway: Gateway,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
530
        *args,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
531
        **kwargs,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Keyword argument before variable positional arguments list in the definition of execute_error function
Loading history...
583
        self,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
584
        error: Exception,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
585
        gateway: Gateway,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
586
        name: str = "on_error",
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
587
        *args,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
588
        **kwargs,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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.

Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
671
        *,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
672
        name: str,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
673
        region: Optional[str] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
674
        icon: Optional[str] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
675
        verification_level: Optional[int] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
676
        default_message_notifications: Optional[int] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
677
        explicit_content_filter: Optional[int] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
678
        roles: Optional[List[Role]] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
679
        channels: Optional[List[Channel]] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
680
        afk_channel_id: Optional[Snowflake] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
681
        afk_timeout: Optional[int] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
682
        system_channel_id: Optional[Snowflake] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
683
        system_channel_flags: Optional[int] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Missing function or method docstring
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
773
        event_name: str,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
774
        check: CheckFunction = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
775
        timeout: Optional[float] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
797
        event_name: str,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
798
        check: CheckFunction = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
799
        iteration_timeout: Optional[float] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
800
        loop_timeout: Optional[float] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
993
        before: Optional[Snowflake] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
994
        after: Optional[Snowflake] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
995
        limit: Optional[int] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
996
    ) -> AsyncIterator[Guild]:
0 ignored issues
show
Value 'AsyncIterator' is unsubscriptable
Loading history...
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
The Instance of Client does not seem to have a member named _client.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
1036
1037
    async def create_group_dm(
1038
        self, access_tokens: List[str], nicks: Dict[Snowflake, str]
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Value 'AsyncIterator' is unsubscriptable
Loading history...
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
Value 'AsyncIterator' is unsubscriptable
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1094
        channel_id: int,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1095
        topic: str,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1096
        privacy_level: Optional[PrivacyLevel] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1097
        reason: Optional[str] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1146
        _id: int,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1147
        topic: Optional[str] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1148
        privacy_level: Optional[PrivacyLevel] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1149
        reason: Optional[str] = None,
0 ignored issues
show
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Wrong hanging indentation before block (add 4 spaces).
Loading history...
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
Instance of 'Client' has no '_http' member; maybe 'http'?

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
1214
            f"channels/{channel_id}/{message_id}/crosspost"
1215
        )
1216
1217
1218
Bot = Client
1219