Completed
Push — main ( b12314...8ccbcd )
by Yohann
17s queued 14s
created

pincer.objects.embed._is_valid_url()   A

Complexity

Conditions 1

Size

Total Lines 13
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
# MIT License
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
#
3
# Copyright (c) 2021 Pincer
4
#
5
# Permission is hereby granted, free of charge, to any person obtaining
6
# a copy of this software and associated documentation files
7
# (the "Software"), to deal in the Software without restriction,
8
# including without limitation the rights to use, copy, modify, merge,
9
# publish, distribute, sublicense, and/or sell copies of the Software,
10
# and to permit persons to whom the Software is furnished to do so,
11
# subject to the following conditions:
12
#
13
# The above copyright notice and this permission notice shall be
14
# included in all copies or substantial portions of the Software.
15
#
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24
from __future__ import annotations
25
26
from dataclasses import dataclass, field
27
from datetime import datetime
28
from re import match
29
30
from pincer.exceptions import InvalidUrlError, EmbedFieldError
31
from pincer.utils.api_object import APIObject
32
from pincer.utils.constants import MISSING, APINullable
33
34
35
def _field_size(field: str) -> int:
0 ignored issues
show
Comprehensibility Bug introduced by
field is re-defining a name which is already available in the outer-scope (previously defined on line 26).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
36
    """
37
    The Discord API removes white space
38
        when counting the length of a field.
39
40
    :param field:
41
        The field.
42
43
    :return:
44
        Length of the string without white space.
45
    """
46
    return 0 if field == MISSING else len(field.strip())
47
48
49
def _is_valid_url(url: str) -> bool:
50
    """
51
    Checks whether the url is a proper and valid url.
52
    (matches for http and attachment protocol.
53
54
    :param url:
55
        The url which must be checked.
56
57
    :return:
58
        Whether the provided url is valid.
59
    """
60
    stmt = r"(http[s]|attachment)?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (105/100).

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

Loading history...
61
    return bool(match(stmt, url))
62
63
64
def _check_if_valid_url(url: str):
65
    """
66
    Checks if the provided url is valid.
67
68
    :raises 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
@dataclass
79
class EmbedAuthor:
80
    """
81
    Representation of the Embed Author class
82
83
    :param name:
84
        Name of the author
85
86
    :param url:
87
        Url of the author
88
89
    :param icon_url:
90
        Url of the author icon
91
92
    :param proxy_icon_url:
93
        A proxied url of the author icon
94
    """
95
    icon_url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
96
    name: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
97
    proxy_icon_url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
98
    url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
99
100
    def __post_init__(self):
101
        """
102
        :raises EmbedFieldError:
103
            Name is longer than 256 characters.
104
105
        :raises InvalidUrlError:
106
            if the url didn't match the url regex.
107
            (which means that it was malformed or didn't match the
108
            http/attachment protocol).
109
        """
110
        if _field_size(self.name) > 256:
111
            raise EmbedFieldError.from_desc("Author name", 256, len(self.name))
112
113
        _check_if_valid_url(self.url)
114
115
116
@dataclass
117
class EmbedImage:
118
    """
119
    Representation of the Embed Image class
120
121
    :param url:
122
        Source url of the image
123
124
    :param proxy_url:
125
        A proxied url of the image
126
127
    :param height:
128
        Height of the image
129
130
    :param width:
131
        Width of the image
132
    """
133
134
    height: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
135
    proxy_url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
136
    url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
137
    width: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
138
139
    def __post_init__(self):
140
        """
141
        :raises InvalidUrlError:
142
            if the url didn't match the url regex.
143
            (which means that it was malformed or didn't match the
144
            http/attachment protocol).
145
        """
146
        _check_if_valid_url(self.url)
147
148
149
@dataclass
150
class EmbedProvider:
151
    """
152
    Representation of the Provider class
153
154
    :param name:
155
        Name of the provider
156
157
    :param url:
158
        Url of the provider
159
    """
160
    name: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
161
    url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
162
163
164
@dataclass
165
class EmbedThumbnail:
166
    """
167
    Representation of the Embed Thumbnail class
168
169
    :param url:
170
        Source url of the thumbnail
171
172
    :param proxy_url:
173
        A proxied url of the thumbnail
174
175
    :param height:
176
        Height of the thumbnail
177
178
    :param width:
179
        Width of the thumbnail
180
181
    """
182
183
    height: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
184
    proxy_url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
185
    url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
186
    width: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
187
188
    def __post_init__(self):
189
        """
190
        :raises InvalidUrlError:
191
            if the url didn't match the url regex.
192
            (which means that it was malformed or didn't match the
193
            http/attachment protocol).
194
        """
195
        _check_if_valid_url(self.url)
196
197
198
@dataclass
199
class EmbedVideo:
200
    """
201
    Representation of the Embed Video class
202
203
    :param url:
204
        Source url of the video
205
206
    :param proxy_url:
207
        A proxied url of the video
208
209
    :param height:
210
        Height of the video
211
212
    :param width:
213
        Width of the video
214
    """
215
    height: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
216
    url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
217
    proxy_url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
218
    width: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
219
220
221
@dataclass
222
class EmbedFooter:
223
    """
224
    Representation of the Embed Footer class
225
226
    :param text:
227
        Footer text
228
229
    :param icon_url:
230
        Url of the footer icon
231
232
    :param proxy_icon_url:
233
        A proxied url of the footer icon
234
235
    :raises EmbedFieldError:
236
        Text is longer than 2048 characters
237
    """
238
239
    text: str
240
241
    icon_url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
242
    proxy_icon_url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
243
244
    def __post_init__(self):
245
        if _field_size(self.text) > 2048:
246
            raise EmbedFieldError.from_desc(
247
                "Footer text", 2048, len(self.text)
248
            )
249
250
251
@dataclass
252
class EmbedField:
253
    """
254
    Representation of the Embed Field class
255
256
    :param name:
257
        The name of the field
258
259
    :param value:
260
        The text in the field
261
262
    :param inline:
263
        Whether or not this field should display inline
264
265
    :raises EmbedFieldError:
266
        Name is longer than 256 characters
267
268
    :raises EmbedFieldError:
269
        Description is longer than 1024 characters
270
    """
271
272
    name: str
273
    value: str
274
275
    inline: APINullable[bool] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
276
277
    def __post_init__(self):
278
        if _field_size(self.name) > 256:
279
            raise EmbedFieldError.from_desc(
280
                "Field name", 256, len(self.name)
281
            )
282
283
        if _field_size(self.value) > 1024:
284
            raise EmbedFieldError.from_desc(
285
                "Field value", 1024, len(self.value)
286
            )
287
288
289
# 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...
290
# https://discord.com/developers/docs/resources/channel#embed-limits
291
# Currently ignored since I don't think it would make sense to put
292
# This with the Embed class
293
@dataclass
0 ignored issues
show
best-practice introduced by
Too many instance attributes (12/7)
Loading history...
294
class Embed(APIObject):
295
    """
296
    Representation of the discord Embed class
297
298
    :property author:
299
        Author information.
300
301
    :param color:
302
        Embed color code.
303
304
    :param description:
305
        Embed description.
306
307
    :param fields:
308
        Fields information.
309
310
    :param footer:
311
        Footer information.
312
313
    :param image:
314
        Image information.
315
316
    :param provider:
317
        Provider information.
318
319
    :param thumbnail:
320
        Thumbnail information.
321
322
    :param timestamp:
323
        Timestamp of embed content in ISO format.
324
325
    :param title:
326
        Embed title.
327
328
    :param url:
329
        Embed url.
330
331
    :param video:
332
        Video information.
333
    """
334
335
    author: APINullable[EmbedAuthor] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
336
    color: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
337
    description: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
338
    fields: list[EmbedField] = field(default_factory=list)
0 ignored issues
show
introduced by
Value 'list' is unsubscriptable
Loading history...
339
    footer: APINullable[EmbedFooter] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
340
    image: APINullable[EmbedImage] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
341
    provider: APINullable[EmbedProvider] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
342
    thumbnail: APINullable[EmbedThumbnail] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
343
    timestamp: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
344
    title: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
345
    url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
346
    video: APINullable[EmbedVideo] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
347
348
    def __post_init__(self):
349
        """
350
        :raises EmbedFieldError:
351
            Embed title is longer than 256 characters.
352
        :raises EmbedFieldError:
353
            Embed description is longer than 4096 characters.
354
        :raises EmbedFieldError:
355
            Embed has more than 25 fields.
356
        """
357
        if _field_size(self.title) > 256:
358
            raise EmbedFieldError.from_desc(
359
                "Embed title", 256, len(self.title)
360
            )
361
362
        if _field_size(self.description) > 4096:
363
            raise EmbedFieldError.from_desc(
364
                "Embed description", 4096, len(self.description)
365
            )
366
367
        if len(self.fields) > 25:
368
            raise EmbedFieldError.from_desc(
369
                "Embed field", 25, len(self.fields)
370
            )
371
372
    def set_timestamp(self, time: datetime) -> Embed:
373
        """
374
        Discord uses iso format for time stamps.
375
        This function will set the time to that format.
376
377
        :param time:
378
            A datetime object.
379
380
        :return: self
381
        """
382
        self.timestamp = time.isoformat()
383
384
        return self
385
386
    def set_author(
387
            self,
388
            icon_url: APINullable[str] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
389
            name: APINullable[str] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
390
            proxy_icon_url: APINullable[str] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
391
            url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
392
    ) -> Embed:
393
        """
394
        Set the author message for the embed. This is the top
395
        field of the embed.
396
397
        :param icon_url:
398
            The icon which will be next to the author name.
399
400
        :param name:
401
            The name for the author (so the message).
402
403
        :param proxy_icon_url:
404
            A proxied url of the author icon.
405
406
        :param url:
407
            The url for the author name, this will make the
408
            name field a link/url.
409
410
        :return: self
411
        """
412
413
        self.author = EmbedAuthor(
414
            icon_url=icon_url,
415
            name=name,
416
            proxy_icon_url=proxy_icon_url,
417
            url=url
418
        )
419
420
        return self
421
422
    def set_image(
423
            self,
424
            height: APINullable[int] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
425
            url: APINullable[str] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
426
            proxy_url: APINullable[str] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
427
            width: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
428
    ) -> Embed:
429
        """
430
        Sets an image for your embed.
431
432
        :param url:
433
            Source url of the video
434
435
        :param proxy_url:
436
            A proxied url of the video
437
438
        :param height:
439
            Height of the video
440
441
        :param width:
442
            Width of the video
443
444
        :return: self
445
        """
446
        self.video = EmbedImage(
447
            height=height,
448
            url=url,
449
            proxy_url=proxy_url,
450
            width=width
451
        )
452
453
        return self
454
455
    def set_thumbnail(
456
            self,
457
            height: APINullable[int] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
458
            url: APINullable[str] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
459
            proxy_url: APINullable[str] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
460
            width: APINullable[int] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
461
    ) -> Embed:
462
        """
463
        Sets the thumbnail of the embed.
464
        This image is bigger than the `image` property.
465
466
        :param url:
467
            Source url of the video
468
469
        :param proxy_url:
470
            A proxied url of the video
471
472
        :param height:
473
            Height of the video
474
475
        :param width:
476
            Width of the video
477
478
        :return self:
479
        """
480
        self.video = EmbedThumbnail(
481
            height=height,
482
            url=url,
483
            proxy_url=proxy_url,
484
            width=width
485
        )
486
487
        return self
488
489
    def set_footer(
490
            self,
491
            text: str,
492
            icon_url: APINullable[str] = MISSING,
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
493
            proxy_icon_url: APINullable[str] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
494
    ) -> Embed:
495
        """
496
        Sets the embed footer. This is at the bottom of your embed.
497
498
        :param text:
499
            Footer text
500
501
        :param icon_url:
502
            Url of the footer icon
503
504
        :param proxy_icon_url:
505
            A proxied url of the footer icon
506
507
        :return: self
508
        """
509
        self.footer = EmbedFooter(
510
            text=text,
511
            icon_url=icon_url,
512
            proxy_icon_url=proxy_icon_url
513
        )
514
515
        return self
516
517
    def add_field(
518
            self,
519
            name: str,
520
            value: str,
521
            inline: APINullable[bool] = MISSING
0 ignored issues
show
introduced by
Value 'APINullable' is unsubscriptable
Loading history...
522
    ) -> Embed:
523
        """
524
        Adds a field to the embed.
525
        An embed can contain up to 25 fields.
526
527
        :param name:
528
            The name of the field
529
530
        :param value:
531
            The text in the field
532
533
        :param inline:
534
            Whether or not this field should display inline
535
536
        :raises EmbedFieldError:
537
            Raised when there are more than 25 fields in the embed
538
        """
539
        _field = EmbedField(
540
            name=name,
541
            value=value,
542
            inline=inline
543
        )
544
545
        if len(self.fields) > 25:
546
            raise EmbedFieldError.from_desc(
547
                "Embed field", 25, len(self.fields) + 1
548
            )
549
550
        self.fields += [_field]
551
552
        return self
553