Passed
Pull Request — main (#473)
by Yohann
02:57
created

pincer.objects.message.embed.Embed.add_fields()   A

Complexity

Conditions 5

Size

Total Lines 52
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 52
rs 9.0333
c 0
b 0
f 0
cc 5
nop 6

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, field
7
from datetime import datetime
8
from re import match
9
from typing import TYPE_CHECKING
10
11
from ...exceptions import InvalidUrlError, EmbedFieldError
12
from ...utils.api_object import APIObject
13
from ...utils.types import MISSING
14
15
if TYPE_CHECKING:
16
    from typing import Any, Callable, Dict, Iterable, Union, Optional
17
18
    from ...utils.types import APINullable
19
20
21
def _field_size(_field: str) -> int:
22
    """
23
    The Discord API removes white space
24
        when counting the length of a field.
25
26
    Parameters
27
    ----------
28
    _field: str
29
        The field to check the length of.
30
31
    Returns
32
    -------
33
    int
34
        The length of the field without white space.
35
    """
36
    return 0 if _field == MISSING else len(_field.strip())
37
38
39
def _is_valid_url(url: str) -> bool:
40
    """
41
    Checks whether the url is a proper and valid url.
42
    (matches for http and attachment protocol.
43
44
    Parameters
45
    ----------
46
    url: str
47
        The url which must be checked.
48
49
    Returns
50
    -------
51
    bool
52
        Whether the provided url is valid.
53
    """
54
    stmt = (
55
        r"(http[s]|attachment)"
56
        r"?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|"
57
        r"(?:%[0-9a-fA-F][0-9a-fA-F]))+"
58
    )
59
60
    return bool(match(stmt, url))
61
62
63
def _check_if_valid_url(url: str):
64
    """Checks if the provided url is valid.
65
66
    Raises
67
    ------
68
    :class:`~pincer.exceptions.InvalidUrlError`
69
        if the url didn't match the url regex.
70
        (which means that it was malformed or didn't match the http/attachment
71
        protocol).
72
    """
73
    if not _is_valid_url(url):
74
        raise InvalidUrlError(
75
            "Url was malformed or wasn't of protocol http(s)/attachment."
76
        )
77
78
79
@dataclass(repr=False)
80
class EmbedAuthor:
81
    """Representation of the Embed Author class
82
83
    Attributes
84
    ----------
85
    name: APINullable[:class:`str`]
86
        Name of the author
87
    url: APINullable[:class:`str`]
88
        Url of the author
89
    icon_url: APINullable[:class:`str`]
90
        Url of the author icon
91
    proxy_icon_url: APINullable[:class:`str`]
92
        A proxied url of the author icon
93
    """
94
95
    icon_url: APINullable[str] = MISSING
96
    name: APINullable[str] = MISSING
97
    proxy_icon_url: APINullable[str] = MISSING
98
    url: APINullable[str] = MISSING
99
100
    def __post_init__(self):  # stop documenting special methods
101
        if _field_size(self.name) > 256:
102
            raise EmbedFieldError.from_desc("Author name", 256, len(self.name))
103
104
        _check_if_valid_url(self.url)
105
106
107
@dataclass(repr=False)
108
class EmbedImage:
109
    """Representation of the Embed Image class
110
111
    Attributes
112
    ----------
113
    url: APINullable[:class:`str`]
114
        Source url of the image
115
    proxy_url: APINullable[:class:`str`]
116
        A proxied url of the image
117
    height: APINullable[:class:`int`]
118
        Height of the image
119
    width: APINullable[:class:`int`]
120
        Width of the image
121
    """
122
123
    url: APINullable[str] = MISSING
124
    proxy_url: APINullable[str] = MISSING
125
    height: APINullable[int] = MISSING
126
    width: APINullable[int] = MISSING
127
128
    def __post_init__(self):
129
        _check_if_valid_url(self.url)
130
131
132
@dataclass(repr=False)
133
class EmbedProvider:
134
    """Representation of the Provider class
135
136
    Attributes
137
    ----------
138
    name: APINullable[:class:`str`]
139
        Name of the provider
140
    url: APINullable[:class:`str`]
141
        Url of the provider
142
    """
143
144
    name: APINullable[str] = MISSING
145
    url: APINullable[str] = MISSING
146
147
148
@dataclass(repr=False)
149
class EmbedThumbnail:
150
    """Representation of the Embed Thumbnail class
151
152
    Attributes
153
    ----------
154
    url: APINullable[:class:`str`]
155
        Source url of the thumbnail
156
    proxy_url: APINullable[:class:`str`]
157
        A proxied url of the thumbnail
158
    height: APINullable[:class:`int`]
159
        Height of the thumbnail
160
    width: APINullable[:class:`int`]
161
        Width of the thumbnail
162
    """
163
164
    url: APINullable[str] = MISSING
165
    proxy_url: APINullable[str] = MISSING
166
    height: APINullable[int] = MISSING
167
    width: APINullable[int] = MISSING
168
169
    def __post_init__(self):
170
        _check_if_valid_url(self.url)
171
172
173
@dataclass(repr=False)
174
class EmbedVideo:
175
    """Representation of the Embed Video class
176
177
    Attributes
178
    ----------
179
    url: APINullable[:class:`str`]
180
        Source url of the video
181
    proxy_url: APINullable[:class:`str`]
182
        A proxied url of the video
183
    height: APINullable[:class:`int`]
184
        Height of the video
185
    width: APINullable[:class:`int`]
186
        Width of the video
187
    """
188
189
    height: APINullable[int] = MISSING
190
    url: APINullable[str] = MISSING
191
    proxy_url: APINullable[str] = MISSING
192
    width: APINullable[int] = MISSING
193
194
195
@dataclass(repr=False)
196
class EmbedFooter:
197
    """Representation of the Embed Footer class
198
199
    Attributes
200
    ----------
201
    text: :class:`str`
202
        Footer text
203
    icon_url: APINullable[:class:`str`]
204
        Url of the footer icon
205
    proxy_icon_url: APINullable[:class:`str`]
206
        A proxied url of the footer icon
207
208
    Raises
209
    ------
210
    EmbedFieldError:
211
        Text is longer than 2048 characters
212
    """
213
214
    text: str
215
216
    icon_url: APINullable[str] = MISSING
217
    proxy_icon_url: APINullable[str] = MISSING
218
219
    def __post_init__(self):
220
        if _field_size(self.text) > 2048:
221
            raise EmbedFieldError.from_desc("Footer text", 2048, len(self.text))
222
223
224
@dataclass(repr=False)
225
class EmbedField:
226
    """Representation of the Embed Field class
227
228
    Attributes
229
    ----------
230
    name: :class:`str`
231
        The name of the field
232
    value: :class:`str`
233
        The text in the field
234
    inline: APINullable[:class:`bool`]
235
        Whether this field should display inline
236
237
    Raises
238
    ------
239
    EmbedFieldError:
240
        Name is longer than 256 characters
241
    EmbedFieldError:
242
        Description is longer than 1024 characters
243
    """
244
245
    name: str
246
    value: str
247
248
    inline: APINullable[bool] = MISSING
249
250
    def __post_init__(self):
251
        if _field_size(self.name) > 256:
252
            raise EmbedFieldError.from_desc("Field name", 256, len(self.name))
253
254
        if _field_size(self.value) > 1024:
255
            raise EmbedFieldError.from_desc(
256
                "Field value", 1024, len(self.value)
257
            )
258
259
260
# TODO: Handle Bad Request if embed that is too big is sent
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
261
# https://discord.com/developers/docs/resources/channel#embed-limits
262
# Currently ignored since I don't think it would make sense to put
263
# This with the Embed class
264
@dataclass(repr=False)
265
class Embed(APIObject):
0 ignored issues
show
best-practice introduced by
Too many instance attributes (13/7)
Loading history...
266
    """Representation of the discord Embed class
267
268
    Attributes
269
    ----------
270
    title: APINullable[:class:`str`]
271
        Embed title.
272
    description: APINullable[:class:`str`]
273
        Embed description.
274
    color: APINullable[:class:`int`]
275
        Embed color code.
276
    fields: List[:class:`~pincer.objects.message.embed.EmbedField`]
277
        Fields information.
278
    footer: APINullable[:class:`~pincer.objects.message.embed.EmbedFooter`]
279
        Footer information.
280
    image: APINullable[:class:`~pincer.objects.message.embed.EmbedImage`]
281
        Image information.
282
    provider: APINullable[:class:`~pincer.objects.message.embed.EmbedProvider`]
283
        Provider information.
284
    thumbnail: APINullable[:class:`~pincer.objects.message.embed.EmbedThumbnail`]
285
        Thumbnail information.
286
    timestamp: APINullable[:class:`str`]
287
        Timestamp of embed content in ISO format.
288
    url: APINullable[:class:`str`]
289
        Embed url.
290
    video: APINullable[:class:`~pincer.objects.message.embed.EmbedVideo`]
291
        Video information.
292
    type: APINullable[:class:`int`]
293
        type of message
294
    """
295
296
    # noqa: E501
297
298
    title: APINullable[str] = MISSING
299
    description: APINullable[str] = MISSING
300
    color: APINullable[int] = MISSING
301
    fields: list[EmbedField] = field(default_factory=list)
0 ignored issues
show
introduced by
Value 'list' is unsubscriptable
Loading history...
302
    footer: APINullable[EmbedFooter] = MISSING
303
    image: APINullable[EmbedImage] = MISSING
304
    provider: APINullable[EmbedProvider] = MISSING
305
    thumbnail: APINullable[EmbedThumbnail] = MISSING
306
    timestamp: APINullable[str] = MISSING
307
    author: APINullable[EmbedAuthor] = MISSING
308
    url: APINullable[str] = MISSING
309
    video: APINullable[EmbedVideo] = MISSING
310
    type: APINullable[int] = MISSING
311
312
    def __post_init__(self):
313
        if _field_size(self.title) > 256:
314
            raise EmbedFieldError.from_desc("Embed title", 256, len(self.title))
315
316
        if _field_size(self.description) > 4096:
317
            raise EmbedFieldError.from_desc(
318
                "Embed description", 4096, len(self.description)
319
            )
320
321
        if len(self.fields) > 25:
322
            raise EmbedFieldError.from_desc("Embed field", 25, len(self.fields))
323
324
    def set_timestamp(self, time: datetime) -> Embed:
325
        """Discord uses iso format for time stamps.
326
        This function will set the time to that format.
327
328
        Parameters
329
        ----------
330
        time : :class:`datetime.datetime`
331
            The datetime to set the timestamp to.
332
333
        Returns
334
        -------
335
        :class:`~pincer.objects.message.embed.Embed`
336
            The new embed object.
337
        """
338
        self.timestamp = time.isoformat()
339
340
        return self
341
342
    def set_author(
343
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
344
        icon_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
345
        name: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
346
        proxy_icon_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
347
        url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
348
    ) -> Embed:
349
        """Set the author message for the embed. This is the top
350
        field of the embed.
351
352
        Parameters
353
        ----------
354
        icon_url: APINullable[:class:`str`]
355
            The icon which will be next to the author name.
356
        name: APINullable[:class:`str`]
357
            The name for the author (so the message).
358
        proxy_icon_url: APINullable[:class:`str`]
359
            A proxied url of the author icon.
360
        url: APINullable[:class:`str`]
361
            The url for the author name, this will make the
362
            name field a link/url.
363
364
        Returns
365
        -------
366
        :class:`~pincer.objects.message.embed.Embed`
367
            The new embed object.
368
        """
369
370
        self.author = EmbedAuthor(
371
            icon_url=icon_url, name=name, proxy_icon_url=proxy_icon_url, url=url
372
        )
373
374
        return self
375
376
    def set_image(
377
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
378
        url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
379
        proxy_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
380
        height: APINullable[int] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
381
        width: APINullable[int] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
382
    ) -> Embed:
383
        """Sets an image for your embed.
384
385
        Parameters
386
        ----------
387
        url: APINullable[:class:`str`]
388
            Source url of the video
389
        proxy_url: APINullable[:class:`str`]
390
            A proxied url of the video
391
        height: APINullable[:class:`int`]
392
            Height of the video
393
        width: APINullable[:class:`int`]
394
            Width of the video
395
396
        Returns
397
        -------
398
        :class:`~pincer.objects.message.embed.Embed`
399
            The new embed object.
400
        """
401
        self.image = EmbedImage(
402
            height=height, url=url, proxy_url=proxy_url, width=width
403
        )
404
405
        return self
406
407
    def set_thumbnail(
408
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
409
        height: APINullable[int] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
410
        url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
411
        proxy_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
412
        width: APINullable[int] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
413
    ) -> Embed:  # ? it's normally smaller in the corner?
414
        """Sets the thumbnail of the embed.
415
        This image is bigger than the ``image`` property.
416
417
        url: APINullable[:class:`str`]
418
            Source url of the video
419
        proxy_url: APINullable[:class:`str`]
420
            A proxied url of the video
421
        height: APINullable[:class:`int`]
422
            Height of the video
423
        width: APINullable[:class:`int`]
424
            Width of the video
425
426
        Returns
427
        -------
428
        :class:`~pincer.objects.message.embed.Embed`
429
            The new embed object.
430
        """
431
        self.thumbnail = EmbedThumbnail(
432
            height=height, url=url, proxy_url=proxy_url, width=width
433
        )
434
435
        return self
436
437
    def set_footer(
438
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
439
        text: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
440
        icon_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
441
        proxy_icon_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
442
    ) -> Embed:
443
        """
444
        Sets the embed footer. This is at the bottom of your embed.
445
446
        Parameters
447
        ----------
448
        text: :class:`str`
449
            Footer text
450
        icon_url: APINullable[:class:`str`]
451
            Url of the footer icon
452
        proxy_icon_url: APINullable[:class:`str`]
453
            A proxied url of the footer icon
454
455
        Returns
456
        -------
457
        :class:`~pincer.objects.message.embed.Embed`
458
            The new embed object.
459
        """
460
        self.footer = EmbedFooter(
461
            text=text, icon_url=icon_url, proxy_icon_url=proxy_icon_url
462
        )
463
464
        return self
465
466
    def add_field(
467
        self, name: str, value: str, inline: APINullable[bool] = MISSING
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
468
    ) -> Embed:
469
        """Adds a field to the embed.
470
        An embed can contain up to 25 fields.
471
472
        Parameters
473
        ----------
474
        name: :class:`str`
475
            The name of the field
476
        value: :class:`str`
477
            The text in the field
478
        inline: APINullable[:class:`bool`]
479
            Whether this field should display inline
480
481
        Raises
482
        ------
483
        EmbedFieldError:
484
            Raised when there are more than 25 fields in the embed
485
        """
486
        _field = EmbedField(name=name, value=value, inline=inline)
487
488
        if len(self.fields) > 25:
489
            raise EmbedFieldError.from_desc(
490
                "Embed field", 25, len(self.fields) + 1
491
            )
492
493
        self.fields += [_field]
494
495
        return self
496
497
    def add_fields(
0 ignored issues
show
best-practice introduced by
Too many arguments (6/5)
Loading history...
498
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
499
        field_list: Union[Dict[Any, Any], Iterable[Iterable[Any, Any]]],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
500
        checks: Optional[Callable[[Any], Any]] = bool,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
501
        map_title: Optional[Callable[[Any], str]] = str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
502
        map_values: Optional[Callable[[Any], str]] = str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
503
        inline: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
504
    ) -> Embed:
505
        """Add multiple fields from a list,
506
        dict or generator of fields with possible mapping.
507
508
        Parameters
509
        ----------
510
        field_list: Union[Dict[Any, Any], Iterable[Iterable[Any, Any]]]
511
            A iterable or generator of the fields to add.
512
            If the field_list type is a dictionary, will take items.
513
        checks: Optional[Callable[[Any], Any]]
514
            A filter function to remove embed fields.
515
        map_title: Optional[Callable[[Any], :class:`str`]]
516
            A transform function to change the titles.
517
        map_values: Optional[Callable[[Any], :class:`str`]]
518
            A transform function to change the values.
519
        inline: :class:`bool`
520
            Whether to create grid or each field on a new line.
521
522
        Raises
523
        ------
524
        EmbedFieldError:
525
            Raised when there are more than 25 fields in the embed
526
527
        Returns
528
        -------
529
        :class:`~pincer.objects.message.embed.Embed`
530
            The new embed object.
531
        """
532
533
        if isinstance(field_list, dict):
534
            field_list: Iterable[Iterable[Any, Any]] = field_list.items()
535
536
        for field_name, field_value in field_list:
537
            val = (
538
                map_values(field_value)
539
                if not isinstance(field_value, tuple)
540
                else map_values(*field_value)
541
            )
542
543
            if checks(val):
544
                self.add_field(
545
                    name=map_title(field_name), value=val, inline=inline
546
                )
547
548
        return self
549