Passed
Pull Request — main (#237)
by
unknown
02:02
created

AllowedMentions.to_dict()   A

Complexity

Conditions 2

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 12
rs 9.9
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
import re
7
from dataclasses import dataclass
8
from enum import Enum, IntEnum
9
from typing import TYPE_CHECKING, DefaultDict
10
from collections import defaultdict
11
12
from .attachment import Attachment
13
from .component import MessageComponent
14
from .embed import Embed
15
from .reaction import Reaction
16
from .reference import MessageReference
17
from .sticker import StickerItem
18
from ..app.application import Application
19
from ..app.interaction_base import MessageInteraction
20
from ..guild.member import GuildMember
21
from ..guild.role import Role
22
from ..user.user import User
23
from ..._config import GatewayConfig
24
from ...utils.api_object import APIObject, GuildProperty, ChannelProperty
25
from ...utils.conversion import construct_client_dict
26
from ...utils.snowflake import Snowflake
27
from ...utils.types import MISSING, JSONSerializable
28
29
if TYPE_CHECKING:
30
    from typing import Any, List, Optional, Union, Generator
0 ignored issues
show
introduced by
Imports from package typing are not grouped
Loading history...
31
32
    from ... import Client
33
    from ..guild.channel import Channel, ChannelMention
34
    from ...utils.types import APINullable
35
    from ...utils.timestamp import Timestamp
36
37
38
MARKDOWN_PATTERNS = (
39
    re.compile(r"\*\*(.*?)\*\*"),  # bold
40
    re.compile(r"\*(.*?)\*"),  # italic
41
    re.compile(r"_(.*?)_"),  # italic2
42
    re.compile(r"\*\*\*(.*?)\*\*\*"),  # bold+italic
43
    re.compile(r"\_\_(.*?)\_\_"),  # underline
44
    re.compile(r"~~(.*?)~~"),  # crossed
45
    re.compile(r"`?`(.*?)`?`"),  # small code blocks
46
    re.compile(r"\|\|(.*?)\|\|")  # spoilers
47
)
48
49
50
class AllowedMentionTypes(str, Enum):
51
    """The allowed mentions.
52
53
    Attributes
54
    ----------
55
    ROLES:
56
        Controls role mentions
57
    USERS:
58
        Controls user mentions
59
    EVERYONE:
60
        Controls @everyone and @here mentions
61
    """
62
63
    ROLES = "roles"
64
    USERS = "users"
65
    EVERYONE = "everyone"
66
67
68
@dataclass(repr=False)
69
class AllowedMentions(APIObject):
70
    """Represents the entities the client can mention
71
72
    Attributes
73
    ----------
74
    parse: List[:class:`~pincer.objects.message.user_message.AllowedMentionTypes`]
75
        An array of allowed mention types to parse from the content.
76
    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...
77
        List of ``Role`` objects or snowflakes of allowed mentions.
78
    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...
79
        List of ``user`` objects or snowflakes of allowed mentions.
80
    reply: :class:`bool`
81
        If replies should mention the author.
82
        |default| :data:`True`
83
    """
84
85
    # noqa: E501
86
87
    parse: List[AllowedMentionTypes]
88
    roles: List[Union[Role, Snowflake]]
89
    users: List[Union[User, Snowflake]]
90
    reply: bool = True
91
92
    def to_dict(self):
93
        def get_str_id(obj: Union[Snowflake, User, Role]) -> str:
94
            if hasattr(obj, "id"):
95
                obj = obj.id
96
97
            return str(obj)
98
99
        return {
100
            "parse": self.parse,
101
            "roles": list(map(get_str_id, self.roles)),
102
            "users": list(map(get_str_id, self.users)),
103
            "replied_user": self.reply,
104
        }
105
106
107
class MessageActivityType(IntEnum):
108
    """The activity people can perform on a rich presence activity.
109
110
    Such an activity could for example be a spotify listen.
111
112
    Attributes
113
    ----------
114
    JOIN:
115
        Invite to join.
116
    SPECTATE:
117
        Invite to spectate.
118
    LISTEN:
119
        Invite to listen along.
120
    JOIN_REQUEST:
121
        Request to join.
122
    """
123
124
    JOIN = 1
125
    SPECTATE = 2
126
    LISTEN = 3
127
    JOIN_REQUEST = 5
128
129
130
class MessageFlags(IntEnum):
131
    """Special message properties.
132
133
    Attributes
134
    ----------
135
    CROSSPOSTED:
136
        The message has been published to subscribed
137
        channels (via Channel Following)
138
    IS_CROSSPOST:
139
        This message originated from a message
140
        in another channel (via Channel Following)
141
    SUPPRESS_EMBEDS:
142
        Do not include any embeds when serializing this message
143
    SOURCE_MESSAGE_DELETED:
144
        The source message for this crosspost
145
        has been deleted (via Channel Following)
146
    URGENT:
147
        This message came from the urgent message system
148
    HAS_THREAD:
149
        This message has an associated thread,
150
        with the same id as the message
151
    EPHEMERAL:
152
        This message is only visible to the user
153
        who invoked the Interaction
154
    LOADING:
155
        This message is an Interaction
156
        Response and the bot is "thinking"
157
    """
158
159
    CROSSPOSTED = 1 << 0
160
    IS_CROSSPOST = 1 << 1
161
    SUPPRESS_EMBEDS = 1 << 2
162
    SOURCE_MESSAGE_DELETED = 1 << 3
163
    URGENT = 1 << 4
164
    HAS_THREAD = 1 << 5
165
    EPHEMERAL = 1 << 6
166
    LOADING = 1 << 7
167
168
169
class MessageType(IntEnum):
170
    """Represents the type of the message.
171
172
    Attributes
173
    ----------
174
    DEFAULT:
175
        Normal message.
176
    RECIPIENT_ADD:
177
        Recipient is added.
178
    RECIPIENT_REMOVE:
179
        Recipient is removed.
180
    CALL:
181
        A call is being made.
182
    CHANNEL_NAME_CHANGE:
183
        The group channel name is changed.
184
    CHANNEL_ICON_CHANGE:
185
        The group channel icon is changed.
186
    CHANNEL_PINNED_MESSAGE:
187
        A message is pinned.
188
    GUILD_MEMBER_JOIN:
189
        A member joined.
190
    USER_PREMIUM_GUILD_SUBSCRIPTION:
191
        A boost.
192
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1:
193
        A boost that reached tier 1.
194
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2:
195
        A boost that reached tier 2.
196
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3:
197
        A boost that reached tier 3.
198
    CHANNEL_FOLLOW_ADD:
199
        A channel is subscribed to.
200
    GUILD_DISCOVERY_DISQUALIFIED:
201
        The guild is disqualified from discovery,
202
    GUILD_DISCOVERY_REQUALIFIED:
203
        The guild is requalified for discovery.
204
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING:
205
        Warning about discovery violations.
206
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING:
207
        Final warning about discovery violations.
208
    THREAD_CREATED:
209
        A thread is created.
210
    REPLY:
211
        A message reply.
212
    APPLICATION_COMMAND:
213
        Slash command is used and responded to.
214
    THREAD_STARTER_MESSAGE:
215
        The initial message in a thread when its created off a message.
216
    GUILD_INVITE_REMINDER:
217
        ??
218
    """
219
220
    DEFAULT = 0
221
    RECIPIENT_ADD = 1
222
    RECIPIENT_REMOVE = 2
223
    CALL = 3
224
    CHANNEL_NAME_CHANGE = 4
225
    CHANNEL_ICON_CHANGE = 5
226
    CHANNEL_PINNED_MESSAGE = 6
227
    GUILD_MEMBER_JOIN = 7
228
    USER_PREMIUM_GUILD_SUBSCRIPTION = 8
229
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9
230
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10
231
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11
232
    CHANNEL_FOLLOW_ADD = 12
233
    GUILD_DISCOVERY_DISQUALIFIED = 14
234
    GUILD_DISCOVERY_REQUALIFIED = 15
235
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16
236
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17
237
    THREAD_CREATED = 18
238
    REPLY = 19
239
    APPLICATION_COMMAND = 20
240
241
    if GatewayConfig.version < 8:
242
        REPLY = 0
243
        APPLICATION_COMMAND = 0
244
245
    if GatewayConfig.version >= 9:
246
        THREAD_STARTER_MESSAGE = 21
247
248
    GUILD_INVITE_REMINDER = 22
249
    CONTEXT_MENU_COMMAND = 23
250
251
252
@dataclass(repr=False)
253
class MessageActivity(APIObject):
254
    """Represents a Discord Message Activity object
255
256
    Attributes
257
    ----------
258
    type: :class:`~pincer.objects.message.user_message.MessageActivity`
259
        type of message activity
260
    party_id: APINullable[:class:`str`]
261
        party_id from a Rich Presence event
262
    """
263
264
    type: MessageActivityType
265
    party_id: APINullable[str] = MISSING
266
267
268
@dataclass(repr=False)
269
class UserMessage(APIObject, GuildProperty, ChannelProperty):
0 ignored issues
show
best-practice introduced by
Too many instance attributes (30/7)
Loading history...
270
    """Represents a message sent in a channel within Discord.
271
272
    Attributes
273
    ----------
274
    id: :class:`~pincer.utils.snowflake.Snowflake`
275
        Ud of the message
276
    channel_id: :class:`~pincer.utils.snowflake.Snowflake`
277
        Id of the channel the message was sent in
278
    author: :class:`~pincer.objects.user.user.User`
279
        The author of this message (not guaranteed to be a valid user)
280
    content: :class:`str`
281
        Contents of the message
282
    timestamp: :class:`~pincer.utils.timestamp.Timestamp`
283
        When this message was sent
284
    edited_timestamp: Optional[:class:`~pincer.utils.timestamp.Timestamp`]
285
        When this message was edited (or null if never)
286
    tts: :class:`bool`
287
        Whether this was a TTS message
288
    mention_everyone: :class:`bool`
289
        Whether this message mentions everyone
290
    mentions: List[:class:`~pincer.objects.guild.member.GuildMember`]
291
        Users specifically mentioned in the message
292
    mention_roles: List[:class:`~pincer.objects.guild.role.Role`]
293
        Roles specifically mentioned in this message
294
    attachments: List[:class:`~pincer.objects.message.attachment.Attachment`]
295
        Any attached files
296
    embeds: List[:class:`~pincer.objects.message.embed.Embed`]
297
        Any embedded content
298
    pinned: :class:`bool`
299
        Whether this message is pinned
300
    type: :class:`~pincer.objects.message.user_message.MessageType`
301
        Type of message
302
    mention_channels: APINullable[List[:class:`~pincer.objects.guild.channel.Channel`]]
303
        Channels specifically mentioned in this message
304
    guild_id: APINullable[:class:`~pincer.utils.snowflake.Snowflake`]
305
        Id of the guild the message was sent in
306
    member: APINullable[:class:`~pincer.objects.guild.member.PartialGuildMember`]
307
        Member properties for this message's author
308
    reactions: APINullable[List[:class:`~pincer.objects.message.reaction.Reaction`]]
309
        Reactions to the message
310
    nonce: APINullable[Union[:class:`int`, :class:`str`]]
311
        User for validating a message was sent
312
    webhook_id: APINullable[:class:`~pincer.utils.snowflake.Snowflake`]
313
        If the message is generated by a webhook,
314
        this is the webhook's id
315
    activity: APINullable[:class:`~pincer.objects.message.user_message.MessageActivity`]
316
        Sent with Rich Presence-related chat embeds
317
    application: APINullable[:class:`~pincer.objects.app.application.Application`]
318
        Sent with Rich Presence-related chat embeds
319
    application_id: APINullable[:class:`~pincer.utils.snowflake.Snowflake`]
320
        If the message is a response to an Interaction,
321
        this is the id of the interaction's application
322
    message_reference: APINullable[:class:`~pincer.objects.message.reference.MessageReference`]
323
        Data showing the source of a crosspost,
324
        channel follow add, pin, or reply message
325
    flags: APINullable[:class:`~pincer.objects.message.user_message.MessageFlags`]
326
        Message flags combined as a bitfield
327
    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...
328
        The message associated with the message_reference
329
    interaction: APINullable[:class:`~pincer.objects.app.interaction_base.MessageInteraction`]
330
        Sent if the message is a response to an Interaction
331
    thread: APINullable[:class:`~pincer.objects.guild.channel.Channel`]
332
        The thread that was started from this message,
333
        includes thread member object
334
    components: APINullable[List[:class:`~pincer.objects.message.component.MessageComponent`]]
335
        Sent if the message contains components like buttons,
336
        action rows, or other interactive components
337
    sticker_items: APINullable[List[:class:`~pincer.objects.message.sticker.StickerItem`]]
338
        Sent if the message contains stickers
339
    """
340
341
    # noqa: E501
342
343
    id: Snowflake
344
    channel_id: Snowflake
345
    author: User
346
    content: str
347
    timestamp: Timestamp
348
    tts: bool
349
    mention_everyone: bool
350
    mentions: List[GuildMember]
351
    mention_roles: List[Role]
352
    attachments: List[Attachment]
353
    embeds: List[Embed]
354
    pinned: bool
355
    type: MessageType
356
357
    edited_timestamp: APINullable[Timestamp] = MISSING
358
    mention_channels: APINullable[List[ChannelMention]] = MISSING
359
    guild_id: APINullable[Snowflake] = MISSING
360
    member: APINullable[GuildMember] = MISSING
361
    reactions: APINullable[List[Reaction]] = MISSING
362
    nonce: APINullable[Union[int, str]] = MISSING
363
    webhook_id: APINullable[Snowflake] = MISSING
364
    activity: APINullable[MessageActivity] = MISSING
365
    application: APINullable[Application] = MISSING
366
    application_id: APINullable[Snowflake] = MISSING
367
    message_reference: APINullable[MessageReference] = MISSING
368
    flags: APINullable[MessageFlags] = MISSING
369
    referenced_message: APINullable[Optional[UserMessage]] = MISSING
370
    interaction: APINullable[MessageInteraction] = MISSING
371
    thread: APINullable[Channel] = MISSING
372
    components: APINullable[List[MessageComponent]] = MISSING
373
    sticker_items: APINullable[List[StickerItem]] = MISSING
374
375
    @classmethod
376
    async def from_id(
377
        cls, client: Client, _id: Snowflake, channel_id: Snowflake
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
378
    ) -> UserMessage:
379
        """|coro|
380
381
        Creates a UserMessage object
382
        It is recommended to use the ``get_message`` function from
383
        :class:`~pincer.client.Client` most of the time.
384
385
        Parameters
386
        ----------
387
        client : :class:`~pincer.client.Client`
388
            Client object to use the HTTP class of.
389
        _id: :class:`~pincer.utils.snowflake.Snowflake`
390
            ID of the message that is wanted.
391
        channel_id : int
392
            ID of the channel the message is in.
393
394
        Returns
395
        -------
396
        :class:`~pincer.objects.message.user_message.UserMessage`
397
            The message object.
398
        """
399
        msg = await client.http.get(f"channels/{channel_id}/messages/{_id}")
400
        return cls.from_dict(construct_client_dict(client, msg))
401
402
    def __str__(self):
403
        return self.content
404
405
    async def get_most_recent(self):
406
        """|coro|
407
408
        Certain Discord methods don't return the message object data after its
409
        updated. This function can be run to get the most recent version of the
410
        message object.
411
        """
412
413
        return self.from_dict(
414
            construct_client_dict(
415
                self._client,
416
                await self._http.get(
417
                    f"/channels/{self.channel_id}/messages/{self.id}"
418
                ),
419
            )
420
        )
421
422
    async def react(self, emoji: str):
423
        """|coro|
424
425
        Create a reaction for the message. Requires the
426
        ``READ_MESSAGE_HISTORY` intent. ``ADD_REACTIONS`` intent is required if
427
        nobody else has reacted using the emoji.
428
429
        Parameters
430
        ----------
431
        emoji: :class:`str`
432
            Character for emoji. Does not need to be URL encoded.
433
        """
434
435
        await self._http.put(
436
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}/@me"
437
        )
438
439
    async def unreact(self, emoji: str):
440
        """|coro|
441
442
        Delete a reaction the current user has made for the message.
443
444
        Parameters
445
        ----------
446
        emoji: :class:`str`
447
            Character for emoji. Does not need to be URL encoded.
448
        """
449
450
        await self._http.delete(
451
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}/@me"
452
        )
453
454
    async def remove_user_reaction(self, emoji: str, user_id: Snowflake):
455
        """|coro|
456
457
        Deletes another user's reaction. Requires the ``MANAGE_MESSAGES``
458
        intent.
459
460
        Parameters
461
        ----------
462
        emoji: :class:`str`
463
            Character for emoji. Does not need to be URL encoded.
464
        user_id: :class:`~pincer.utils.snowflake.Snowflake`
465
            User ID
466
        """
467
468
        await self._http.delete(
469
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
470
            f"/{user_id}"
471
        )
472
473
    async def get_reactions(
474
        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...
475
    ) -> Generator[User, None, None]:
476
        # 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...
477
        """|coro|
478
479
        Returns the users that reacted with this emoji.
480
481
        Parameters
482
        ----------
483
        emoji: :class:`str`
484
            Emoji to get users for.
485
        after: :class:`~pincer.utils.snowflake.Snowflake`
486
            Get users after this user ID. Returns all users if not provided.
487
            |default| ``0``
488
        limit: :class:`int`
489
            Max number of users to return (1-100).
490
            |default| ``25``
491
        """
492
493
        for user in await self._http.get(
494
            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...
495
            f"?after={after}&limit={limit}"
496
        ):
497
            yield User.from_dict(user)
498
499
    async def remove_all_reactions(self):
500
        """|coro|
501
502
        Delete all reactions on a message. Requires the ``MANAGE_MESSAGES``
503
        intent.
504
        """
505
506
        await self._http.delete(
507
            f"/channels/{self.channel_id}/messages/{self.id}/reactions"
508
        )
509
510
    async def remove_emoji(self, emoji):
511
        """|coro|
512
513
        Deletes all the reactions for a given emoji on a message. Requires the
514
        ``MANAGE_MESSAGES`` intent.
515
516
        Parameters
517
        ----------
518
        emoji: :class:`str`
519
            Character for emoji. Does not need to be URL encoded.
520
        """
521
522
        await self._http.delete(
523
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
524
        )
525
526
    # 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...
527
    async def edit(
0 ignored issues
show
best-practice introduced by
Too many arguments (7/5)
Loading history...
528
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
529
        content: str = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
530
        embeds: List[Embed] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
531
        flags: int = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
532
        allowed_mentions: AllowedMentions = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
533
        attachments: List[Attachment] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
534
        components: List[MessageComponent] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
535
    ):
536
        """|coro|
537
538
        Edit a previously sent message. The fields content, embeds, and flags
539
        can be edited by the original message author. Other users can only
540
        edit flags and only if they have the ``MANAGE_MESSAGES`` permission in
541
        the corresponding channel. When specifying flags, ensure to include
542
        all previously set flags/bits in addition to ones that you are
543
        modifying.
544
545
        Parameters
546
        ----------
547
        content: :class:`str`
548
            The message contents (up to 2000 characters)
549
            |default| ``None``
550
        embeds: List[:class:`~pincer.objects.message.embed.Embed`]
551
            Embedded rich content (up to 6000 characters)
552
        flags: :class:`int`
553
            Edit the flags of a message (only ``SUPPRESS_EMBEDS`` can
554
            currently be set/unset)
555
        allowed_mentions: :class:`~pincer.objects.message.message.AllowedMentions`
556
            allowed mentions for the message
557
        attachments: List[:class:`~pincer.objects.message.attachment.Attachment`]
558
            attached files to keep
559
        components: List[:class:`~pincer.objects.message.component.MessageComponent`]
560
            the components to include with the message
561
        """
562
563
        data: DefaultDict[str, JSONSerializable] = defaultdict(list)
564
565
        def set_if_not_none(value: Any, name: str):
0 ignored issues
show
Unused Code introduced by
Either all return statements in a function should return an expression, or none of them should.
Loading history...
566
            if isinstance(value, list):
567
                for item in value:
568
                    return set_if_not_none(item, name)
569
570
            if isinstance(value, APIObject):
571
                data[name].append(value.to_dict())
572
573
            elif value is not None:
574
                data[name] = value
575
576
        set_if_not_none(content, "content")
577
        set_if_not_none(embeds, "embeds")
578
        set_if_not_none(flags, "flags")
579
        set_if_not_none(allowed_mentions, "allowed_mentions")
580
        set_if_not_none(attachments, "attachments")
581
        set_if_not_none(components, "components")
582
583
        await self._http.patch(
584
            f"/channels/{self.channel_id}/messages/{self.id}", data=data
585
        )
586
587
    async def delete(self):
588
        """|coro|
589
590
        Delete a message. Requires the ``MANAGE_MESSAGES`` intent if the
591
        message was not sent by the current user.
592
        """
593
594
        await self._http.delete(
595
            f"/channels/{self.channel_id}/messages/{self.id}"
596
        )
597
598
    @property
599
    def clean_content(self) -> str:
600
        """
601
        Returns
602
        -------
603
        :class:`str`
604
            The message content with any special characters removed.
605
        """
606
        new_content = self.content
607
608
        for pattern in MARKDOWN_PATTERNS:
609
            new_content = re.sub(pattern, r"\1", new_content)
610
611
        new_content = re.sub(
612
            re.compile(r"(.*?)```[a-zA-Z]+[\n]+((?:.|\s)*?)```"),
613
            r"\1\2\3",
614
            new_content
615
        )
616
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
617
        return new_content[:-1] if new_content.endswith("\n") else new_content
618