Passed
Pull Request — main (#158)
by
unknown
01:29
created

UserMessage.edit()   A

Complexity

Conditions 3

Size

Total Lines 55
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 55
rs 9.328
c 0
b 0
f 0
cc 3
nop 7

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 dataclasses import dataclass
7
from enum import IntEnum, Enum
8
from typing import Any, Generator, List, Optional, Union, TYPE_CHECKING
9
10
from ..._config import GatewayConfig
11
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 81)'
Loading history...
12
from ...utils.types import MISSING
13
14
if TYPE_CHECKING:
15
    from ..app.application import Application
16
    from ..app.interaction_base import MessageInteraction
17
    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 235)'
Loading history...
18
    from ..guild.member import GuildMember
19
    from ..guild.role import Role
20
    from ..message.attachment import Attachment
21
    from ..message.component import MessageComponent
22
    from ..message.embed import Embed
23
    from ..message.reaction import Reaction
24
    from ..message.reference import MessageReference
25
    from ..message.sticker import StickerItem
26
    from ..user import User
27
    from ...utils.snowflake import Snowflake
28
    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 70)'
Loading history...
29
    from ...utils import APINullable
30
31
32
class MessageActivityType(IntEnum):
33
    """
34
    The activity people can perform on a rich presence activity.
35
36
    Such an activity could for example be a spotify listen.
37
    """
38
    JOIN = 1
39
    SPECTATE = 2
40
    LISTEN = 3
41
    JOIN_REQUEST = 5
42
43
44
class MessageFlags(IntEnum):
45
    """
46
    Special message properties.
47
48
    :param CROSSPOSTED:
49
        the message has been published to subscribed
50
        channels (via Channel Following)
51
52
    :param IS_CROSSPOST:
53
        this message originated from a message
54
        in another channel (via Channel Following)
55
56
    :param SUPPRESS_EMBEDS:
57
        do not include any embeds when serializing this message
58
59
    :param SOURCE_MESSAGE_DELETED:
60
        the source message for this crosspost
61
        has been deleted (via Channel Following)
62
63
    :param URGENT:
64
        this message came from the urgent message system
65
66
    :param HAS_THREAD:
67
        this message has an associated thread,
68
        with the same id as the message
69
70
    :param EPHEMERAL:
71
        this message is only visible to the user
72
        who invoked the Interaction
73
74
    :param LOADING:
75
        this message is an Interaction
76
        Response and the bot is "thinking"
77
    """
78
    CROSSPOSTED = 1 << 0
79
    IS_CROSSPOST = 1 << 1
80
    SUPPRESS_EMBEDS = 1 << 2
81
    SOURCE_MESSAGE_DELETED = 1 << 3
82
    URGENT = 1 << 4
83
    HAS_THREAD = 1 << 5
84
    EPHEMERAL = 1 << 6
85
    LOADING = 1 << 7
86
87
88
class MessageType(IntEnum):
89
    """
90
    Represents the type of the message.
91
    """
92
    DEFAULT = 0
93
    RECIPIENT_ADD = 1
94
    RECIPIENT_REMOVE = 2
95
    CALL = 3
96
    CHANNEL_NAME_CHANGE = 4
97
    CHANNEL_ICON_CHANGE = 5
98
    CHANNEL_PINNED_MESSAGE = 6
99
    GUILD_MEMBER_JOIN = 7
100
    USER_PREMIUM_GUILD_SUBSCRIPTION = 8
101
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9
102
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10
103
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11
104
    CHANNEL_FOLLOW_ADD = 12
105
    GUILD_DISCOVERY_DISQUALIFIED = 14
106
    GUILD_DISCOVERY_REQUALIFIED = 15
107
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16
108
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17
109
    THREAD_CREATED = 18
110
    REPLY = 19
111
    APPLICATION_COMMAND = 20
112
113
    if GatewayConfig.version < 8:
114
        REPLY = 0
115
        APPLICATION_COMMAND = 0
116
117
    if GatewayConfig.version >= 9:
118
        THREAD_STARTER_MESSAGE = 21
119
120
    GUILD_INVITE_REMINDER = 22
121
122
123
@dataclass
124
class MessageActivity(APIObject):
125
    """
126
    Represents a Discord Message Activity object
127
128
    :param type:
129
        type of message activity
130
131
    :param party_id:
132
        party_id from a Rich Presence event
133
    """
134
    type: MessageActivityType
135
    party_id: APINullable[str] = MISSING
136
137
138
class AllowedMentionTypes(str, Enum):
139
    """
140
    The allowed mentions.
141
142
    :param ROLES:
143
        Controls role mentions
144
145
    :param USERS:
146
        Controls user mentions
147
148
    :param EVERYONE:
149
        Controls @everyone and @here mentions
150
    """
151
    ROLES = "roles"
152
    USERS = "users"
153
    EVERYONE = "everyone"
154
155
156
@dataclass
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
157
class AllowedMentions(APIObject):
158
    parse: List[AllowedMentionTypes]
159
    roles: List[Union[Role, Snowflake]]
160
    users: List[Union[User, Snowflake]]
161
    reply: bool = True
162
163
    @staticmethod
164
    def get_str_id(obj: Union[Snowflake, User, Role]) -> str:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
165
        if hasattr(obj, "id"):
166
            obj = obj.id
167
168
        return str(obj)
169
170
    def to_dict(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
171
        return {
172
            "parse": self.parse,
173
            "roles": list(map(self.get_str_id, self.roles)),
174
            "users": list(map(self.get_str_id, self.users)),
175
            "replied_user": self.reply
176
        }
177
178
179
@dataclass
0 ignored issues
show
best-practice introduced by
Too many instance attributes (30/7)
Loading history...
180
class UserMessage(APIObject):
181
    """
182
    Represents a message sent in a channel within Discord.
183
184
    :param id:
185
        id of the message
186
187
    :param channel_id:
188
        id of the channel the message was sent in
189
190
    :param guild_id:
191
        id of the guild the message was sent in
192
193
    :param author:
194
        the author of this message (not guaranteed to be a valid user)
195
196
    :param member:
197
        member properties for this message's author
198
199
    :param content:
200
        contents of the message
201
202
    :param timestamp:
203
        when this message was sent
204
205
    :param edited_timestamp:
206
        when this message was edited (or null if never)
207
208
    :param tts:
209
        whether this was a TTS message
210
211
    :param mention_everyone:
212
        whether this message mentions everyone
213
214
    :param mentions:
215
        users specifically mentioned in the message
216
217
    :param mention_roles:
218
        roles specifically mentioned in this message
219
220
    :param mention_channels:
221
        channels specifically mentioned in this message
222
223
    :param attachments:
224
        any attached files
225
226
    :param embeds:
227
        any embedded content
228
229
    :param reactions:
230
        reactions to the message
231
232
    :param nonce:
233
        user for validating a message was sent
234
235
    :param pinned:
236
        whether this message is pinned
237
238
    :param webhook_id:
239
        if the message is generated by a webhook,
240
        this is the webhook's id
241
242
    :param type:
243
        type of message
244
245
    :param activity:
246
        sent with Rich Presence-related chat embeds
247
248
    :param application:
249
        sent with Rich Presence-related chat embeds
250
251
    :param application_id:
252
        if the message is a response to an Interaction,
253
        this is the id of the interaction's application
254
255
    :param message_reference:
256
        data showing the source of a crosspost,
257
        channel follow add, pin, or reply message
258
259
    :param flags:
260
        message flags combined as a bitfield
261
262
    :param referenced_message:
263
        the message associated with the message_reference
264
265
    :param interaction:
266
        sent if the message is a response to an Interaction
267
268
    :param thread:
269
        the thread that was started from this message,
270
        includes thread member object
271
272
    :param components:
273
        sent if the message contains components like buttons,
274
        action rows, or other interactive components
275
276
    :param sticker_items:
277
        sent if the message contains stickers
278
    """
279
    id: Snowflake
280
    channel_id: Snowflake
281
    author: User
282
    content: str
283
    timestamp: Timestamp
284
    tts: bool
285
    mention_everyone: bool
286
    mentions: List[GuildMember]
287
    mention_roles: List[Role]
288
    attachments: List[Attachment]
289
    embeds: List[Embed]
290
    pinned: bool
291
    type: MessageType
292
    edited_timestamp: Optional[Timestamp] = None
293
294
    edited_timestamp: APINullable[Timestamp] = MISSING
295
    mention_channels: APINullable[List[ChannelMention]] = MISSING
296
    guild_id: APINullable[Snowflake] = MISSING
297
    member: APINullable[GuildMember] = MISSING
298
    reactions: APINullable[List[Reaction]] = MISSING
299
    nonce: APINullable[Union[int, str]] = MISSING
300
    webhook_id: APINullable[Snowflake] = MISSING
301
    activity: APINullable[MessageActivity] = MISSING
302
    application: APINullable[Application] = MISSING
303
    application_id: APINullable[Snowflake] = MISSING
304
    message_reference: APINullable[MessageReference] = MISSING
305
    flags: APINullable[MessageFlags] = MISSING
306
    referenced_message: APINullable[Optional[UserMessage]] = MISSING
307
    interaction: APINullable[MessageInteraction] = MISSING
308
    thread: APINullable[Channel] = MISSING
309
    components: APINullable[List[MessageComponent]] = MISSING
310
    sticker_items: APINullable[List[StickerItem]] = MISSING
311
312
    def __str__(self):
313
        return self.content
314
315
    async def get_most_recent(self):
316
        """
317
        Certain Discord methods don't return the message object data after its
318
        updated. This function can be run to get the most recent version of the
319
        message object.
320
        """
321
322
        return self.from_dict(
323
            construct_client_dict(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'construct_client_dict'
Loading history...
324
                self._client,
325
                await self._http.get(
326
                    f"/channels/{self.channel_id}/messages/{self.id}"
327
                )
328
            )
329
        )
330
331
    async def react(self, emoji: str):
332
        """
333
        Create a reaction for the message. Requires the
334
        ``READ_MESSAGE_HISTORY` itent. ``ADD_REACTIONS`` intent is required if
335
        nobody else has reacted using the emoji.
336
337
        :param emoji:
338
            Character for emoji. Does not need to be URL encoded.
339
        """
340
341
        await self._http.put(
342
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}/@me"
343
        )
344
345
    async def unreact(self, emoji: str):
346
        """
347
        Delete a reaction the current user has made for the message.
348
349
        :param emoji:
350
            Character for emoji. Does not need to be URL encoded.
351
        """
352
353
        await self._http.delete(
354
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}/@me"
355
        )
356
357
    async def remove_user_reaction(self, emoji: str, user_id: Snowflake):
358
        """
359
        Deletes another user's reaction. Requires the ``MANAGE_MESSAGES``
360
        intent.
361
362
        :param emoji:
363
            Character for emoji. Does not need to be URL encoded.
364
365
        :param user_id:
366
            User ID
367
        """
368
369
        await self._http.delete(
370
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
371
            f"/{user_id}"
372
        )
373
374
    async def get_reactions(
375
        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...
376
    ) -> Generator[User, None, None]:
377
        # 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...
378
        """
379
        Returns the users that reacted with this emoji.
380
381
        :param after:
382
            Get users after this user ID. Returns all users if not provided.
383
384
        :param limit:
385
            Max number of users to return (1-100). 25 if not provided.
386
        """
387
388
        for user in await self._http.get(
389
            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...
390
            f"?after={after}&limit={limit}"
391
        ):
392
            yield User.from_dict(user)
393
394
    async def remove_all_reactions(self):
395
        """
396
        Delete all reactions on a message. Requires the ``MANAGE_MESSAGES``
397
        intent.
398
        """
399
400
        await self._http.delete(
401
            f"/channels/{self.channel_id}/messages/{self.id}/reactions"
402
        )
403
404
    async def remove_emoji(self, emoji):
405
        """
406
        Deletes all the reactions for a given emoji on a message. Requires the
407
        ``MANAGE_MESSAGES`` intent.
408
409
        :param emoji:
410
            Character for emoji. Does not need to be URL encoded.
411
        """
412
413
        await self._http.delete(
414
            f"/channels/{self.channel_id}/messages/{self.id}/reactions/{emoji}"
415
        )
416
417
    # 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...
418
    async def edit(
0 ignored issues
show
best-practice introduced by
Too many arguments (7/5)
Loading history...
419
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
420
        content: str = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
421
        embeds: List[Embed] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
422
        flags: int = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
423
        allowed_mentions: AllowedMentions = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
424
        attachments: List[Attachment] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
425
        components: List[MessageComponent] = None
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
426
    ):
427
        """
428
        Edit a previously sent message. The fields content, embeds, and flags
429
        can be edited by the original message author. Other users can only
430
        edit flags and only if they have the ``MANAGE_MESSAGES`` permission in
431
        the corresponding channel. When specifying flags, ensure to include
432
        all previously set flags/bits in addition to ones that you are
433
        modifying.
434
435
        :param content:
436
            The message contents (up to 2000 characters)
437
438
        :param embeds:
439
            Embedded rich content (up to 6000 characters)
440
441
        :param flags:
442
            Edit the flags of a message (only ``SUPPRESS_EMBEDS`` can
443
            currently be set/unset)
444
445
        :param allowed_mentions:
446
            allowed mentions for the message
447
448
        :param attachments:
449
            attached files to keep
450
451
        :param components:
452
            the components to include with the message
453
        """
454
455
        data = {}
456
457
        def set_if_not_none(value: Any, name: str):
458
            if isinstance(value, APIObject):
459
                data[name] = value.to_dict()
460
            elif value is not None:
461
                data[name] = value
462
463
        set_if_not_none(content, "content")
464
        set_if_not_none(embeds, "embeds")
465
        set_if_not_none(flags, "flags")
466
        set_if_not_none(allowed_mentions, "allowed_mentions")
467
        set_if_not_none(attachments, "attachments")
468
        set_if_not_none(components, "components")
469
470
        await self._http.patch(
471
            f"/channels/{self.channel_id}/messages/{self.id}",
472
            data=data
473
        )
474
475
    async def delete(self):
476
        """
477
        Delete a message. Requires the ``MANAGE_MESSAGES`` intent if the
478
        message was not sent by the current user.
479
        """
480
481
        await self._http.delete(
482
            f"/channels/{self.channel_id}/messages/{self.id}"
483
        )
484