Passed
Pull Request — main (#155)
by Oliver
03:16 queued 01:36
created

AllowedMentions.get_str_id()   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nop 1
1
# Copyright Pincer 2021-Present
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
# Full MIT License can be found in `LICENSE` at the project root.
3
4
from __future__ import annotations
5
6
from enum import Enum, IntEnum
7
from typing import TYPE_CHECKING
8
from dataclasses import dataclass
9
10
from .embed import Embed
11
from .reaction import Reaction
12
from .sticker import StickerItem
13
from .attachment import Attachment
14
from .component import MessageComponent
15
from .reference import MessageReference
16
from ..user.user import User
17
from ..guild.role import Role
18
from ..app.application import Application
19
from ..app.interaction_base import MessageInteraction
20
from ..guild.member import GuildMember
21
from ...utils.types import MISSING
22
from ..._config import GatewayConfig
23
from ...utils.snowflake import Snowflake
24
from ...utils.api_object import APIObject
0 ignored issues
show
Bug introduced by
The name api_object does not seem to exist in module pincer.utils.
Loading history...
introduced by
Cannot import 'utils.api_object' due to syntax error 'invalid syntax (<unknown>, line 80)'
Loading history...
25
from ...utils.conversion import construct_client_dict
26
27
if TYPE_CHECKING:
28
    from typing import Any, List, Optional, Union, Generator
0 ignored issues
show
introduced by
Imports from package typing are not grouped
Loading history...
29
30
    from ..guild.channel import Channel, ChannelMention
0 ignored issues
show
Bug introduced by
The name channel does not seem to exist in module pincer.objects.guild.
Loading history...
introduced by
Cannot import 'guild.channel' due to syntax error 'invalid syntax (<unknown>, line 248)'
Loading history...
31
    from ...utils.types import APINullable
32
    from ...utils.timestamp import Timestamp
0 ignored issues
show
Bug introduced by
The name timestamp does not seem to exist in module pincer.utils.
Loading history...
introduced by
Cannot import 'utils.timestamp' due to syntax error 'invalid syntax (<unknown>, line 99)'
Loading history...
33
34
35
@dataclass
36
class AllowedMentions(APIObject):
37
    """Represents the entities the client can mention
38
39
    Attributes
40
    ----------
41
    parse: List[:class:`~pincer.objects.message.user_message.AllowedMentionTypes`]
42
        An array of allowed mention types to parse from the content.
43
    roles: List[Union[:class:`~pincer.objects.guild.role.Role`, :class:`~pincer.utils.snowflake.Snowflake`]]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (108/100).

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

Loading history...
44
        List of ``Role`` objects or snowflakes of allowed mentions.
45
    users: List[Union[:class:`~pincer.objects.user.user.User` :class:`~pincer.utils.snowflake.Snowflake`]]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (106/100).

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

Loading history...
46
        List of ``user`` objects or snowflakes of allowed mentions.
47
    reply: :class:`bool`
48
        If replies should mention the author.
49
        |default| :data:`True`
50
    """  # noqa: E501
51
52
    parse: List[AllowedMentionTypes]
53
    roles: List[Union[Role, Snowflake]]
54
    users: List[Union[User, Snowflake]]
55
    reply: bool = True
56
57
    def to_dict(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
58
        def get_str_id(obj: Union[Snowflake, User, Role]) -> str:
59
            if hasattr(obj, "id"):
60
                obj = obj.id
61
62
            return str(obj)
63
64
        return {
65
            "parse": self.parse,
66
            "roles": list(map(get_str_id, self.roles)),
67
            "users": list(map(get_str_id, self.users)),
68
            "replied_user": self.reply
69
        }
70
71
72
class AllowedMentionTypes(str, Enum):
73
    """The allowed mentions.
74
75
    Attributes
76
    ----------
77
    ROLES:
78
        Controls role mentions
79
    USERS:
80
        Controls user mentions
81
    EVERYONE:
82
        Controls @everyone and @here mentions
83
    """
84
    ROLES = "roles"
85
    USERS = "users"
86
    EVERYONE = "everyone"
87
88
89
class MessageActivityType(IntEnum):
90
    """The activity people can perform on a rich presence activity.
91
92
    Such an activity could for example be a spotify listen.
93
94
    Attributes
95
    ----------
96
    JOIN:
97
        Invite to join.
98
    SPECTATE:
99
        Invite to spectate.
100
    LISTEN:
101
        Invite to listen along.
102
    JOIN_REQUEST:
103
        Request to join.
104
    """
105
    JOIN = 1
106
    SPECTATE = 2
107
    LISTEN = 3
108
    JOIN_REQUEST = 5
109
110
111
class MessageFlags(IntEnum):
112
    """Special message properties.
113
114
    Attributes
115
    ----------
116
    CROSSPOSTED:
117
        The message has been published to subscribed
118
        channels (via Channel Following)
119
    IS_CROSSPOST:
120
        This message originated from a message
121
        in another channel (via Channel Following)
122
    SUPPRESS_EMBEDS:
123
        Do not include any embeds when serializing this message
124
    SOURCE_MESSAGE_DELETED:
125
        The source message for this crosspost
126
        has been deleted (via Channel Following)
127
    URGENT:
128
        This message came from the urgent message system
129
    HAS_THREAD:
130
        This message has an associated thread,
131
        with the same id as the message
132
    EPHEMERAL:
133
        This message is only visible to the user
134
        who invoked the Interaction
135
    LOADING:
136
        This message is an Interaction
137
        Response and the bot is "thinking"
138
    """
139
    CROSSPOSTED = 1 << 0
140
    IS_CROSSPOST = 1 << 1
141
    SUPPRESS_EMBEDS = 1 << 2
142
    SOURCE_MESSAGE_DELETED = 1 << 3
143
    URGENT = 1 << 4
144
    HAS_THREAD = 1 << 5
145
    EPHEMERAL = 1 << 6
146
    LOADING = 1 << 7
147
148
149
class MessageType(IntEnum):
150
    """Represents the type of the message.
151
152
    Attributes
153
    ----------
154
    DEFAULT:
155
        Normal message.
156
    RECIPIENT_ADD:
157
        Recipient is added.
158
    RECIPIENT_REMOVE:
159
        Recipient is removed.
160
    CALL:
161
        A call is being made.
162
    CHANNEL_NAME_CHANGE:
163
        The group channel name is changed.
164
    CHANNEL_ICON_CHANGE:
165
        The group channel icon is changed.
166
    CHANNEL_PINNED_MESSAGE:
167
        A message is pinned.
168
    GUILD_MEMBER_JOIN:
169
        A member joined.
170
    USER_PREMIUM_GUILD_SUBSCRIPTION:
171
        A boost.
172
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1:
173
        A boost that reached tier 1.
174
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2:
175
        A boost that reached tier 2.
176
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3:
177
        A boost that reached tier 3.
178
    CHANNEL_FOLLOW_ADD:
179
        A channel is subscribed to.
180
    GUILD_DISCOVERY_DISQUALIFIED:
181
        The guild is disqualified from discovery,
182
    GUILD_DISCOVERY_REQUALIFIED:
183
        The guild is requalified for discovery.
184
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING:
185
        Warning about discovery violations.
186
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING:
187
        Final warning about discovery violations.
188
    THREAD_CREATED:
189
        A thread is created.
190
    REPLY:
191
        A message reply.
192
    APPLICATION_COMMAND:
193
        Slash command is used and responded to.
194
    THREAD_STARTER_MESSAGE:
195
        The initial message in a thread when its created off a message.
196
    GUILD_INVITE_REMINDER:
197
        ??
198
    """
199
    DEFAULT = 0
200
    RECIPIENT_ADD = 1
201
    RECIPIENT_REMOVE = 2
202
    CALL = 3
203
    CHANNEL_NAME_CHANGE = 4
204
    CHANNEL_ICON_CHANGE = 5
205
    CHANNEL_PINNED_MESSAGE = 6
206
    GUILD_MEMBER_JOIN = 7
207
    USER_PREMIUM_GUILD_SUBSCRIPTION = 8
208
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9
209
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10
210
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11
211
    CHANNEL_FOLLOW_ADD = 12
212
    GUILD_DISCOVERY_DISQUALIFIED = 14
213
    GUILD_DISCOVERY_REQUALIFIED = 15
214
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16
215
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17
216
    THREAD_CREATED = 18
217
    REPLY = 19
218
    APPLICATION_COMMAND = 20
219
220
    if GatewayConfig.version < 8:
221
        REPLY = 0
222
        APPLICATION_COMMAND = 0
223
224
    if GatewayConfig.version >= 9:
225
        THREAD_STARTER_MESSAGE = 21
226
227
    GUILD_INVITE_REMINDER = 22
228
229
230
@dataclass
231
class MessageActivity(APIObject):
232
    """Represents a Discord Message Activity object
233
234
    Attributes
235
    ----------
236
    type: :class:`~pincer.objects.message.user_message.MessageActivity`
237
        type of message activity
238
    party_id: APINullable[:class:`str`]
239
        party_id from a Rich Presence event
240
    """
241
    type: MessageActivityType
242
    party_id: APINullable[str] = MISSING
243
244
245
@dataclass
0 ignored issues
show
best-practice introduced by
Too many instance attributes (30/7)
Loading history...
246
class UserMessage(APIObject):
247
    """Represents a message sent in a channel within Discord.
248
249
    Attributes
250
    ----------
251
    id: :class:`~pincer.utils.snowflake.Snowflake`
252
        Ud of the message
253
    channel_id: :class:`~pincer.utils.snowflake.Snowflake`
254
        Id of the channel the message was sent in
255
    author: :class:`~pincer.objects.user.user.User`
256
        The author of this message (not guaranteed to be a valid user)
257
    content: :class:`str`
258
        Contents of the message
259
    timestamp: :class:`~pincer.utils.timestamp.Timestamp`
260
        When this message was sent
261
    edited_timestamp: Optional[:class:`~pincer.utils.timestamp.Timestamp`]
262
        When this message was edited (or null if never)
263
    tts: :class:`bool`
264
        Whether this was a TTS message
265
    mention_everyone: :class:`bool`
266
        Whether this message mentions everyone
267
    mentions: List[:class:`~pincer.objects.guild.member.GuildMember`]
268
        Users specifically mentioned in the message
269
    mention_roles: List[:class:`~pincer.objects.guild.role.Role`]
270
        Roles specifically mentioned in this message
271
    attachments: List[:class:`~pincer.objects.message.attachment.Attachment`]
272
        Any attached files
273
    embeds: List[:class:`~pincer.objects.message.embed.Embed`]
274
        Any embedded content
275
    pinned: :class:`bool`
276
        Whether this message is pinned
277
    type: :class:`~pincer.objects.message.user_message.MessageType`
278
        Type of message
279
    mention_channels: APINullable[List[:class:`~pincer.objects.guild.channel.Channel`]]
280
        Channels specifically mentioned in this message
281
    guild_id: APINullable[:class:`~pincer.utils.snowflake.Snowflake`]
282
        Id of the guild the message was sent in
283
    member: APINullable[:class:`~pincer.objects.guild.member.PartialGuildMember`]
284
        Member properties for this message's author
285
    reactions: APINullable[List[:class:`~pincer.objects.message.reaction.Reaction`]]
286
        Reactions to the message
287
    nonce: APINullable[Union[:class:`int`, :class:`str`]]
288
        User for validating a message was sent
289
    webhook_id: APINullable[:class:`~pincer.utils.snowflake.Snowflake`]
290
        If the message is generated by a webhook,
291
        this is the webhook's id
292
    activity: APINullable[:class:`~pincer.objects.message.user_message.MessageActivity`]
293
        Sent with Rich Presence-related chat embeds
294
    application: APINullable[:class:`~pincer.objects.app.application.Application`]
295
        Sent with Rich Presence-related chat embeds
296
    application_id: APINullable[:class:`~pincer.utils.snowflake.Snowflake`]
297
        If the message is a response to an Interaction,
298
        this is the id of the interaction's application
299
    message_reference: APINullable[:class:`~pincer.objects.message.reference.MessageReference`]
300
        Data showing the source of a crosspost,
301
        channel follow add, pin, or reply message
302
    flags: APINullable[:class:`~pincer.objects.message.user_message.MessageFlags`]
303
        Message flags combined as a bitfield
304
    referenced_message: APINullable[Optional[:class:`~pincer.objects.message.user_message.UserMessage`]]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/100).

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

Loading history...
305
        The message associated with the message_reference
306
    interaction: APINullable[:class:`~pincer.objects.app.interaction_base.MessageInteraction`]
307
        Sent if the message is a response to an Interaction
308
    thread: APINullable[:class:`~pincer.objects.guild.channel.Channel`]
309
        The thread that was started from this message,
310
        includes thread member object
311
    components: APINullable[List[:class:`~pincer.objects.message.component.MessageComponent`]]
312
        Sent if the message contains components like buttons,
313
        action rows, or other interactive components
314
    sticker_items: APINullable[List[:class:`~pincer.objects.message.sticker.StickerItem`]]
315
        Sent if the message contains stickers
316
    """  # noqa: E501
317
318
    id: Snowflake
319
    channel_id: Snowflake
320
    author: User
321
    content: str
322
    timestamp: Timestamp
323
    tts: bool
324
    mention_everyone: bool
325
    mentions: List[GuildMember]
326
    mention_roles: List[Role]
327
    attachments: List[Attachment]
328
    embeds: List[Embed]
329
    pinned: bool
330
    type: MessageType
331
    edited_timestamp: Optional[Timestamp] = None
332
333
    edited_timestamp: APINullable[Timestamp] = MISSING
334
    mention_channels: APINullable[List[ChannelMention]] = MISSING
335
    guild_id: APINullable[Snowflake] = MISSING
336
    member: APINullable[GuildMember] = MISSING
337
    reactions: APINullable[List[Reaction]] = MISSING
338
    nonce: APINullable[Union[int, str]] = MISSING
339
    webhook_id: APINullable[Snowflake] = MISSING
340
    activity: APINullable[MessageActivity] = MISSING
341
    application: APINullable[Application] = MISSING
342
    application_id: APINullable[Snowflake] = MISSING
343
    message_reference: APINullable[MessageReference] = MISSING
344
    flags: APINullable[MessageFlags] = MISSING
345
    referenced_message: APINullable[Optional[UserMessage]] = MISSING
346
    interaction: APINullable[MessageInteraction] = MISSING
347
    thread: APINullable[Channel] = MISSING
348
    components: APINullable[List[MessageComponent]] = MISSING
349
    sticker_items: APINullable[List[StickerItem]] = MISSING
350
351
    def __str__(self):
352
        return self.content
353
354
    async def get_most_recent(self):
355
        """|coro|
356
357
        Certain Discord methods don't return the message object data after its
358
        updated. This function can be run to get the most recent version of the
359
        message object.
360
        """
361
362
        return self.from_dict(
363
            construct_client_dict(
364
                self._client,
365
                await self._http.get(
366
                    f"/channels/{self.channel_id}/messages/{self.id}"
367
                )
368
            )
369
        )
370
371
    async def react(self, emoji: str):
372
        """|coro|
373
374
        Create a reaction for the message. Requires the
375
        ``READ_MESSAGE_HISTORY` itent. ``ADD_REACTIONS`` intent is required if
376
        nobody else has reacted using the emoji.
377
378
        Parameters
379
        ----------
380
        emoji: :class:`str`
381
            Character for emoji. Does not need to be URL encoded.
382
        """
383
384
        await self._http.put(
385
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}/@me"
386
        )
387
388
    async def unreact(self, emoji: str):
389
        """|coro|
390
391
        Delete a reaction the current user has made for the message.
392
393
        Parameters
394
        ----------
395
        emoji: :class:`str`
396
            Character for emoji. Does not need to be URL encoded.
397
        """
398
399
        await self._http.delete(
400
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}/@me"
401
        )
402
403
    async def remove_user_reaction(self, emoji: str, user_id: Snowflake):
404
        """|coro|
405
406
        Deletes another user's reaction. Requires the ``MANAGE_MESSAGES``
407
        intent.
408
409
        Parameters
410
        ----------
411
        emoji: :class:`str`
412
            Character for emoji. Does not need to be URL encoded.
413
        user_id: :class:`~pincer.utils.snowflake.Snowflake`
414
            User ID
415
        """
416
417
        await self._http.delete(
418
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
419
            f"/{user_id}"
420
        )
421
422
    async def get_reactions(
423
        self, emoji: str, after: Snowflake = 0, limit=25
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
424
    ) -> Generator[User, None, None]:
425
        # TODO: HTTP Client will need to refactored to allow parameters using aiohttp's system.
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
426
        """|coro|
427
428
        Returns the users that reacted with this emoji.
429
430
        Parameters
431
        ----------
432
        emoji: :class:`str`
433
            Emoji to get users for.
434
        after: :class:`~pincer.utils.snowflake.Snowflake`
435
            Get users after this user ID. Returns all users if not provided.
436
            |default| ``0``
437
        limit: :class:`int`
438
            Max number of users to return (1-100).
439
            |default| ``25``
440
        """
441
442
        for user in await self._http.get(
443
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
444
            f"?after={after}&limit={limit}"
445
        ):
446
            yield User.from_dict(user)
447
448
    async def remove_all_reactions(self):
449
        """|coro|
450
451
        Delete all reactions on a message. Requires the ``MANAGE_MESSAGES``
452
        intent.
453
        """
454
455
        await self._http.delete(
456
            f"/channels/{self.channel_id}/messages/{self.id}/reactions"
457
        )
458
459
    async def remove_emoji(self, emoji):
460
        """|coro|
461
462
        Deletes all the reactions for a given emoji on a message. Requires the
463
        ``MANAGE_MESSAGES`` intent.
464
465
        Parameters
466
        ----------
467
        emoji: :class:`str`
468
            Character for emoji. Does not need to be URL encoded.
469
        """
470
471
        await self._http.delete(
472
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
473
        )
474
475
    # TODO: Implement file (https://discord.com/developers/docs/resources/channel#edit-message)
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
476
    async def edit(
0 ignored issues
show
best-practice introduced by
Too many arguments (7/5)
Loading history...
477
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
478
        content: str = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
479
        embeds: List[Embed] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
480
        flags: int = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
481
        allowed_mentions: AllowedMentions = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
482
        attachments: List[Attachment] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
483
        components: List[MessageComponent] = None
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
484
    ):
485
        """|coro|
486
487
        Edit a previously sent message. The fields content, embeds, and flags
488
        can be edited by the original message author. Other users can only
489
        edit flags and only if they have the ``MANAGE_MESSAGES`` permission in
490
        the corresponding channel. When specifying flags, ensure to include
491
        all previously set flags/bits in addition to ones that you are
492
        modifying.
493
494
        Parameters
495
        ----------
496
        content: :class:`str`
497
            The message contents (up to 2000 characters)
498
            |default| ``None``
499
        embeds: List[:class:`~pincer.objects.message.embed.Embed`]
500
            Embedded rich content (up to 6000 characters)
501
        flags: :class:`int`
502
            Edit the flags of a message (only ``SUPPRESS_EMBEDS`` can
503
            currently be set/unset)
504
        allowed_mentions: :class:`~pincer.objects.message.message.AllowedMentions`
505
            allowed mentions for the message
506
        attachments: List[:class:`~pincer.objects.message.attachment.Attachment`]
507
            attached files to keep
508
        components: List[:class:`~pincer.objects.message.component.MessageComponent`]
509
            the components to include with the message
510
        """
511
512
        data = {}
513
514
        def set_if_not_none(value: Any, name: str):
515
            if isinstance(value, APIObject):
516
                data[name] = value.to_dict()
517
            elif value is not None:
518
                data[name] = value
519
520
        set_if_not_none(content, "content")
521
        set_if_not_none(embeds, "embeds")
522
        set_if_not_none(flags, "flags")
523
        set_if_not_none(allowed_mentions, "allowed_mentions")
524
        set_if_not_none(attachments, "attachments")
525
        set_if_not_none(components, "components")
526
527
        await self._http.patch(
528
            f"/channels/{self.channel_id}/messages/{self.id}",
529
            data=data
530
        )
531
532
    async def delete(self):
533
        """|coro|
534
535
        Delete a message. Requires the ``MANAGE_MESSAGES`` intent if the
536
        message was not sent by the current user.
537
        """
538
539
        await self._http.delete(
540
            f"/channels/{self.channel_id}/messages/{self.id}"
541
        )
542