diff options
author | 2021-02-11 00:04:11 -0800 | |
---|---|---|
committer | 2021-02-11 00:04:11 -0800 | |
commit | 9f7070ffc63bcd632892e28865a4c07237076c4a (patch) | |
tree | 5a8653a5c897406f3ace8a6242bcf8c8bd772e8b | |
parent | Remove reference to `try - except` (diff) | |
parent | Merge pull request #1401 from python-discord/bug/1371/moderation-check (diff) |
Merge branch 'master' into patch-1
-rw-r--r-- | bot/api.py | 2 | ||||
-rw-r--r-- | bot/constants.py | 1 | ||||
-rw-r--r-- | bot/errors.py | 19 | ||||
-rw-r--r-- | bot/exts/backend/error_handler.py | 4 | ||||
-rw-r--r-- | bot/exts/moderation/infraction/_utils.py | 5 | ||||
-rw-r--r-- | bot/resources/tags/defaultdict.md | 21 | ||||
-rw-r--r-- | bot/resources/tags/dictcomps.md | 24 | ||||
-rw-r--r-- | bot/resources/tags/f-strings.md | 20 | ||||
-rw-r--r-- | bot/resources/tags/listcomps.md | 23 | ||||
-rw-r--r-- | bot/resources/tags/local-file.md | 23 | ||||
-rw-r--r-- | config-default.yml | 3 |
11 files changed, 103 insertions, 42 deletions
diff --git a/bot/api.py b/bot/api.py index d93f9f2ba..6ce9481f4 100644 --- a/bot/api.py +++ b/bot/api.py @@ -53,7 +53,7 @@ class APIClient: @staticmethod def _url_for(endpoint: str) -> str: - return f"{URLs.site_schema}{URLs.site_api}/{quote_url(endpoint)}" + return f"{URLs.site_api_schema}{URLs.site_api}/{quote_url(endpoint)}" async def close(self) -> None: """Close the aiohttp session.""" diff --git a/bot/constants.py b/bot/constants.py index 95e22513f..91e41e334 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -530,6 +530,7 @@ class URLs(metaclass=YAMLGetter): site: str site_api: str site_schema: str + site_api_schema: str # Site endpoints site_logs_view: str diff --git a/bot/errors.py b/bot/errors.py index 65d715203..ab0adcd42 100644 --- a/bot/errors.py +++ b/bot/errors.py @@ -1,4 +1,6 @@ -from typing import Hashable +from typing import Hashable, Union + +from discord import Member, User class LockedResourceError(RuntimeError): @@ -18,3 +20,18 @@ class LockedResourceError(RuntimeError): f"Cannot operate on {self.type.lower()} `{self.id}`; " "it is currently locked and in use by another operation." ) + + +class InvalidInfractedUser(Exception): + """ + Exception raised upon attempt of infracting an invalid user. + + Attributes: + `user` -- User or Member which is invalid + """ + + def __init__(self, user: Union[Member, User], reason: str = "User infracted is a bot."): + self.user = user + self.reason = reason + + super().__init__(reason) diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index ed7962b06..d2cce5558 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -12,7 +12,7 @@ from bot.api import ResponseCodeError from bot.bot import Bot from bot.constants import Colours, ERROR_REPLIES, Icons, MODERATION_ROLES from bot.converters import TagNameConverter -from bot.errors import LockedResourceError +from bot.errors import InvalidInfractedUser, LockedResourceError from bot.exts.backend.branding._errors import BrandingError from bot.utils.checks import InWhitelistCheckFailure @@ -82,6 +82,8 @@ class ErrorHandler(Cog): elif isinstance(e.original, BrandingError): await ctx.send(embed=self._get_error_embed(random.choice(ERROR_REPLIES), str(e.original))) return + elif isinstance(e.original, InvalidInfractedUser): + await ctx.send(f"Cannot infract that user. {e.original.reason}") else: await self.handle_unexpected_error(ctx, e.original) return # Exit early to avoid logging. diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index d0dc3f0a1..e766c1e5c 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -7,6 +7,7 @@ from discord.ext.commands import Context from bot.api import ResponseCodeError from bot.constants import Colours, Icons +from bot.errors import InvalidInfractedUser log = logging.getLogger(__name__) @@ -79,6 +80,10 @@ async def post_infraction( active: bool = True ) -> t.Optional[dict]: """Posts an infraction to the API.""" + if isinstance(user, (discord.Member, discord.User)) and user.bot: + log.trace(f"Posting of {infr_type} infraction for {user} to the API aborted. User is a bot.") + raise InvalidInfractedUser(user) + log.trace(f"Posting {infr_type} infraction for {user} to the API.") payload = { diff --git a/bot/resources/tags/defaultdict.md b/bot/resources/tags/defaultdict.md new file mode 100644 index 000000000..b6c3175fc --- /dev/null +++ b/bot/resources/tags/defaultdict.md @@ -0,0 +1,21 @@ +**[`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict)** + +The Python `defaultdict` type behaves almost exactly like a regular Python dictionary, but if you try to access or modify a missing key, the `defaultdict` will automatically insert the key and generate a default value for it. +While instantiating a `defaultdict`, we pass in a function that tells it how to create a default value for missing keys. + +```py +>>> from collections import defaultdict +>>> my_dict = defaultdict(int) +>>> my_dict +defaultdict(<class 'int'>, {}) +``` + +In this example, we've used the `int` class which returns 0 when called like a function, so any missing key will get a default value of 0. You can also get an empty list by default with `list` or an empty string with `str`. + +```py +>>> my_dict["foo"] +0 +>>> my_dict["bar"] += 5 +>>> my_dict +defaultdict(<class 'int'>, {'foo': 0, 'bar': 5}) +``` diff --git a/bot/resources/tags/dictcomps.md b/bot/resources/tags/dictcomps.md index 11867d77b..6c8018761 100644 --- a/bot/resources/tags/dictcomps.md +++ b/bot/resources/tags/dictcomps.md @@ -1,20 +1,14 @@ -**Dictionary Comprehensions** - -Like lists, there is a convenient way of creating dictionaries: +Dictionary comprehensions (*dict comps*) provide a convenient way to make dictionaries, just like list comps: ```py ->>> ftoc = {f: round((5/9)*(f-32)) for f in range(-40,101,20)} ->>> print(ftoc) -{-40: -40, -20: -29, 0: -18, 20: -7, 40: 4, 60: 16, 80: 27, 100: 38} +>>> {word.lower(): len(word) for word in ('I', 'love', 'Python')} +{'i': 1, 'love': 4, 'python': 6} ``` -In the example above, I created a dictionary of temperatures in Fahrenheit, that are mapped to (*roughly*) their Celsius counterpart within a small range. These comprehensions are useful for succinctly creating dictionaries from some other sequence. +The syntax is very similar to list comps except that you surround it with curly braces and have two expressions: one for the key and one for the value. -They are also very useful for inverting the key value pairs of a dictionary that already exists, such that the value in the old dictionary is now the key, and the corresponding key is now its value: +One can use a dict comp to change an existing dictionary using its `items` method ```py ->>> ctof = {v:k for k, v in ftoc.items()} ->>> print(ctof) -{-40: -40, -29: -20, -18: 0, -7: 20, 4: 40, 16: 60, 27: 80, 38: 100} +>>> first_dict = {'i': 1, 'love': 4, 'python': 6} +>>> {key.upper(): value * 2 for key, value in first_dict.items()} +{'I': 2, 'LOVE': 8, 'PYTHON': 12} ``` - -Also like list comprehensions, you can add a conditional to it in order to filter out items you don't want. - -For more information and examples, check [PEP 274](https://www.python.org/dev/peps/pep-0274/) +For more information and examples, check out [PEP 274](https://www.python.org/dev/peps/pep-0274/) diff --git a/bot/resources/tags/f-strings.md b/bot/resources/tags/f-strings.md index 69bc82487..5ccafe723 100644 --- a/bot/resources/tags/f-strings.md +++ b/bot/resources/tags/f-strings.md @@ -1,17 +1,9 @@ -In Python, there are several ways to do string interpolation, including using `%s`\'s and by using the `+` operator to concatenate strings together. However, because some of these methods offer poor readability and require typecasting to prevent errors, you should for the most part be using a feature called format strings. +Creating a Python string with your variables using the `+` operator can be difficult to write and read. F-strings (*format-strings*) make it easy to insert values into a string. If you put an `f` in front of the first quote, you can then put Python expressions between curly braces in the string. -**In Python 3.6 or later, we can use f-strings like this:** ```py -snake = "Pythons" -print(f"{snake} are some of the largest snakes in the world") -``` -**In earlier versions of Python or in projects where backwards compatibility is very important, use str.format() like this:** -```py -snake = "Pythons" - -# With str.format() you can either use indexes -print("{0} are some of the largest snakes in the world".format(snake)) - -# Or keyword arguments -print("{family} are some of the largest snakes in the world".format(family=snake)) +>>> snake = "pythons" +>>> number = 21 +>>> f"There are {number * 2} {snake} on the plane." +"There are 42 pythons on the plane." ``` +Note that even when you include an expression that isn't a string, like `number * 2`, Python will convert it to a string for you. diff --git a/bot/resources/tags/listcomps.md b/bot/resources/tags/listcomps.md index 0003b9bb8..ba00a4bf7 100644 --- a/bot/resources/tags/listcomps.md +++ b/bot/resources/tags/listcomps.md @@ -1,14 +1,19 @@ -Do you ever find yourself writing something like: +Do you ever find yourself writing something like this? ```py -even_numbers = [] -for n in range(20): - if n % 2 == 0: - even_numbers.append(n) +>>> squares = [] +>>> for n in range(5): +... squares.append(n ** 2) +[0, 1, 4, 9, 16] ``` -Using list comprehensions can simplify this significantly, and greatly improve code readability. If we rewrite the example above to use list comprehensions, it would look like this: +Using list comprehensions can make this both shorter and more readable. As a list comprehension, the same code would look like this: ```py -even_numbers = [n for n in range(20) if n % 2 == 0] +>>> [n ** 2 for n in range(5)] +[0, 1, 4, 9, 16] +``` +List comprehensions also get an `if` statement: +```python +>>> [n ** 2 for n in range(5) if n % 2 == 0] +[0, 4, 16] ``` -This also works for generators, dicts and sets by using `()` or `{}` instead of `[]`. -For more info, see [this pythonforbeginners.com post](http://www.pythonforbeginners.com/basics/list-comprehensions-in-python) or [PEP 202](https://www.python.org/dev/peps/pep-0202/). +For more info, see [this pythonforbeginners.com post](http://www.pythonforbeginners.com/basics/list-comprehensions-in-python). diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md new file mode 100644 index 000000000..ae41d589c --- /dev/null +++ b/bot/resources/tags/local-file.md @@ -0,0 +1,23 @@ +Thanks to discord.py, sending local files as embed images is simple. You have to create an instance of [`discord.File`](https://discordpy.readthedocs.io/en/latest/api.html#discord.File) class: +```py +# When you know the file exact path, you can pass it. +file = discord.File("/this/is/path/to/my/file.png", filename="file.png") + +# When you have the file-like object, then you can pass this instead path. +with open("/this/is/path/to/my/file.png", "rb") as f: + file = discord.File(f) +``` +When using the file-like object, you have to open it in `rb` mode. Also, in this case, passing `filename` to it is not necessary. +Please note that `filename` can't contain underscores. This is a Discord limitation. + +[`discord.Embed`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed) instances have a [`set_image`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed.set_image) method which can be used to set an attachment as an image: +```py +embed = discord.Embed() +# Set other fields +embed.set_image(url="attachment://file.png") # Filename here must be exactly same as attachment filename. +``` +After this, you can send an embed with an attachment to Discord: +```py +await channel.send(file=file, embed=embed) +``` +This example uses [`discord.TextChannel`](https://discordpy.readthedocs.io/en/latest/api.html#discord.TextChannel) for sending, but any instance of [`discord.abc.Messageable`](https://discordpy.readthedocs.io/en/latest/api.html#discord.abc.Messageable) can be used for sending. diff --git a/config-default.yml b/config-default.yml index d3b267159..d7415c821 100644 --- a/config-default.yml +++ b/config-default.yml @@ -335,7 +335,8 @@ keys: urls: # PyDis site vars site: &DOMAIN "pythondiscord.com" - site_api: &API !JOIN ["api.", *DOMAIN] + site_api: &API "pydis-api.default.svc.cluster.local" + site_api_schema: "http://" site_paste: &PASTE !JOIN ["paste.", *DOMAIN] site_schema: &SCHEMA "https://" site_staff: &STAFF !JOIN ["staff.", *DOMAIN] |