Completed
Push — main ( e04df9...e9b147 )
by Yohann
15s queued 13s
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 103)'
Loading history...
33
34
35
class AllowedMentionTypes(str, Enum):
36
    """The allowed mentions.
37
38
    Attributes
39
    ----------
40
    ROLES:
41
        Controls role mentions
42
    USERS:
43
        Controls user mentions
44
    EVERYONE:
45
        Controls @everyone and @here mentions
46
    """
47
    ROLES = "roles"
48
    USERS = "users"
49
    EVERYONE = "everyone"
50
51
52
@dataclass
53
class AllowedMentions(APIObject):
54
    """Represents the entities the client can mention
55
56
    Attributes
57
    ----------
58
    parse: List[:class:`~pincer.objects.message.user_message.AllowedMentionTypes`]
59
        An array of allowed mention types to parse from the content.
60
    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...
61
        List of ``Role`` objects or snowflakes of allowed mentions.
62
    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...
63
        List of ``user`` objects or snowflakes of allowed mentions.
64
    reply: :class:`bool`
65
        If replies should mention the author.
66
        |default| :data:`True`
67
    """  # noqa: E501
68
69
    parse: List[AllowedMentionTypes]
70
    roles: List[Union[Role, Snowflake]]
71
    users: List[Union[User, Snowflake]]
72
    reply: bool = True
73
74
    def to_dict(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
75
        def get_str_id(obj: Union[Snowflake, User, Role]) -> str:
76
            if hasattr(obj, "id"):
77
                obj = obj.id
78
79
            return str(obj)
80
81
        return {
82
            "parse": self.parse,
83
            "roles": list(map(get_str_id, self.roles)),
84
            "users": list(map(get_str_id, self.users)),
85
            "replied_user": self.reply
86
        }
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
332
    edited_timestamp: APINullable[Timestamp] = MISSING
333
    mention_channels: APINullable[List[ChannelMention]] = MISSING
334
    guild_id: APINullable[Snowflake] = MISSING
335
    member: APINullable[GuildMember] = MISSING
336
    reactions: APINullable[List[Reaction]] = MISSING
337
    nonce: APINullable[Union[int, str]] = MISSING
338
    webhook_id: APINullable[Snowflake] = MISSING
339
    activity: APINullable[MessageActivity] = MISSING
340
    application: APINullable[Application] = MISSING
341
    application_id: APINullable[Snowflake] = MISSING
342
    message_reference: APINullable[MessageReference] = MISSING
343
    flags: APINullable[MessageFlags] = MISSING
344
    referenced_message: APINullable[Optional[UserMessage]] = MISSING
345
    interaction: APINullable[MessageInteraction] = MISSING
346
    thread: APINullable[Channel] = MISSING
347
    components: APINullable[List[MessageComponent]] = MISSING
348
    sticker_items: APINullable[List[StickerItem]] = MISSING
349
350
    def __str__(self):
351
        return self.content
352
353
    async def get_most_recent(self):
354
        """|coro|
355
356
        Certain Discord methods don't return the message object data after its
357
        updated. This function can be run to get the most recent version of the
358
        message object.
359
        """
360
361
        return self.from_dict(
362
            construct_client_dict(
363
                self._client,
364
                await self._http.get(
365
                    f"/channels/{self.channel_id}/messages/{self.id}"
366
                )
367
            )
368
        )
369
370
    async def react(self, emoji: str):
371
        """|coro|
372
373
        Create a reaction for the message. Requires the
374
        ``READ_MESSAGE_HISTORY` itent. ``ADD_REACTIONS`` intent is required if
375
        nobody else has reacted using the emoji.
376
377
        Parameters
378
        ----------
379
        emoji: :class:`str`
380
            Character for emoji. Does not need to be URL encoded.
381
        """
382
383
        await self._http.put(
384
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}/@me"
385
        )
386
387
    async def unreact(self, emoji: str):
388
        """|coro|
389
390
        Delete a reaction the current user has made for the message.
391
392
        Parameters
393
        ----------
394
        emoji: :class:`str`
395
            Character for emoji. Does not need to be URL encoded.
396
        """
397
398
        await self._http.delete(
399
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}/@me"
400
        )
401
402
    async def remove_user_reaction(self, emoji: str, user_id: Snowflake):
403
        """|coro|
404
405
        Deletes another user's reaction. Requires the ``MANAGE_MESSAGES``
406
        intent.
407
408
        Parameters
409
        ----------
410
        emoji: :class:`str`
411
            Character for emoji. Does not need to be URL encoded.
412
        user_id: :class:`~pincer.utils.snowflake.Snowflake`
413
            User ID
414
        """
415
416
        await self._http.delete(
417
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
418
            f"/{user_id}"
419
        )
420
421
    async def get_reactions(
422
        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...
423
    ) -> Generator[User, None, None]:
424
        # 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...
425
        """|coro|
426
427
        Returns the users that reacted with this emoji.
428
429
        Parameters
430
        ----------
431
        emoji: :class:`str`
432
            Emoji to get users for.
433
        after: :class:`~pincer.utils.snowflake.Snowflake`
434
            Get users after this user ID. Returns all users if not provided.
435
            |default| ``0``
436
        limit: :class:`int`
437
            Max number of users to return (1-100).
438
            |default| ``25``
439
        """
440
441
        for user in await self._http.get(
442
            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...
443
            f"?after={after}&limit={limit}"
444
        ):
445
            yield User.from_dict(user)
446
447
    async def remove_all_reactions(self):
448
        """|coro|
449
450
        Delete all reactions on a message. Requires the ``MANAGE_MESSAGES``
451
        intent.
452
        """
453
454
        await self._http.delete(
455
            f"/channels/{self.channel_id}/messages/{self.id}/reactions"
456
        )
457
458
    async def remove_emoji(self, emoji):
459
        """|coro|
460
461
        Deletes all the reactions for a given emoji on a message. Requires the
462
        ``MANAGE_MESSAGES`` intent.
463
464
        Parameters
465
        ----------
466
        emoji: :class:`str`
467
            Character for emoji. Does not need to be URL encoded.
468
        """
469
470
        await self._http.delete(
471
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
472
        )
473
474
    # 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...
475
    async def edit(
0 ignored issues
show
best-practice introduced by
Too many arguments (7/5)
Loading history...
476
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
477
        content: str = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
478
        embeds: List[Embed] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
479
        flags: int = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
480
        allowed_mentions: AllowedMentions = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
481
        attachments: List[Attachment] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
482
        components: List[MessageComponent] = None
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
483
    ):
484
        """|coro|
485
486
        Edit a previously sent message. The fields content, embeds, and flags
487
        can be edited by the original message author. Other users can only
488
        edit flags and only if they have the ``MANAGE_MESSAGES`` permission in
489
        the corresponding channel. When specifying flags, ensure to include
490
        all previously set flags/bits in addition to ones that you are
491
        modifying.
492
493
        Parameters
494
        ----------
495
        content: :class:`str`
496
            The message contents (up to 2000 characters)
497
            |default| ``None``
498
        embeds: List[:class:`~pincer.objects.message.embed.Embed`]
499
            Embedded rich content (up to 6000 characters)
500
        flags: :class:`int`
501
            Edit the flags of a message (only ``SUPPRESS_EMBEDS`` can
502
            currently be set/unset)
503
        allowed_mentions: :class:`~pincer.objects.message.message.AllowedMentions`
504
            allowed mentions for the message
505
        attachments: List[:class:`~pincer.objects.message.attachment.Attachment`]
506
            attached files to keep
507
        components: List[:class:`~pincer.objects.message.component.MessageComponent`]
508
            the components to include with the message
509
        """
510
511
        data = {}
512
513
        def set_if_not_none(value: Any, name: str):
514
            if isinstance(value, APIObject):
515
                data[name] = value.to_dict()
516
            elif value is not None:
517
                data[name] = value
518
519
        set_if_not_none(content, "content")
520
        set_if_not_none(embeds, "embeds")
521
        set_if_not_none(flags, "flags")
522
        set_if_not_none(allowed_mentions, "allowed_mentions")
523
        set_if_not_none(attachments, "attachments")
524
        set_if_not_none(components, "components")
525
526
        await self._http.patch(
527
            f"/channels/{self.channel_id}/messages/{self.id}",
528
            data=data
529
        )
530
531
    async def delete(self):
532
        """|coro|
533
534
        Delete a message. Requires the ``MANAGE_MESSAGES`` intent if the
535
        message was not sent by the current user.
536
        """
537
538
        await self._http.delete(
539
            f"/channels/{self.channel_id}/messages/{self.id}"
540
        )
541