From df897e57ea0dbbaeb3e2a07a1e443c5b2df34c36 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Mon, 30 Nov 2020 22:26:23 +0300 Subject: Adds Member Checks Before Changing Voice Adds a check that checks if the user object is an instance of guild member, before performing guild operations. Adds tests. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/exts/moderation/infraction/infractions.py | 12 ++++++++++-- tests/bot/exts/moderation/infraction/test_infractions.py | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 18e937e87..78c326c47 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -257,6 +257,10 @@ class Infractions(InfractionScheduler, commands.Cog): self.mod_log.ignore(Event.member_update, user.id) async def action() -> None: + # Skip members that left the server + if not isinstance(user, Member): + return + await user.add_roles(self._muted_role, reason=reason) log.trace(f"Attempting to kick {user} from voice because they've been muted.") @@ -351,9 +355,13 @@ class Infractions(InfractionScheduler, commands.Cog): if reason: reason = textwrap.shorten(reason, width=512, placeholder="...") - await user.move_to(None, reason="Disconnected from voice to apply voiceban.") + action = None + + # Skip members that left the server + if isinstance(user, Member): + await user.move_to(None, reason="Disconnected from voice to apply voiceban.") + action = user.remove_roles(self._voice_verified_role, reason=reason) - action = user.remove_roles(self._voice_verified_role, reason=reason) await self.apply_infraction(ctx, infraction, user, action) # endregion diff --git a/tests/bot/exts/moderation/infraction/test_infractions.py b/tests/bot/exts/moderation/infraction/test_infractions.py index bf557a484..4ba5a4feb 100644 --- a/tests/bot/exts/moderation/infraction/test_infractions.py +++ b/tests/bot/exts/moderation/infraction/test_infractions.py @@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch from bot.constants import Event from bot.exts.moderation.infraction.infractions import Infractions -from tests.helpers import MockBot, MockContext, MockGuild, MockMember, MockRole +from tests.helpers import MockBot, MockContext, MockGuild, MockMember, MockRole, MockUser class TruncationTests(unittest.IsolatedAsyncioTestCase): @@ -164,6 +164,19 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): ) self.cog.apply_infraction.assert_awaited_once_with(self.ctx, {"foo": "bar"}, self.user, "my_return_value") + @patch("bot.exts.moderation.infraction._utils.get_active_infraction", return_value=None) + @patch("bot.exts.moderation.infraction._utils.post_infraction") + @patch("bot.exts.moderation.infraction.infractions.Infractions.apply_infraction") + async def test_voice_ban_user_left_guild(self, apply_infraction_mock, post_infraction_mock, _): + """Should voice ban user that left the guild without throwing an error.""" + infraction = {"foo": "bar"} + post_infraction_mock.return_value = {"foo": "bar"} + + user = MockUser() + await self.cog.voiceban(self.cog, self.ctx, user, reason=None) + post_infraction_mock.assert_called_once_with(self.ctx, user, "voice_ban", None, active=True) + apply_infraction_mock.assert_called_once_with(self.ctx, infraction, user, None) + async def test_voice_unban_user_not_found(self): """Should include info to return dict when user was not found from guild.""" self.guild.get_member.return_value = None -- cgit v1.2.3 From 5e93396e1655715187d176d1cfdd32aa54616a1a Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 16 Jan 2021 18:03:38 +0100 Subject: Add an allow_moderation_roles argument to the wait_for_deletion() util The `allow_moderation_roles` bool can be specified to allow anyone with a role in `MODERATION_ROLES` to delete the message. --- bot/utils/messages.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 42bde358d..b0b6cbf82 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -11,7 +11,7 @@ from discord.errors import HTTPException from discord.ext.commands import Context import bot -from bot.constants import Emojis, NEGATIVE_REPLIES +from bot.constants import Emojis, NEGATIVE_REPLIES, MODERATION_ROLES log = logging.getLogger(__name__) @@ -22,12 +22,15 @@ async def wait_for_deletion( deletion_emojis: Sequence[str] = (Emojis.trashcan,), timeout: float = 60 * 5, attach_emojis: bool = True, + allow_moderation_roles: bool = True ) -> None: """ Wait for up to `timeout` seconds for a reaction by any of the specified `user_ids` to delete the message. An `attach_emojis` bool may be specified to determine whether to attach the given `deletion_emojis` to the message in the given `context`. + An `allow_moderation_roles` bool may also be specified to allow anyone with a role in `MODERATION_ROLES` to delete + the message. """ if message.guild is None: raise ValueError("Message must be sent on a guild") @@ -46,6 +49,7 @@ async def wait_for_deletion( reaction.message.id == message.id and str(reaction.emoji) in deletion_emojis and user.id in user_ids + or allow_moderation_roles and any(role.id in MODERATION_ROLES for role in user.roles) ) with contextlib.suppress(asyncio.TimeoutError): -- cgit v1.2.3 From eb78b9c261ecbcea4b2ca5bb0d423f7b3bfb9a0f Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 16 Jan 2021 18:55:26 +0100 Subject: Restrict paginator usage to the author and moderators --- bot/pagination.py | 13 ++++++++++--- bot/utils/messages.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bot/pagination.py b/bot/pagination.py index 182b2fa76..09dbad7b5 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -8,6 +8,7 @@ from discord.abc import User from discord.ext.commands import Context, Paginator from bot import constants +from bot.constants import MODERATION_ROLES FIRST_EMOJI = "\u23EE" # [:track_previous:] LEFT_EMOJI = "\u2B05" # [:arrow_left:] @@ -210,6 +211,9 @@ class LinePaginator(Paginator): Pagination will also be removed automatically if no reaction is added for five minutes (300 seconds). + The interaction will be limited to `restrict_to_user` (ctx.author by default) or + to any user with a moderation role. + Example: >>> embed = discord.Embed() >>> embed.set_author(name="Some Operation", url=url, icon_url=icon) @@ -218,10 +222,10 @@ class LinePaginator(Paginator): def event_check(reaction_: discord.Reaction, user_: discord.Member) -> bool: """Make sure that this reaction is what we want to operate on.""" no_restrictions = ( - # Pagination is not restricted - not restrict_to_user # The reaction was by a whitelisted user - or user_.id == restrict_to_user.id + user_.id == restrict_to_user.id + # The reaction was by a moderator + or any(role.id in MODERATION_ROLES for role in user_.roles) ) return ( @@ -242,6 +246,9 @@ class LinePaginator(Paginator): scale_to_size=scale_to_size) current_page = 0 + if not restrict_to_user: + restrict_to_user = ctx.author + if not lines: if exception_on_empty_embed: log.exception("Pagination asked for empty lines iterable") diff --git a/bot/utils/messages.py b/bot/utils/messages.py index b0b6cbf82..832ad4d55 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -11,7 +11,7 @@ from discord.errors import HTTPException from discord.ext.commands import Context import bot -from bot.constants import Emojis, NEGATIVE_REPLIES, MODERATION_ROLES +from bot.constants import Emojis, MODERATION_ROLES, NEGATIVE_REPLIES log = logging.getLogger(__name__) -- cgit v1.2.3 From 37bfd7569cdeb0900ee131c22f67aed3f78692ba Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Wed, 20 Jan 2021 20:19:47 +0300 Subject: Cleans Up Tests --- tests/bot/exts/moderation/infraction/test_infractions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/bot/exts/moderation/infraction/test_infractions.py b/tests/bot/exts/moderation/infraction/test_infractions.py index 4ba5a4feb..13efee054 100644 --- a/tests/bot/exts/moderation/infraction/test_infractions.py +++ b/tests/bot/exts/moderation/infraction/test_infractions.py @@ -3,8 +3,9 @@ import unittest from unittest.mock import AsyncMock, MagicMock, Mock, patch from bot.constants import Event +from bot.exts.moderation.infraction import _utils from bot.exts.moderation.infraction.infractions import Infractions -from tests.helpers import MockBot, MockContext, MockGuild, MockMember, MockRole, MockUser +from tests.helpers import MockBot, MockContext, MockGuild, MockMember, MockRole, MockUser, autospec class TruncationTests(unittest.IsolatedAsyncioTestCase): @@ -164,9 +165,8 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): ) self.cog.apply_infraction.assert_awaited_once_with(self.ctx, {"foo": "bar"}, self.user, "my_return_value") - @patch("bot.exts.moderation.infraction._utils.get_active_infraction", return_value=None) - @patch("bot.exts.moderation.infraction._utils.post_infraction") - @patch("bot.exts.moderation.infraction.infractions.Infractions.apply_infraction") + @autospec(_utils, "post_infraction", "get_active_infraction", return_value=None) + @autospec(Infractions, "apply_infraction") async def test_voice_ban_user_left_guild(self, apply_infraction_mock, post_infraction_mock, _): """Should voice ban user that left the guild without throwing an error.""" infraction = {"foo": "bar"} @@ -175,7 +175,7 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): user = MockUser() await self.cog.voiceban(self.cog, self.ctx, user, reason=None) post_infraction_mock.assert_called_once_with(self.ctx, user, "voice_ban", None, active=True) - apply_infraction_mock.assert_called_once_with(self.ctx, infraction, user, None) + apply_infraction_mock.assert_called_once_with(self.cog, self.ctx, infraction, user, None) async def test_voice_unban_user_not_found(self): """Should include info to return dict when user was not found from guild.""" -- cgit v1.2.3 From 06c5eba63f735237b44f2b1da9944bc7cddd2177 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Thu, 21 Jan 2021 01:26:41 +0300 Subject: Restructures Voice Ban Action Updates the voice ban action so the infraction pardoning is always run, and so all operations are handled in the _scheduler. Updates tests. --- bot/exts/moderation/infraction/infractions.py | 11 ++++--- .../exts/moderation/infraction/test_infractions.py | 37 +++++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 78c326c47..be4327bb0 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -355,14 +355,15 @@ class Infractions(InfractionScheduler, commands.Cog): if reason: reason = textwrap.shorten(reason, width=512, placeholder="...") - action = None + async def action() -> None: + # Skip members that left the server + if not isinstance(user, Member): + return - # Skip members that left the server - if isinstance(user, Member): await user.move_to(None, reason="Disconnected from voice to apply voiceban.") - action = user.remove_roles(self._voice_verified_role, reason=reason) + await user.remove_roles(self._voice_verified_role, reason=reason) - await self.apply_infraction(ctx, infraction, user, action) + await self.apply_infraction(ctx, infraction, user, action()) # endregion # region: Base pardon functions diff --git a/tests/bot/exts/moderation/infraction/test_infractions.py b/tests/bot/exts/moderation/infraction/test_infractions.py index 13efee054..86c2617ea 100644 --- a/tests/bot/exts/moderation/infraction/test_infractions.py +++ b/tests/bot/exts/moderation/infraction/test_infractions.py @@ -1,6 +1,7 @@ +import inspect import textwrap import unittest -from unittest.mock import AsyncMock, MagicMock, Mock, patch +from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch from bot.constants import Event from bot.exts.moderation.infraction import _utils @@ -133,20 +134,29 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): self.assertIsNone(await self.cog.apply_voice_ban(self.ctx, self.user, "foobar")) self.cog.mod_log.ignore.assert_called_once_with(Event.member_update, self.user.id) + async def action_tester(self, action, reason: str) -> None: + """Helper method to test voice ban action.""" + self.assertTrue(inspect.iscoroutine(action)) + await action + + self.user.move_to.assert_called_once_with(None, reason=ANY) + self.user.remove_roles.assert_called_once_with(self.cog._voice_verified_role, reason=reason) + @patch("bot.exts.moderation.infraction.infractions._utils.post_infraction") @patch("bot.exts.moderation.infraction.infractions._utils.get_active_infraction") async def test_voice_ban_apply_infraction(self, get_active_infraction, post_infraction_mock): """Should ignore Voice Verified role removing.""" self.cog.mod_log.ignore = MagicMock() self.cog.apply_infraction = AsyncMock() - self.user.remove_roles = MagicMock(return_value="my_return_value") get_active_infraction.return_value = None post_infraction_mock.return_value = {"foo": "bar"} - self.assertIsNone(await self.cog.apply_voice_ban(self.ctx, self.user, "foobar")) - self.user.remove_roles.assert_called_once_with(self.cog._voice_verified_role, reason="foobar") - self.cog.apply_infraction.assert_awaited_once_with(self.ctx, {"foo": "bar"}, self.user, "my_return_value") + reason = "foobar" + self.assertIsNone(await self.cog.apply_voice_ban(self.ctx, self.user, reason)) + self.cog.apply_infraction.assert_awaited_once_with(self.ctx, {"foo": "bar"}, self.user, ANY) + + await self.action_tester(self.cog.apply_infraction.call_args[0][-1], reason) @patch("bot.exts.moderation.infraction.infractions._utils.post_infraction") @patch("bot.exts.moderation.infraction.infractions._utils.get_active_infraction") @@ -154,16 +164,16 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): """Should truncate reason for voice ban.""" self.cog.mod_log.ignore = MagicMock() self.cog.apply_infraction = AsyncMock() - self.user.remove_roles = MagicMock(return_value="my_return_value") get_active_infraction.return_value = None post_infraction_mock.return_value = {"foo": "bar"} self.assertIsNone(await self.cog.apply_voice_ban(self.ctx, self.user, "foobar" * 3000)) - self.user.remove_roles.assert_called_once_with( - self.cog._voice_verified_role, reason=textwrap.shorten("foobar" * 3000, 512, placeholder="...") - ) - self.cog.apply_infraction.assert_awaited_once_with(self.ctx, {"foo": "bar"}, self.user, "my_return_value") + self.cog.apply_infraction.assert_awaited_once_with(self.ctx, {"foo": "bar"}, self.user, ANY) + + # Test action + action = self.cog.apply_infraction.call_args[0][-1] + await self.action_tester(action, textwrap.shorten("foobar" * 3000, 512, placeholder="...")) @autospec(_utils, "post_infraction", "get_active_infraction", return_value=None) @autospec(Infractions, "apply_infraction") @@ -175,7 +185,12 @@ class VoiceBanTests(unittest.IsolatedAsyncioTestCase): user = MockUser() await self.cog.voiceban(self.cog, self.ctx, user, reason=None) post_infraction_mock.assert_called_once_with(self.ctx, user, "voice_ban", None, active=True) - apply_infraction_mock.assert_called_once_with(self.cog, self.ctx, infraction, user, None) + apply_infraction_mock.assert_called_once_with(self.cog, self.ctx, infraction, user, ANY) + + # Test action + action = self.cog.apply_infraction.call_args[0][-1] + self.assertTrue(inspect.iscoroutine(action)) + await action async def test_voice_unban_user_not_found(self): """Should include info to return dict when user was not found from guild.""" -- cgit v1.2.3 From a41b932436b8b5c07c0f5ea3bd7ee886fa01e88e Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Thu, 21 Jan 2021 02:25:46 +0300 Subject: Updates Apply Infraction Docstring --- bot/exts/moderation/infraction/_scheduler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 242b2d30f..a73f2e8da 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -102,6 +102,7 @@ class InfractionScheduler: """ Apply an infraction to the user, log the infraction, and optionally notify the user. + `action_coro`, if not provided, will result in the infraction not getting scheduled for deletion. `user_reason`, if provided, will be sent to the user in place of the infraction reason. `additional_info` will be attached to the text field in the mod-log embed. -- cgit v1.2.3 From 576146af6a01b387c6b0abec1309169aa77a5bf2 Mon Sep 17 00:00:00 2001 From: PH-KDX <50588793+PH-KDX@users.noreply.github.com> Date: Fri, 22 Jan 2021 16:23:39 +0100 Subject: Create voice.md This tag would provide info for users which are not voice-verified, so that they can easily be directed toward the appropriate channel. --- bot/resources/tags/voice.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 bot/resources/tags/voice.md diff --git a/bot/resources/tags/voice.md b/bot/resources/tags/voice.md new file mode 100644 index 000000000..3d88b0c71 --- /dev/null +++ b/bot/resources/tags/voice.md @@ -0,0 +1,3 @@ +**Voice verification** + +Can’t talk in voice chat? Check out <#764802555427029012> to get access. The criteria for verifying are specified there. -- cgit v1.2.3 From eb45e9895a0bf1b294bbdca9b154ee433942989a Mon Sep 17 00:00:00 2001 From: PH-KDX <50588793+PH-KDX@users.noreply.github.com> Date: Sat, 23 Jan 2021 19:46:09 +0100 Subject: Rename voice.md to voice-verification.md --- bot/resources/tags/voice-verification.md | 3 +++ bot/resources/tags/voice.md | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 bot/resources/tags/voice-verification.md delete mode 100644 bot/resources/tags/voice.md diff --git a/bot/resources/tags/voice-verification.md b/bot/resources/tags/voice-verification.md new file mode 100644 index 000000000..3d88b0c71 --- /dev/null +++ b/bot/resources/tags/voice-verification.md @@ -0,0 +1,3 @@ +**Voice verification** + +Can’t talk in voice chat? Check out <#764802555427029012> to get access. The criteria for verifying are specified there. diff --git a/bot/resources/tags/voice.md b/bot/resources/tags/voice.md deleted file mode 100644 index 3d88b0c71..000000000 --- a/bot/resources/tags/voice.md +++ /dev/null @@ -1,3 +0,0 @@ -**Voice verification** - -Can’t talk in voice chat? Check out <#764802555427029012> to get access. The criteria for verifying are specified there. -- cgit v1.2.3 From 46ff6949ac583fb705c52431297e6c7a47ad231b Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sun, 24 Jan 2021 17:39:36 +0100 Subject: Make sure that the paginator doesn't choke on DMs --- bot/pagination.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/pagination.py b/bot/pagination.py index 09dbad7b5..3b16cc9ff 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -4,6 +4,7 @@ import typing as t from contextlib import suppress import discord +from discord import Member from discord.abc import User from discord.ext.commands import Context, Paginator @@ -225,7 +226,7 @@ class LinePaginator(Paginator): # The reaction was by a whitelisted user user_.id == restrict_to_user.id # The reaction was by a moderator - or any(role.id in MODERATION_ROLES for role in user_.roles) + or isinstance(user_, Member) and any(role.id in MODERATION_ROLES for role in user_.roles) ) return ( -- cgit v1.2.3 From 9dea9e6fa57682b94b93f7ff6567d58862ada7ed Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 28 Jan 2021 21:57:54 +0000 Subject: catch the response error and deal with it --- bot/converters.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/converters.py b/bot/converters.py index d0a9731d6..880b1fe38 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -575,7 +575,10 @@ class Infraction(Converter): return infractions[0] else: - return await ctx.bot.api_client.get(f"bot/infractions/{arg}") + try: + return await ctx.bot.api_client.get(f"bot/infractions/{arg}") + except ResponseCodeError: + raise BadArgument("The provided infraction could not be found.") Expiry = t.Union[Duration, ISODateTime] -- cgit v1.2.3 From 3f0565b04fa08f7d799bc5b05af6f0926800aaa1 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 29 Jan 2021 22:16:51 +0000 Subject: handle within the error handler --- bot/converters.py | 5 +---- bot/exts/backend/error_handler.py | 6 ++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index 880b1fe38..d0a9731d6 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -575,10 +575,7 @@ class Infraction(Converter): return infractions[0] else: - try: - return await ctx.bot.api_client.get(f"bot/infractions/{arg}") - except ResponseCodeError: - raise BadArgument("The provided infraction could not be found.") + return await ctx.bot.api_client.get(f"bot/infractions/{arg}") Expiry = t.Union[Duration, ISODateTime] diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index b8bb3757f..8923a6b3d 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -85,6 +85,12 @@ class ErrorHandler(Cog): else: await self.handle_unexpected_error(ctx, e.original) return # Exit early to avoid logging. + elif isinstance(e, errors.ConversionError): + if isinstance(e.original, ResponseCodeError): + await self.handle_api_error(ctx, e.original) + else: + await self.handle_unexpected_error(ctx, e.original) + return # Exit early to avoid logging. elif not isinstance(e, errors.DisabledCommand): # ConversionError, MaxConcurrencyReached, ExtensionError await self.handle_unexpected_error(ctx, e) -- cgit v1.2.3 From acc8bcfd2371035b315538d525a7b9231664fd34 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 29 Jan 2021 22:30:01 +0000 Subject: Remove ConversionError from comment, as its now handled above. --- bot/exts/backend/error_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index 8923a6b3d..ed7962b06 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -92,7 +92,7 @@ class ErrorHandler(Cog): await self.handle_unexpected_error(ctx, e.original) return # Exit early to avoid logging. elif not isinstance(e, errors.DisabledCommand): - # ConversionError, MaxConcurrencyReached, ExtensionError + # MaxConcurrencyReached, ExtensionError await self.handle_unexpected_error(ctx, e) return # Exit early to avoid logging. -- cgit v1.2.3 From ced0af2b2936b1fab4f5601bd7b1c00bbbd9130a Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 30 Jan 2021 15:19:17 +0100 Subject: Make sure that restrictions also applies to moderators Without this, if a moderator add a reaction to any message, all the messages currently listening for reaction will pass the check since the user has a moderation role. --- bot/utils/messages.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 832ad4d55..077dd9569 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -48,8 +48,10 @@ async def wait_for_deletion( return ( reaction.message.id == message.id and str(reaction.emoji) in deletion_emojis - and user.id in user_ids - or allow_moderation_roles and any(role.id in MODERATION_ROLES for role in user.roles) + and ( + user.id in user_ids + or allow_moderation_roles and any(role.id in MODERATION_ROLES for role in user.roles) + ) ) with contextlib.suppress(asyncio.TimeoutError): -- cgit v1.2.3 From 8130b34fe0d9c59b75294844b9f05942a4e6b5ad Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 30 Jan 2021 22:24:36 +0000 Subject: Protect against overflows caused by large expirations --- bot/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/converters.py b/bot/converters.py index d0a9731d6..0d9a519df 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -350,7 +350,7 @@ class Duration(DurationDelta): try: return now + delta - except ValueError: + except (ValueError, OverflowError): raise BadArgument(f"`{duration}` results in a datetime outside the supported range.") -- cgit v1.2.3 From 01ad92b2d3c4a3679f86ac8889736fa873e00ae4 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Sun, 31 Jan 2021 17:35:30 +0100 Subject: Re created file from last point --- bot/resources/tags/empty-json.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 bot/resources/tags/empty-json.md diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md new file mode 100644 index 000000000..a54d3d71a --- /dev/null +++ b/bot/resources/tags/empty-json.md @@ -0,0 +1,27 @@ +When creating a new JSON file you might run into the following error. + +`JSONDecodeError: Expecting value: line 1 column 1 (char 0)` + +In short this error means your JSON is invalid in it's current state. +A JSON may never be completely empty and must always at least have one of the following items. + +``` +object +array +string +number +"true" +"false" +"null" +``` + +To resolve this issue, you create one of the above values in your JSON. It is very common to use `{}` to make an object. Adding the following to your JSON should resolve this issue. + +```json +{ + + +} +``` + +Make sure to put all your data between the `{}`, just like you would when making a dictionary. \ No newline at end of file -- cgit v1.2.3 From 5a6f77fde58f024ea151adfdc6a5745eeb0046cd Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Sun, 31 Jan 2021 17:41:06 +0100 Subject: name fix, added suggestions from previous PR --- bot/resources/tags/empty-json.md | 27 --------------------------- bot/resources/tags/empty_json.md | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 27 deletions(-) delete mode 100644 bot/resources/tags/empty-json.md create mode 100644 bot/resources/tags/empty_json.md diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md deleted file mode 100644 index a54d3d71a..000000000 --- a/bot/resources/tags/empty-json.md +++ /dev/null @@ -1,27 +0,0 @@ -When creating a new JSON file you might run into the following error. - -`JSONDecodeError: Expecting value: line 1 column 1 (char 0)` - -In short this error means your JSON is invalid in it's current state. -A JSON may never be completely empty and must always at least have one of the following items. - -``` -object -array -string -number -"true" -"false" -"null" -``` - -To resolve this issue, you create one of the above values in your JSON. It is very common to use `{}` to make an object. Adding the following to your JSON should resolve this issue. - -```json -{ - - -} -``` - -Make sure to put all your data between the `{}`, just like you would when making a dictionary. \ No newline at end of file diff --git a/bot/resources/tags/empty_json.md b/bot/resources/tags/empty_json.md new file mode 100644 index 000000000..36511abb6 --- /dev/null +++ b/bot/resources/tags/empty_json.md @@ -0,0 +1,24 @@ +When creating a new JSON file you might run into the following error. + +`JSONDecodeError: Expecting value: line 1 column 1 (char 0)` + +In short, this means that your JSON is invalid in its current state. This could very well happen because the file is just new and empty. +A JSON may never be completely empty. It is recommended to have at least one of the following in your json: + +``` +object +array +``` + +To resolve this issue, you create one of the above values in your JSON. It is very common to use `{}` to make an object, which is similar to a dictionary in python. +When this is added to your JSON, it will look like this: + +```json +{ + + +} +``` + +The error is resolved now. +Make sure to put all your data between the `{}`, just like you would when making a dictionary. \ No newline at end of file -- cgit v1.2.3 From 4704344807bf56a544ddeb8cdc592bcb69675cf6 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Sun, 31 Jan 2021 17:48:09 +0100 Subject: Fixed EOF --- bot/resources/tags/empty_json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/empty_json.md b/bot/resources/tags/empty_json.md index 36511abb6..8fb7c4e23 100644 --- a/bot/resources/tags/empty_json.md +++ b/bot/resources/tags/empty_json.md @@ -21,4 +21,4 @@ When this is added to your JSON, it will look like this: ``` The error is resolved now. -Make sure to put all your data between the `{}`, just like you would when making a dictionary. \ No newline at end of file +Make sure to put all your data between the `{}`, just like you would when making a dictionary. -- cgit v1.2.3 From 9931ebc20ff3dcda11cd2bca338c3a798e6f6b17 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Mon, 1 Feb 2021 18:29:48 +0100 Subject: Removed extra whitespace line in last example --- bot/resources/tags/empty_json.md | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/resources/tags/empty_json.md b/bot/resources/tags/empty_json.md index 8fb7c4e23..d5e0f843f 100644 --- a/bot/resources/tags/empty_json.md +++ b/bot/resources/tags/empty_json.md @@ -16,7 +16,6 @@ When this is added to your JSON, it will look like this: ```json { - } ``` -- cgit v1.2.3 From dc8a5d4084e124f9a7d6e0d31658b6eb0637bccc Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Mon, 1 Feb 2021 18:41:09 +0100 Subject: Changed some wording --- bot/resources/tags/empty_json.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/resources/tags/empty_json.md b/bot/resources/tags/empty_json.md index d5e0f843f..45df3fd54 100644 --- a/bot/resources/tags/empty_json.md +++ b/bot/resources/tags/empty_json.md @@ -2,16 +2,16 @@ When creating a new JSON file you might run into the following error. `JSONDecodeError: Expecting value: line 1 column 1 (char 0)` -In short, this means that your JSON is invalid in its current state. This could very well happen because the file is just new and empty. -A JSON may never be completely empty. It is recommended to have at least one of the following in your json: +In short, this means that your JSON is invalid in its current state. This could very well happen because the file is just new and completely empty. +Whilst the JSON data may be empty, the .json file must not. It is recommended to have at least one of the following data types in your .json file: ``` object array ``` -To resolve this issue, you create one of the above values in your JSON. It is very common to use `{}` to make an object, which is similar to a dictionary in python. -When this is added to your JSON, it will look like this: +To resolve this issue, you create one of the above data types in your .json file. It is very common to use `{}` to make an object, which works similar to a dictionary in python. +When this is added to your .json file, it will look like this: ```json { -- cgit v1.2.3 From 5040129e8b32ace05d8b391c1753a96769555bab Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Mon, 1 Feb 2021 18:42:46 +0100 Subject: added some more clarification --- bot/resources/tags/empty_json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/empty_json.md b/bot/resources/tags/empty_json.md index 45df3fd54..eaeafeb18 100644 --- a/bot/resources/tags/empty_json.md +++ b/bot/resources/tags/empty_json.md @@ -3,7 +3,7 @@ When creating a new JSON file you might run into the following error. `JSONDecodeError: Expecting value: line 1 column 1 (char 0)` In short, this means that your JSON is invalid in its current state. This could very well happen because the file is just new and completely empty. -Whilst the JSON data may be empty, the .json file must not. It is recommended to have at least one of the following data types in your .json file: +Whilst the JSON data, the data you wish to store, may be empty, the .json file must not. It is recommended to have at least one of the following data types in your .json file: ``` object -- cgit v1.2.3 From 7b4833ed11f96d1e8bc26ec3997ee42956dca230 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Mon, 1 Feb 2021 18:45:15 +0100 Subject: suggestion for: more friendly/less personal suggestion. --- bot/resources/tags/empty_json.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/resources/tags/empty_json.md b/bot/resources/tags/empty_json.md index eaeafeb18..9e5c5fd4f 100644 --- a/bot/resources/tags/empty_json.md +++ b/bot/resources/tags/empty_json.md @@ -3,14 +3,14 @@ When creating a new JSON file you might run into the following error. `JSONDecodeError: Expecting value: line 1 column 1 (char 0)` In short, this means that your JSON is invalid in its current state. This could very well happen because the file is just new and completely empty. -Whilst the JSON data, the data you wish to store, may be empty, the .json file must not. It is recommended to have at least one of the following data types in your .json file: +Whilst the JSON data, the data you wish to store, may be empty, the .json file must not. You most likely want to use one of the following data types in your .json file: ``` object array ``` -To resolve this issue, you create one of the above data types in your .json file. It is very common to use `{}` to make an object, which works similar to a dictionary in python. +To resolve this issue, create one of the above data types in your .json file. It is very common to use `{}` to make an object, which works similar to a dictionary in python. When this is added to your .json file, it will look like this: ```json -- cgit v1.2.3 From 7432f580300c58321d3a37695779026fe5e51f8c Mon Sep 17 00:00:00 2001 From: wookie184 Date: Tue, 2 Feb 2021 14:16:17 +0000 Subject: Add tag on float imprecision Adds a tag on float imprecision --- bot/resources/tags/floats.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 bot/resources/tags/floats.md diff --git a/bot/resources/tags/floats.md b/bot/resources/tags/floats.md new file mode 100644 index 000000000..4b0651930 --- /dev/null +++ b/bot/resources/tags/floats.md @@ -0,0 +1,19 @@ +**Floating Point Arithmetic** +You may have noticed that when doing arithmetic with floats in Python you sometimes get strange results, like this: +```python +>>> 0.1 + 0.2 +0.30000000000000004 +``` +**Why this happens** +Internally your computer stores floats as as binary fractions. Many decimal values cannot be stored as exact binary fractions, which means an approximation has to be used. + +**How you can avoid this** +If you require an exact decimal representation, you can use the [decimal](https://docs.python.org/3/library/decimal.html) or [fractions](https://docs.python.org/3/library/fractions.html) module. Here is an example using the decimal module: +```python +>>> from decimal import Decimal +>>> Decimal('0.1') + Decimal('0.2') +Decimal('0.3') +``` +Note that we enter in the number we want as a string so we don't pass on the imprecision from the float. + +For more details on why this happens check out this [page in the python docs](https://docs.python.org/3/tutorial/floatingpoint.html) or this [Computerphile video](https://www.youtube.com/watch/PZRI1IfStY0). -- cgit v1.2.3 From 9c1e5fd22e71f919bcbb7e2215430a0b4d518310 Mon Sep 17 00:00:00 2001 From: wookie184 Date: Wed, 3 Feb 2021 10:00:42 +0000 Subject: Mention math.isclose and add an example --- bot/resources/tags/floats.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bot/resources/tags/floats.md b/bot/resources/tags/floats.md index 4b0651930..7129b91bb 100644 --- a/bot/resources/tags/floats.md +++ b/bot/resources/tags/floats.md @@ -8,12 +8,13 @@ You may have noticed that when doing arithmetic with floats in Python you someti Internally your computer stores floats as as binary fractions. Many decimal values cannot be stored as exact binary fractions, which means an approximation has to be used. **How you can avoid this** -If you require an exact decimal representation, you can use the [decimal](https://docs.python.org/3/library/decimal.html) or [fractions](https://docs.python.org/3/library/fractions.html) module. Here is an example using the decimal module: + You can use [math.isclose](https://docs.python.org/3/library/math.html#math.isclose) to check if two floats are close, or to get an exact decimal representation, you can use the [decimal](https://docs.python.org/3/library/decimal.html) or [fractions](https://docs.python.org/3/library/fractions.html) module. Here are some examples: ```python ->>> from decimal import Decimal ->>> Decimal('0.1') + Decimal('0.2') +>>> math.isclose(0.1 + 0.2, 0.3) +True +>>> decimal.Decimal('0.1') + decimal.Decimal('0.2') Decimal('0.3') ``` -Note that we enter in the number we want as a string so we don't pass on the imprecision from the float. +Note that with `decimal.Decimal` we enter the number we want as a string so we don't pass on the imprecision from the float. For more details on why this happens check out this [page in the python docs](https://docs.python.org/3/tutorial/floatingpoint.html) or this [Computerphile video](https://www.youtube.com/watch/PZRI1IfStY0). -- cgit v1.2.3 From 47c4321b1fbd49896a6935c58e1bec9dbaf6918f Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 4 Feb 2021 16:02:52 -0800 Subject: Added how_to_get_help constant. --- bot/constants.py | 1 + config-default.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index 95e22513f..6b86d33a3 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -406,6 +406,7 @@ class Channels(metaclass=YAMLGetter): meta: int python_general: int + how_to_get_help: int cooldown: int diff --git a/config-default.yml b/config-default.yml index d3b267159..913d5ca09 100644 --- a/config-default.yml +++ b/config-default.yml @@ -158,6 +158,7 @@ guild: python_general: &PY_GENERAL 267624335836053506 # Python Help: Available + how_to_get_help: 704250143020417084 cooldown: 720603994149486673 # Topical -- cgit v1.2.3 From 0f46bf58bea83da9434b53ddfda3ce8331829588 Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 4 Feb 2021 16:07:37 -0800 Subject: Added dynamic available help channels message --- bot/exts/help_channels/_cog.py | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 0995c8a79..c4ec820b5 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -11,6 +11,7 @@ from discord.ext import commands from bot import constants from bot.bot import Bot +from bot.constants import Channels, Categories from bot.exts.help_channels import _caches, _channel, _cooldown, _message, _name, _stats from bot.utils import channel as channel_utils, lock, scheduling @@ -20,6 +21,9 @@ NAMESPACE = "help" HELP_CHANNEL_TOPIC = """ This is a Python help channel. You can claim your own help channel in the Python Help: Available category. """ +AVAILABLE_HELP_CHANNELS = """ +Currently available help channel(s): {available} +""" class HelpChannels(commands.Cog): @@ -72,6 +76,11 @@ class HelpChannels(commands.Cog): self.last_notification: t.Optional[datetime] = None + # Acquiring and modifying the channel to dynamically update the available help channels message. + self.how_to_get_help: discord.TextChannel = None + self.available_help_channels: t.Set[int] = set() + self.dynamic_message: discord.Message = None + # Asyncio stuff self.queue_tasks: t.List[asyncio.Task] = [] self.init_task = self.bot.loop.create_task(self.init_cog()) @@ -114,6 +123,9 @@ class HelpChannels(commands.Cog): await _caches.unanswered.set(message.channel.id, True) + self.available_help_channels.remove(message.channel.id) + await self.update_available_help_channels() + # Not awaited because it may indefinitely hold the lock while waiting for a channel. scheduling.create_task(self.move_to_available(), name=f"help_claim_{message.id}") @@ -275,6 +287,15 @@ class HelpChannels(commands.Cog): # This may confuse users. So would potentially long delays for the cog to become ready. self.close_command.enabled = True + # Getting channels that need to be included in the dynamic message. + task = asyncio.create_task(self.update_available_help_channels()) + self.queue_tasks.append(task) + + await task + + log.trace(f"Dynamic available help message updated.") + self.queue_tasks.remove(task) + await self.init_available() _stats.report_counts() @@ -332,6 +353,10 @@ class HelpChannels(commands.Cog): category_id=constants.Categories.help_available, ) + # Adding the help channel to the dynamic message, and editing/sending that message. + self.available_help_channels.add(channel.id) + await self.update_available_help_channels() + _stats.report_counts() async def move_to_dormant(self, channel: discord.TextChannel) -> None: @@ -461,3 +486,26 @@ class HelpChannels(commands.Cog): self.queue_tasks.remove(task) return channel + + async def update_available_help_channels(self) -> None: + """Updates the dynamic message within #how-to-get-help for available help channels.""" + if not self.available_help_channels: + available_channels_category = await channel_utils.try_get_channel(Categories.help_available) + self.available_help_channels = set( + c.id for c in available_channels_category.channels if 'help-' in c.name + ) + + if self.how_to_get_help is None: + self.how_to_get_help = await channel_utils.try_get_channel(Channels.how_to_get_help) + + if self.dynamic_message is None: + self.dynamic_message = await self.how_to_get_help.history(limit=1).next() + + available_channels = AVAILABLE_HELP_CHANNELS.format( + available=', '.join(f"<#{c}>" for c in self.available_help_channels) + ) + + try: + await self.dynamic_message.edit(content=available_channels) + except discord.Forbidden: + self.dynamic_message = await self.how_to_get_help.send(available_channels) -- cgit v1.2.3 From 5f17d4d19d0952c91ead096a52b206eea86851fe Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 4 Feb 2021 16:18:20 -0800 Subject: Fixed up linting errors. --- bot/exts/help_channels/_cog.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index c4ec820b5..943b63a42 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -11,7 +11,7 @@ from discord.ext import commands from bot import constants from bot.bot import Bot -from bot.constants import Channels, Categories +from bot.constants import Categories, Channels from bot.exts.help_channels import _caches, _channel, _cooldown, _message, _name, _stats from bot.utils import channel as channel_utils, lock, scheduling @@ -293,7 +293,7 @@ class HelpChannels(commands.Cog): await task - log.trace(f"Dynamic available help message updated.") + log.trace("Dynamic available help message updated.") self.queue_tasks.remove(task) await self.init_available() @@ -499,7 +499,8 @@ class HelpChannels(commands.Cog): self.how_to_get_help = await channel_utils.try_get_channel(Channels.how_to_get_help) if self.dynamic_message is None: - self.dynamic_message = await self.how_to_get_help.history(limit=1).next() + last_message = await self.how_to_get_help.history(limit=1) + self.dynamic_message = next(last_message) available_channels = AVAILABLE_HELP_CHANNELS.format( available=', '.join(f"<#{c}>" for c in self.available_help_channels) -- cgit v1.2.3 From 219ac90d494793a99d77ef5a4e912151e936b1d8 Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 4 Feb 2021 17:12:25 -0800 Subject: Fixed logic in case dynamic message doesn't exist. --- bot/exts/help_channels/_cog.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 943b63a42..730635f08 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -495,18 +495,16 @@ class HelpChannels(commands.Cog): c.id for c in available_channels_category.channels if 'help-' in c.name ) - if self.how_to_get_help is None: - self.how_to_get_help = await channel_utils.try_get_channel(Channels.how_to_get_help) - - if self.dynamic_message is None: - last_message = await self.how_to_get_help.history(limit=1) - self.dynamic_message = next(last_message) - available_channels = AVAILABLE_HELP_CHANNELS.format( available=', '.join(f"<#{c}>" for c in self.available_help_channels) ) - try: - await self.dynamic_message.edit(content=available_channels) - except discord.Forbidden: - self.dynamic_message = await self.how_to_get_help.send(available_channels) + if self.how_to_get_help is None: + self.how_to_get_help = await channel_utils.try_get_channel(Channels.how_to_get_help) + + if self.dynamic_message is None: + try: + self.dynamic_message = await self.how_to_get_help.fetch_message(self.how_to_get_help.last_message_id) + await self.dynamic_message.edit(content=available_channels) + except discord.NotFound: + self.dynamic_message = await self.how_to_get_help.send(available_channels) -- cgit v1.2.3 From 12d8670de79011dc1095293ddc7b256f033fcead Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 4 Feb 2021 18:22:21 -0800 Subject: 'None' is now shown if no channels are available. --- bot/exts/help_channels/_cog.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 730635f08..2f14146ab 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -123,12 +123,13 @@ class HelpChannels(commands.Cog): await _caches.unanswered.set(message.channel.id, True) - self.available_help_channels.remove(message.channel.id) - await self.update_available_help_channels() - # Not awaited because it may indefinitely hold the lock while waiting for a channel. scheduling.create_task(self.move_to_available(), name=f"help_claim_{message.id}") + # Removing the help channel from the dynamic message, and editing/sending that message. + self.available_help_channels.remove(message.channel.id) + await self.update_available_help_channels() + def create_channel_queue(self) -> asyncio.Queue: """ Return a queue of dormant channels to use for getting the next available channel. @@ -496,15 +497,15 @@ class HelpChannels(commands.Cog): ) available_channels = AVAILABLE_HELP_CHANNELS.format( - available=', '.join(f"<#{c}>" for c in self.available_help_channels) + available=', '.join(f"<#{c}>" for c in self.available_help_channels) or None ) if self.how_to_get_help is None: self.how_to_get_help = await channel_utils.try_get_channel(Channels.how_to_get_help) - if self.dynamic_message is None: - try: + try: + if self.dynamic_message is None: self.dynamic_message = await self.how_to_get_help.fetch_message(self.how_to_get_help.last_message_id) - await self.dynamic_message.edit(content=available_channels) - except discord.NotFound: - self.dynamic_message = await self.how_to_get_help.send(available_channels) + await self.dynamic_message.edit(content=available_channels) + except discord.NotFound: + self.dynamic_message = await self.how_to_get_help.send(available_channels) -- cgit v1.2.3 From d2a10cd1b758625ea165e599c0271d9e43f0ab8a Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 4 Feb 2021 18:35:11 -0800 Subject: Alphabetized config-default.yml. --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index 913d5ca09..fc1f3b3a8 100644 --- a/config-default.yml +++ b/config-default.yml @@ -158,8 +158,8 @@ guild: python_general: &PY_GENERAL 267624335836053506 # Python Help: Available - how_to_get_help: 704250143020417084 cooldown: 720603994149486673 + how_to_get_help: 704250143020417084 # Topical discord_py: 343944376055103488 -- cgit v1.2.3 From 3a4d38bc27dc8214af91ee8ab598a5b60897815f Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 4 Feb 2021 18:53:54 -0800 Subject: Removed unnecessary update method call. --- bot/exts/help_channels/_cog.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 2f14146ab..d50197339 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -123,12 +123,11 @@ class HelpChannels(commands.Cog): await _caches.unanswered.set(message.channel.id, True) - # Not awaited because it may indefinitely hold the lock while waiting for a channel. - scheduling.create_task(self.move_to_available(), name=f"help_claim_{message.id}") - # Removing the help channel from the dynamic message, and editing/sending that message. self.available_help_channels.remove(message.channel.id) - await self.update_available_help_channels() + + # Not awaited because it may indefinitely hold the lock while waiting for a channel. + scheduling.create_task(self.move_to_available(), name=f"help_claim_{message.id}") def create_channel_queue(self) -> asyncio.Queue: """ -- cgit v1.2.3 From 88b88ba5c42f7db2d253493fa9b3749287d31ffb Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 4 Feb 2021 19:14:02 -0800 Subject: Replaced fetching available category for old one. --- bot/exts/help_channels/_cog.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index d50197339..4520b408d 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -11,7 +11,7 @@ from discord.ext import commands from bot import constants from bot.bot import Bot -from bot.constants import Categories, Channels +from bot.constants import Channels from bot.exts.help_channels import _caches, _channel, _cooldown, _message, _name, _stats from bot.utils import channel as channel_utils, lock, scheduling @@ -490,9 +490,8 @@ class HelpChannels(commands.Cog): async def update_available_help_channels(self) -> None: """Updates the dynamic message within #how-to-get-help for available help channels.""" if not self.available_help_channels: - available_channels_category = await channel_utils.try_get_channel(Categories.help_available) self.available_help_channels = set( - c.id for c in available_channels_category.channels if 'help-' in c.name + c.id for c in self.available_category.channels if 'help-' in c.name ) available_channels = AVAILABLE_HELP_CHANNELS.format( -- cgit v1.2.3 From 2940ba31bb5118f7c9668d14c3d9ffa7611b3890 Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 5 Feb 2021 02:55:50 -0800 Subject: Modified the dynamic to be bold to catch eyes. --- bot/exts/help_channels/_cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 4520b408d..e9333b9a6 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -22,7 +22,7 @@ HELP_CHANNEL_TOPIC = """ This is a Python help channel. You can claim your own help channel in the Python Help: Available category. """ AVAILABLE_HELP_CHANNELS = """ -Currently available help channel(s): {available} +**Currently available help channel(s):** {available} """ -- cgit v1.2.3 From 555c1a8a4543f051b950c72a4b89805db988ca76 Mon Sep 17 00:00:00 2001 From: Senjan21 <53477086+Senjan21@users.noreply.github.com> Date: Fri, 5 Feb 2021 12:29:27 +0100 Subject: Add jumplink to deleted messages --- bot/exts/moderation/modlog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py index e4b119f41..2dae9d268 100644 --- a/bot/exts/moderation/modlog.py +++ b/bot/exts/moderation/modlog.py @@ -546,6 +546,7 @@ class ModLog(Cog, name="ModLog"): f"**Author:** {format_user(author)}\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" + f"[Jump to message]({message.jump_url})\n" "\n" ) else: @@ -553,6 +554,7 @@ class ModLog(Cog, name="ModLog"): f"**Author:** {format_user(author)}\n" f"**Channel:** #{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" + f"[Jump to message]({message.jump_url})\n" "\n" ) -- cgit v1.2.3 From 2fdb0e5fce6d246cfc67962e235b4d53622f03d7 Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:13:40 +0400 Subject: Make `KeyError` tag --- bot/resources/tags/keyerror.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 bot/resources/tags/keyerror.md diff --git a/bot/resources/tags/keyerror.md b/bot/resources/tags/keyerror.md new file mode 100644 index 000000000..d0c069004 --- /dev/null +++ b/bot/resources/tags/keyerror.md @@ -0,0 +1,17 @@ +Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary. \ +While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them. +## __The `dict.get` method__ +The [dict.get](https://docs.python.org/3/library/stdtypes.html#dict.get) method will return the value for the key if it exists, or None (or a default value that you specify) if the key doesn't exist. Hence it will _never raise_ a KeyError. +```py +>>> my_dict = {"foo": 1, "bar": 2} +>>> print(my_dict.get("foo")) +1 +>>> print(my_dict.get("foobar")) +None +>>> print(my_dict.get("foobar", 3)) # here 3 is the default value to be returned, in case the key doesn't exist +3 +>>> print(my_dict) +{'foo': 1, 'bar': 2} # note that the new key was NOT added to the dictionary +``` +\ +Some other methods that can be used for handling KeyErrors gracefully are the [dict.setdefault](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method, or by using [collections.defaultdict](https://docs.python.org/3/library/collections.html#collections.defaultdict). -- cgit v1.2.3 From 2c4d7f41432bb620d83d3403fe4ab9317bc1129f Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:39:36 +0400 Subject: Update and rename keyerror.md to dict-get.md --- bot/resources/tags/dict-get.md | 13 +++++++++++++ bot/resources/tags/keyerror.md | 17 ----------------- 2 files changed, 13 insertions(+), 17 deletions(-) create mode 100644 bot/resources/tags/dict-get.md delete mode 100644 bot/resources/tags/keyerror.md diff --git a/bot/resources/tags/dict-get.md b/bot/resources/tags/dict-get.md new file mode 100644 index 000000000..b22db7af5 --- /dev/null +++ b/bot/resources/tags/dict-get.md @@ -0,0 +1,13 @@ +Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary.\ +While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them. +__**The `dict.get` method**__ +The [`dict.get`](https://docs.python.org/3/library/stdtypes.html#dict.get) method will return the value for the key if it exists, or None (or a default value that you specify) if the key doesn't exist. Hence it will _never raise_ a KeyError. +```py +>>> my_dict = {"foo": 1, "bar": 2} +>>> print(my_dict.get("foobar")) +None +>>> print(my_dict.get("foobar", 3)) # here 3 is the default value to be returned, in case the key doesn't exist +3 +``` + +Some other methods that can be used for handling KeyErrors gracefully are the [`dict.setdefault`](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method, or by using [`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict). diff --git a/bot/resources/tags/keyerror.md b/bot/resources/tags/keyerror.md deleted file mode 100644 index d0c069004..000000000 --- a/bot/resources/tags/keyerror.md +++ /dev/null @@ -1,17 +0,0 @@ -Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary. \ -While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them. -## __The `dict.get` method__ -The [dict.get](https://docs.python.org/3/library/stdtypes.html#dict.get) method will return the value for the key if it exists, or None (or a default value that you specify) if the key doesn't exist. Hence it will _never raise_ a KeyError. -```py ->>> my_dict = {"foo": 1, "bar": 2} ->>> print(my_dict.get("foo")) -1 ->>> print(my_dict.get("foobar")) -None ->>> print(my_dict.get("foobar", 3)) # here 3 is the default value to be returned, in case the key doesn't exist -3 ->>> print(my_dict) -{'foo': 1, 'bar': 2} # note that the new key was NOT added to the dictionary -``` -\ -Some other methods that can be used for handling KeyErrors gracefully are the [dict.setdefault](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method, or by using [collections.defaultdict](https://docs.python.org/3/library/collections.html#collections.defaultdict). -- cgit v1.2.3 From 721472352f05c561625932a88bcf3c10c98c130d Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:43:29 +0400 Subject: Fix faulty heading --- bot/resources/tags/dict-get.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/resources/tags/dict-get.md b/bot/resources/tags/dict-get.md index b22db7af5..d9cc6a691 100644 --- a/bot/resources/tags/dict-get.md +++ b/bot/resources/tags/dict-get.md @@ -1,6 +1,8 @@ Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary.\ -While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them. +While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them.\ + __**The `dict.get` method**__ + The [`dict.get`](https://docs.python.org/3/library/stdtypes.html#dict.get) method will return the value for the key if it exists, or None (or a default value that you specify) if the key doesn't exist. Hence it will _never raise_ a KeyError. ```py >>> my_dict = {"foo": 1, "bar": 2} -- cgit v1.2.3 From 3a08d74e6ecc5a038ccfc97e973dac161171c6c2 Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:50:45 +0400 Subject: Make `defaultdict` tag --- bot/resources/tags/defaultdict.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 bot/resources/tags/defaultdict.md diff --git a/bot/resources/tags/defaultdict.md b/bot/resources/tags/defaultdict.md new file mode 100644 index 000000000..a15ebff2a --- /dev/null +++ b/bot/resources/tags/defaultdict.md @@ -0,0 +1,20 @@ +**[`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 create 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, {"foo": 1, "bar": 2}) +>>> print(my_dict) +defaultdict(, {'foo': 1, 'bar': 2}) +``` + +In this example, we've used the `int` function - this means that if we try to access a non-existent key, it provides the default value of 0. + +```py +>>> print(my_dict["foobar"]) +0 +>>> print(my_dict) +defaultdict(, {'foo': 1, 'bar': 2, 'foobar': 0}) +``` -- cgit v1.2.3 From a6d65fd04e932a8cc860f5e8ab07f05a4a2f51d1 Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:53:49 +0400 Subject: Refer to `defaultdict` tag in `dict-get` --- bot/resources/tags/dict-get.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/dict-get.md b/bot/resources/tags/dict-get.md index d9cc6a691..6f8299dc7 100644 --- a/bot/resources/tags/dict-get.md +++ b/bot/resources/tags/dict-get.md @@ -12,4 +12,4 @@ None 3 ``` -Some other methods that can be used for handling KeyErrors gracefully are the [`dict.setdefault`](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method, or by using [`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict). +Some other methods that can be used for handling KeyErrors gracefully are the [`dict.setdefault`](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method, or by using [`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict) (check out the `!defaultdict` tag). -- cgit v1.2.3 From c346c2becd2967058a73bc900800afbfb8dbe6d5 Mon Sep 17 00:00:00 2001 From: Senjan21 <53477086+Senjan21@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:12:14 +0100 Subject: Fix #1371 error via adding errors. --- bot/errors.py | 17 ++++++++++++++++- bot/exts/backend/error_handler.py | 4 +++- bot/exts/moderation/infraction/_utils.py | 5 +++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/bot/errors.py b/bot/errors.py index 65d715203..016d9bd17 100644 --- a/bot/errors.py +++ b/bot/errors.py @@ -1,4 +1,5 @@ -from typing import Hashable +from typing import Hashable, Union +from discord import Member, User class LockedResourceError(RuntimeError): @@ -18,3 +19,17 @@ 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 + + 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 = { -- cgit v1.2.3 From 4f1f45652ab980502098b98e5534d64f810b4b53 Mon Sep 17 00:00:00 2001 From: Senjan21 <53477086+Senjan21@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:19:02 +0100 Subject: Linting fix. --- bot/errors.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/errors.py b/bot/errors.py index 016d9bd17..a6fc33312 100644 --- a/bot/errors.py +++ b/bot/errors.py @@ -1,4 +1,5 @@ from typing import Hashable, Union + from discord import Member, User @@ -23,7 +24,7 @@ class LockedResourceError(RuntimeError): class InvalidInfractedUser(Exception): """ - Exception raised upon attempt of infracting an invalid user." + Exception raised upon attempt of infracting an invalid user. Attributes: `user` -- User or Member which is invalid -- cgit v1.2.3 From 78ded411d8e57d399a00d9132d4caa94ba59f410 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Fri, 5 Feb 2021 19:31:38 +0100 Subject: replaced underscore with dash --- bot/resources/tags/empty-json.md | 23 +++++++++++++++++++++++ bot/resources/tags/empty_json.md | 23 ----------------------- 2 files changed, 23 insertions(+), 23 deletions(-) create mode 100644 bot/resources/tags/empty-json.md delete mode 100644 bot/resources/tags/empty_json.md diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md new file mode 100644 index 000000000..9e5c5fd4f --- /dev/null +++ b/bot/resources/tags/empty-json.md @@ -0,0 +1,23 @@ +When creating a new JSON file you might run into the following error. + +`JSONDecodeError: Expecting value: line 1 column 1 (char 0)` + +In short, this means that your JSON is invalid in its current state. This could very well happen because the file is just new and completely empty. +Whilst the JSON data, the data you wish to store, may be empty, the .json file must not. You most likely want to use one of the following data types in your .json file: + +``` +object +array +``` + +To resolve this issue, create one of the above data types in your .json file. It is very common to use `{}` to make an object, which works similar to a dictionary in python. +When this is added to your .json file, it will look like this: + +```json +{ + +} +``` + +The error is resolved now. +Make sure to put all your data between the `{}`, just like you would when making a dictionary. diff --git a/bot/resources/tags/empty_json.md b/bot/resources/tags/empty_json.md deleted file mode 100644 index 9e5c5fd4f..000000000 --- a/bot/resources/tags/empty_json.md +++ /dev/null @@ -1,23 +0,0 @@ -When creating a new JSON file you might run into the following error. - -`JSONDecodeError: Expecting value: line 1 column 1 (char 0)` - -In short, this means that your JSON is invalid in its current state. This could very well happen because the file is just new and completely empty. -Whilst the JSON data, the data you wish to store, may be empty, the .json file must not. You most likely want to use one of the following data types in your .json file: - -``` -object -array -``` - -To resolve this issue, create one of the above data types in your .json file. It is very common to use `{}` to make an object, which works similar to a dictionary in python. -When this is added to your .json file, it will look like this: - -```json -{ - -} -``` - -The error is resolved now. -Make sure to put all your data between the `{}`, just like you would when making a dictionary. -- cgit v1.2.3 From f0afae3c7792d3c6b9899a915a05adb95de3b45d Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Fri, 5 Feb 2021 19:49:06 +0100 Subject: Rewrite to make it more compact and to the point --- bot/resources/tags/empty-json.md | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md index 9e5c5fd4f..0246d346f 100644 --- a/bot/resources/tags/empty-json.md +++ b/bot/resources/tags/empty-json.md @@ -1,23 +1,15 @@ -When creating a new JSON file you might run into the following error. -`JSONDecodeError: Expecting value: line 1 column 1 (char 0)` - -In short, this means that your JSON is invalid in its current state. This could very well happen because the file is just new and completely empty. -Whilst the JSON data, the data you wish to store, may be empty, the .json file must not. You most likely want to use one of the following data types in your .json file: +When using JSON you might run into the following error: +``` +JSONDecodeError: Expecting value: line 1 column 1 (char 0) +``` +This error could have appeared because you just created the JSON file and there is nothing in it at the moment. +Whilst having the data empty is no problem, the file itself may never be completely empty. You most likely want one of the following in your json ``` object array ``` +This issue can be resolved by creating one of these data types. An object is the most common of the 2, and is created by editing your file to read `{}`. -To resolve this issue, create one of the above data types in your .json file. It is very common to use `{}` to make an object, which works similar to a dictionary in python. -When this is added to your .json file, it will look like this: - -```json -{ - -} -``` - -The error is resolved now. -Make sure to put all your data between the `{}`, just like you would when making a dictionary. +Different data types are also supported. If you wish to read more on these, please reffer to the following article: https://www.tutorialspoint.com/json/json_data_types.htm -- cgit v1.2.3 From 68d53cbe1955b486903a7c962f8d6602766375ce Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Fri, 5 Feb 2021 19:53:16 +0100 Subject: Removed an excess line --- bot/resources/tags/empty-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md index 0246d346f..a5de2380f 100644 --- a/bot/resources/tags/empty-json.md +++ b/bot/resources/tags/empty-json.md @@ -10,6 +10,6 @@ Whilst having the data empty is no problem, the file itself may never be complet object array ``` -This issue can be resolved by creating one of these data types. An object is the most common of the 2, and is created by editing your file to read `{}`. +An object is the most common of the 2, and is created by editing your file to read `{}`. Different data types are also supported. If you wish to read more on these, please reffer to the following article: https://www.tutorialspoint.com/json/json_data_types.htm -- cgit v1.2.3 From 04b99031d9895afda5a00080c894b07958919ae9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Fri, 5 Feb 2021 19:56:37 +0100 Subject: Fixed random newline at start --- bot/resources/tags/empty-json.md | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md index a5de2380f..98bfe5fa7 100644 --- a/bot/resources/tags/empty-json.md +++ b/bot/resources/tags/empty-json.md @@ -1,4 +1,3 @@ - When using JSON you might run into the following error: ``` JSONDecodeError: Expecting value: line 1 column 1 (char 0) -- cgit v1.2.3 From 970b49aec1cfee6cdffe56b3d675224fecde382f Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Fri, 5 Feb 2021 20:47:31 +0100 Subject: Simplified language --- bot/resources/tags/empty-json.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md index 98bfe5fa7..3851dc142 100644 --- a/bot/resources/tags/empty-json.md +++ b/bot/resources/tags/empty-json.md @@ -4,11 +4,8 @@ JSONDecodeError: Expecting value: line 1 column 1 (char 0) ``` This error could have appeared because you just created the JSON file and there is nothing in it at the moment. -Whilst having the data empty is no problem, the file itself may never be completely empty. You most likely want one of the following in your json -``` -object -array -``` -An object is the most common of the 2, and is created by editing your file to read `{}`. +Whilst having the data empty is no problem, the file itself may never be completely empty. + +You most likely wanted to structure your JSON as a dictionary. For this change your JSON file to read `{}`. Different data types are also supported. If you wish to read more on these, please reffer to the following article: https://www.tutorialspoint.com/json/json_data_types.htm -- cgit v1.2.3 From 6d9e17634ac10ce911e68d544a52aaa928298929 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sat, 6 Feb 2021 01:25:44 -0800 Subject: Reformatted string constant for available help channels. --- bot/exts/help_channels/_cog.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index e9333b9a6..fbfc585a4 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -21,9 +21,7 @@ NAMESPACE = "help" HELP_CHANNEL_TOPIC = """ This is a Python help channel. You can claim your own help channel in the Python Help: Available category. """ -AVAILABLE_HELP_CHANNELS = """ -**Currently available help channel(s):** {available} -""" +AVAILABLE_HELP_CHANNELS = """**Currently available help channel(s):** {available}""" class HelpChannels(commands.Cog): -- cgit v1.2.3 From bbce0a8cb17d0771a4823e67675cf4dc26f72b2a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 11:37:34 +0200 Subject: Create local-file tag about sending local files to Discord --- bot/resources/tags/local-file.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 bot/resources/tags/local-file.md diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md new file mode 100644 index 000000000..309ca4820 --- /dev/null +++ b/bot/resources/tags/local-file.md @@ -0,0 +1,24 @@ +Thanks to discord.py, sending local files as embed images is simple. You have to create an instance of `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 Discord limitation. + +`discord.Embed` instance has method `set_image` what can be used to set attachment as image: +```py +embed = discord.Embed() +# Set other fields +embed.set_image("attachment://file.png") # Filename here must be exactly same as attachment filename. +``` +After this, you can send embed and attachment to Discord: +```py +await channel.send(file=file, embed=embed) +``` +This example uses `discord.Channel` for sending, but any `discord.Messageable` can be used for sending. + -- cgit v1.2.3 From dc72923a4c74207fc405764a8e8afc1b4b239b37 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sat, 6 Feb 2021 01:52:42 -0800 Subject: Available channels are no longer stored as IDs. --- bot/exts/help_channels/_cog.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index fbfc585a4..dae9b5730 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -76,7 +76,7 @@ class HelpChannels(commands.Cog): # Acquiring and modifying the channel to dynamically update the available help channels message. self.how_to_get_help: discord.TextChannel = None - self.available_help_channels: t.Set[int] = set() + self.available_help_channels: t.Set[discord.TextChannel] = set() self.dynamic_message: discord.Message = None # Asyncio stuff @@ -122,7 +122,7 @@ class HelpChannels(commands.Cog): await _caches.unanswered.set(message.channel.id, True) # Removing the help channel from the dynamic message, and editing/sending that message. - self.available_help_channels.remove(message.channel.id) + self.available_help_channels.remove(message.channel) # Not awaited because it may indefinitely hold the lock while waiting for a channel. scheduling.create_task(self.move_to_available(), name=f"help_claim_{message.id}") @@ -352,7 +352,7 @@ class HelpChannels(commands.Cog): ) # Adding the help channel to the dynamic message, and editing/sending that message. - self.available_help_channels.add(channel.id) + self.available_help_channels.add(channel) await self.update_available_help_channels() _stats.report_counts() @@ -489,11 +489,11 @@ class HelpChannels(commands.Cog): """Updates the dynamic message within #how-to-get-help for available help channels.""" if not self.available_help_channels: self.available_help_channels = set( - c.id for c in self.available_category.channels if 'help-' in c.name + c for c in self.available_category.channels if not _channel.is_excluded_channel(c) ) available_channels = AVAILABLE_HELP_CHANNELS.format( - available=', '.join(f"<#{c}>" for c in self.available_help_channels) or None + available=', '.join(c.mention for c in self.available_help_channels) or None ) if self.how_to_get_help is None: -- cgit v1.2.3 From 2b437bfc4858d6ed08eee43defd9a97584140706 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sat, 6 Feb 2021 02:09:20 -0800 Subject: Removed unnecessary task creation. --- bot/exts/help_channels/_cog.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index dae9b5730..2dbe930d3 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -286,13 +286,8 @@ class HelpChannels(commands.Cog): self.close_command.enabled = True # Getting channels that need to be included in the dynamic message. - task = asyncio.create_task(self.update_available_help_channels()) - self.queue_tasks.append(task) - - await task - + await self.update_available_help_channels() log.trace("Dynamic available help message updated.") - self.queue_tasks.remove(task) await self.init_available() _stats.report_counts() -- cgit v1.2.3 From 88fba5fd3489988320431b8a96879941988b5f13 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sat, 6 Feb 2021 02:21:20 -0800 Subject: Formatted available constant, added missing dynamic message trace --- bot/exts/help_channels/_cog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 2dbe930d3..554c27c95 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -21,7 +21,7 @@ NAMESPACE = "help" HELP_CHANNEL_TOPIC = """ This is a Python help channel. You can claim your own help channel in the Python Help: Available category. """ -AVAILABLE_HELP_CHANNELS = """**Currently available help channel(s):** {available}""" +AVAILABLE_HELP_CHANNELS = "**Currently available help channel(s):** {available}" class HelpChannels(commands.Cog): @@ -500,3 +500,4 @@ class HelpChannels(commands.Cog): await self.dynamic_message.edit(content=available_channels) except discord.NotFound: self.dynamic_message = await self.how_to_get_help.send(available_channels) + log.trace("A dynamic message was sent for later modification because one couldn't be found.") -- cgit v1.2.3 From d0c87c7f12ca20ec9be54bf0d299ca23a5e559db Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 12:54:45 +0200 Subject: discord.Channel -> discord.TextChannel --- bot/resources/tags/local-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index 309ca4820..a587139ee 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -20,5 +20,5 @@ After this, you can send embed and attachment to Discord: ```py await channel.send(file=file, embed=embed) ``` -This example uses `discord.Channel` for sending, but any `discord.Messageable` can be used for sending. +This example uses `discord.TextChannel` for sending, but any `discord.Messageable` can be used for sending. -- cgit v1.2.3 From 44be3e8a7411a715b502802863dfc1fb2d6658c3 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 12:56:52 +0200 Subject: discord.Messageable -> discord.abc.Messageable --- bot/resources/tags/local-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index a587139ee..d78258fa2 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -20,5 +20,5 @@ After this, you can send embed and attachment to Discord: ```py await channel.send(file=file, embed=embed) ``` -This example uses `discord.TextChannel` for sending, but any `discord.Messageable` can be used for sending. +This example uses `discord.TextChannel` for sending, but any `discord.abc.Messageable` can be used for sending. -- cgit v1.2.3 From 6db50230970188b4b1a24ec0b4ff84b4896cc78a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 13:03:35 +0200 Subject: Remove additional newline from end of tag --- bot/resources/tags/local-file.md | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index d78258fa2..9e4e0e551 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -21,4 +21,3 @@ After this, you can send embed and attachment to Discord: await channel.send(file=file, embed=embed) ``` This example uses `discord.TextChannel` for sending, but any `discord.abc.Messageable` can be used for sending. - -- cgit v1.2.3 From 1a9d820638acce176f73867b6b321c8c1dbfb479 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 13:38:39 +0200 Subject: Ignore attachment-only messages for duplicates antispam rule --- bot/rules/duplicates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/rules/duplicates.py b/bot/rules/duplicates.py index 455764b53..23aefd3dc 100644 --- a/bot/rules/duplicates.py +++ b/bot/rules/duplicates.py @@ -13,6 +13,7 @@ async def apply( if ( msg.author == last_message.author and msg.content == last_message.content + and (msg.content and not msg.attachments) ) ) -- cgit v1.2.3 From 84a46c9ab27f0a593c413f5ee09ba19cf5fb1d1b Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 13:45:07 +0200 Subject: Lower max attachments per 10 seconds to 3 --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index d3b267159..d323a946d 100644 --- a/config-default.yml +++ b/config-default.yml @@ -367,7 +367,7 @@ anti_spam: rules: attachments: interval: 10 - max: 9 + max: 3 burst: interval: 10 -- cgit v1.2.3 From e1fa3182254727c564afc86d87fc7043b2444c3c Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 13:56:50 +0200 Subject: Mention instance in comment about Messageable --- bot/resources/tags/local-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index 9e4e0e551..c28e14a05 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -20,4 +20,4 @@ After this, you can send embed and attachment to Discord: ```py await channel.send(file=file, embed=embed) ``` -This example uses `discord.TextChannel` for sending, but any `discord.abc.Messageable` can be used for sending. +This example uses `discord.TextChannel` for sending, but any instance of `discord.abc.Messageable` can be used for sending. -- cgit v1.2.3 From 90a9bac84cdac0288c256157f1b5769b0cd2b973 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 14:03:41 +0200 Subject: Add hyperlinks for local file tag discord.py references --- bot/resources/tags/local-file.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index c28e14a05..344f35667 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -1,4 +1,4 @@ -Thanks to discord.py, sending local files as embed images is simple. You have to create an instance of `discord.File` class: +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") @@ -10,14 +10,14 @@ with open("/this/is/path/to/my/file.png", "rb") as 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 Discord limitation. -`discord.Embed` instance has method `set_image` what can be used to set attachment as image: +[`discord.Embed`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed) instance has method [`set_image`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed.set_image) what can be used to set attachment as image: ```py embed = discord.Embed() # Set other fields -embed.set_image("attachment://file.png") # Filename here must be exactly same as attachment filename. +embed.set_image(url="attachment://file.png") # Filename here must be exactly same as attachment filename. ``` After this, you can send embed and attachment to Discord: ```py await channel.send(file=file, embed=embed) ``` -This example uses `discord.TextChannel` for sending, but any instance of `discord.abc.Messageable` can be used for sending. +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. -- cgit v1.2.3 From fbdfaeafb5c3381d545657a395efec07daaea092 Mon Sep 17 00:00:00 2001 From: swfarnsworth Date: Sat, 6 Feb 2021 10:30:54 -0500 Subject: Rewrite to use simpler examples. The previous examples might have been confusing for some readers. I also removed the part about inverting a dict because I think that's out of scope and would require more explanation given all the consequences that could have. --- bot/resources/tags/dictcomps.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/bot/resources/tags/dictcomps.md b/bot/resources/tags/dictcomps.md index 11867d77b..c9f9e62f7 100644 --- a/bot/resources/tags/dictcomps.md +++ b/bot/resources/tags/dictcomps.md @@ -1,20 +1,15 @@ -**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/) -- cgit v1.2.3 From 9655acb6e4b19eec9aadb5cc1b7ed76ef55aff82 Mon Sep 17 00:00:00 2001 From: swfarnsworth Date: Sat, 6 Feb 2021 10:33:36 -0500 Subject: More robust example with no reference to Python versions or `str.format`. The example emphasizes that you can evaluate expressions in the curly braces. Python 3.5 has already reached EOL, so anyone who doesn't have f-strings at this point is probably running 2.7 anyway. I also removed the information about `str.format` to reduce the scope. --- bot/resources/tags/f-strings.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/bot/resources/tags/f-strings.md b/bot/resources/tags/f-strings.md index 69bc82487..4f12640aa 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 handle converting it to a string. -- cgit v1.2.3 From d333a777aff579ac9d4f38467345fb946dd46bc3 Mon Sep 17 00:00:00 2001 From: swfarnsworth Date: Sat, 6 Feb 2021 10:35:27 -0500 Subject: New example to emphasize the mapping functionality rather than filtering. Previously, the example only conveyed how the `if` statement of list comps could be used to filter a list, whereas the mapping functionality is what people primarily use list comps for. --- bot/resources/tags/listcomps.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) 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). -- cgit v1.2.3 From 8f51f239f2ded1d7176a94039d2332ef74532a95 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 18:07:07 +0200 Subject: Fix grammar of local-file tag --- bot/resources/tags/local-file.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index 344f35667..52539c64e 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -10,13 +10,13 @@ with open("/this/is/path/to/my/file.png", "rb") as 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 Discord limitation. -[`discord.Embed`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed) instance has method [`set_image`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed.set_image) what can be used to set attachment as image: +[`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 embed and attachment to Discord: +After this, you send an embed with an attachment to Discord: ```py await channel.send(file=file, embed=embed) ``` -- cgit v1.2.3 From 496129080733096ab7eddd03128750b9fd3a53a2 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 18:17:00 +0200 Subject: Add back removed 'can' to local-file tag --- bot/resources/tags/local-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index 52539c64e..a4aeee736 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -16,7 +16,7 @@ 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 send an embed with an attachment to Discord: +After this, you can send an embed with an attachment to Discord: ```py await channel.send(file=file, embed=embed) ``` -- cgit v1.2.3 From 65ea1657de016a3ba1e58412950ae4bf735bf0fe Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 18:19:40 +0200 Subject: Add missing 'a' article in local-file tag --- bot/resources/tags/local-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index a4aeee736..29fce3ff5 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -8,7 +8,7 @@ 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 Discord limitation. +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 -- cgit v1.2.3 From 691f2393f0fed5d17ec641d5006ea2e486015614 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 18:21:55 +0200 Subject: Remove unnecessary period from local-file tag --- bot/resources/tags/local-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index 29fce3ff5..fdce5605c 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -8,7 +8,7 @@ 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.. +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 -- cgit v1.2.3 From 174f70e216e327e30a9df6902619944f47eea5ad Mon Sep 17 00:00:00 2001 From: Senjan21 <53477086+Senjan21@users.noreply.github.com> Date: Sat, 6 Feb 2021 18:10:17 +0100 Subject: add reason attribute --- bot/errors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/errors.py b/bot/errors.py index a6fc33312..ab0adcd42 100644 --- a/bot/errors.py +++ b/bot/errors.py @@ -32,5 +32,6 @@ class InvalidInfractedUser(Exception): def __init__(self, user: Union[Member, User], reason: str = "User infracted is a bot."): self.user = user + self.reason = reason super().__init__(reason) -- cgit v1.2.3 From 255f2215279d08386152b12dc8adec037538cba7 Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Sat, 6 Feb 2021 21:16:23 +0400 Subject: Reword comment in example --- bot/resources/tags/dict-get.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/resources/tags/dict-get.md b/bot/resources/tags/dict-get.md index 6f8299dc7..7657f420a 100644 --- a/bot/resources/tags/dict-get.md +++ b/bot/resources/tags/dict-get.md @@ -1,5 +1,5 @@ Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary.\ -While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them.\ +While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them. __**The `dict.get` method**__ @@ -8,7 +8,7 @@ The [`dict.get`](https://docs.python.org/3/library/stdtypes.html#dict.get) metho >>> my_dict = {"foo": 1, "bar": 2} >>> print(my_dict.get("foobar")) None ->>> print(my_dict.get("foobar", 3)) # here 3 is the default value to be returned, in case the key doesn't exist +>>> print(my_dict.get("foobar", 3)) # here 3 is the default value to be returned, because the key doesn't exist 3 ``` -- cgit v1.2.3 From df22551dbf7a4dae4e374eb1dd95d9354b73474c Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Sat, 6 Feb 2021 21:41:13 +0400 Subject: Fix trailing whitespaces --- bot/resources/tags/defaultdict.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/defaultdict.md b/bot/resources/tags/defaultdict.md index a15ebff2a..9361d6f2a 100644 --- a/bot/resources/tags/defaultdict.md +++ b/bot/resources/tags/defaultdict.md @@ -1,6 +1,6 @@ **[`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 create the key and generate a default value for it. +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 create 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 -- cgit v1.2.3 From 5dc8f0e7e0cf150a9a89787b518fdbb7f8f2ba5c Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Sat, 6 Feb 2021 23:28:04 +0400 Subject: Correct examples, reword description --- bot/resources/tags/defaultdict.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bot/resources/tags/defaultdict.md b/bot/resources/tags/defaultdict.md index 9361d6f2a..b6c3175fc 100644 --- a/bot/resources/tags/defaultdict.md +++ b/bot/resources/tags/defaultdict.md @@ -1,20 +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 create the key and generate a default value for it. +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, {"foo": 1, "bar": 2}) ->>> print(my_dict) -defaultdict(, {'foo': 1, 'bar': 2}) +>>> my_dict = defaultdict(int) +>>> my_dict +defaultdict(, {}) ``` -In this example, we've used the `int` function - this means that if we try to access a non-existent key, it provides the default value of 0. +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 ->>> print(my_dict["foobar"]) +>>> my_dict["foo"] 0 ->>> print(my_dict) -defaultdict(, {'foo': 1, 'bar': 2, 'foobar': 0}) +>>> my_dict["bar"] += 5 +>>> my_dict +defaultdict(, {'foo': 0, 'bar': 5}) ``` -- cgit v1.2.3 From fc451e1b3c375a73b000cea21f822b7f95d900d7 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Feb 2021 22:04:10 +0200 Subject: Put filename between backticks --- bot/resources/tags/local-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/local-file.md b/bot/resources/tags/local-file.md index fdce5605c..ae41d589c 100644 --- a/bot/resources/tags/local-file.md +++ b/bot/resources/tags/local-file.md @@ -7,7 +7,7 @@ file = discord.File("/this/is/path/to/my/file.png", filename="file.png") 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. +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: -- cgit v1.2.3 From 90eeeb046b392c1b770c44b766dd2ce78816b8bb Mon Sep 17 00:00:00 2001 From: Steele Farnsworth <32915757+swfarnsworth@users.noreply.github.com> Date: Sat, 6 Feb 2021 16:51:35 -0500 Subject: Removed extra blank line. It added more vertical white space than was wanted. Co-authored-by: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> --- bot/resources/tags/dictcomps.md | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/resources/tags/dictcomps.md b/bot/resources/tags/dictcomps.md index c9f9e62f7..6c8018761 100644 --- a/bot/resources/tags/dictcomps.md +++ b/bot/resources/tags/dictcomps.md @@ -11,5 +11,4 @@ One can use a dict comp to change an existing dictionary using its `items` metho >>> {key.upper(): value * 2 for key, value in first_dict.items()} {'I': 2, 'LOVE': 8, 'PYTHON': 12} ``` - For more information and examples, check out [PEP 274](https://www.python.org/dev/peps/pep-0274/) -- cgit v1.2.3 From 9a9eb8fc6f62ac8527f08cba6f72537c13522291 Mon Sep 17 00:00:00 2001 From: Steele Farnsworth <32915757+swfarnsworth@users.noreply.github.com> Date: Sat, 6 Feb 2021 16:52:36 -0500 Subject: "handle converting" -> "convert ... for you". Per Gustav's suggestion. Co-authored-by: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> --- bot/resources/tags/f-strings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/f-strings.md b/bot/resources/tags/f-strings.md index 4f12640aa..5ccafe723 100644 --- a/bot/resources/tags/f-strings.md +++ b/bot/resources/tags/f-strings.md @@ -6,4 +6,4 @@ Creating a Python string with your variables using the `+` operator can be diffi >>> 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 handle converting it to a string. +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. -- cgit v1.2.3 From 1cb760e158259264bc9cf575a609bd2b6e64d1f3 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Feb 2021 15:14:54 +0300 Subject: Revert "Dynamic available help channels message" --- bot/constants.py | 1 - bot/exts/help_channels/_cog.py | 40 ---------------------------------------- config-default.yml | 1 - 3 files changed, 42 deletions(-) diff --git a/bot/constants.py b/bot/constants.py index 6b86d33a3..95e22513f 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -406,7 +406,6 @@ class Channels(metaclass=YAMLGetter): meta: int python_general: int - how_to_get_help: int cooldown: int diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 554c27c95..0995c8a79 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -11,7 +11,6 @@ from discord.ext import commands from bot import constants from bot.bot import Bot -from bot.constants import Channels from bot.exts.help_channels import _caches, _channel, _cooldown, _message, _name, _stats from bot.utils import channel as channel_utils, lock, scheduling @@ -21,7 +20,6 @@ NAMESPACE = "help" HELP_CHANNEL_TOPIC = """ This is a Python help channel. You can claim your own help channel in the Python Help: Available category. """ -AVAILABLE_HELP_CHANNELS = "**Currently available help channel(s):** {available}" class HelpChannels(commands.Cog): @@ -74,11 +72,6 @@ class HelpChannels(commands.Cog): self.last_notification: t.Optional[datetime] = None - # Acquiring and modifying the channel to dynamically update the available help channels message. - self.how_to_get_help: discord.TextChannel = None - self.available_help_channels: t.Set[discord.TextChannel] = set() - self.dynamic_message: discord.Message = None - # Asyncio stuff self.queue_tasks: t.List[asyncio.Task] = [] self.init_task = self.bot.loop.create_task(self.init_cog()) @@ -121,9 +114,6 @@ class HelpChannels(commands.Cog): await _caches.unanswered.set(message.channel.id, True) - # Removing the help channel from the dynamic message, and editing/sending that message. - self.available_help_channels.remove(message.channel) - # Not awaited because it may indefinitely hold the lock while waiting for a channel. scheduling.create_task(self.move_to_available(), name=f"help_claim_{message.id}") @@ -285,10 +275,6 @@ class HelpChannels(commands.Cog): # This may confuse users. So would potentially long delays for the cog to become ready. self.close_command.enabled = True - # Getting channels that need to be included in the dynamic message. - await self.update_available_help_channels() - log.trace("Dynamic available help message updated.") - await self.init_available() _stats.report_counts() @@ -346,10 +332,6 @@ class HelpChannels(commands.Cog): category_id=constants.Categories.help_available, ) - # Adding the help channel to the dynamic message, and editing/sending that message. - self.available_help_channels.add(channel) - await self.update_available_help_channels() - _stats.report_counts() async def move_to_dormant(self, channel: discord.TextChannel) -> None: @@ -479,25 +461,3 @@ class HelpChannels(commands.Cog): self.queue_tasks.remove(task) return channel - - async def update_available_help_channels(self) -> None: - """Updates the dynamic message within #how-to-get-help for available help channels.""" - if not self.available_help_channels: - self.available_help_channels = set( - c for c in self.available_category.channels if not _channel.is_excluded_channel(c) - ) - - available_channels = AVAILABLE_HELP_CHANNELS.format( - available=', '.join(c.mention for c in self.available_help_channels) or None - ) - - if self.how_to_get_help is None: - self.how_to_get_help = await channel_utils.try_get_channel(Channels.how_to_get_help) - - try: - if self.dynamic_message is None: - self.dynamic_message = await self.how_to_get_help.fetch_message(self.how_to_get_help.last_message_id) - await self.dynamic_message.edit(content=available_channels) - except discord.NotFound: - self.dynamic_message = await self.how_to_get_help.send(available_channels) - log.trace("A dynamic message was sent for later modification because one couldn't be found.") diff --git a/config-default.yml b/config-default.yml index fc1f3b3a8..d3b267159 100644 --- a/config-default.yml +++ b/config-default.yml @@ -159,7 +159,6 @@ guild: # Python Help: Available cooldown: 720603994149486673 - how_to_get_help: 704250143020417084 # Topical discord_py: 343944376055103488 -- cgit v1.2.3 From 16f8fd31b3cd321e4ac7d6eeb0ba20eeb8c78892 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers Date: Tue, 9 Feb 2021 10:53:51 +0100 Subject: Tiny grammar edit --- bot/resources/tags/empty-json.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md index 3851dc142..ceb8c6eae 100644 --- a/bot/resources/tags/empty-json.md +++ b/bot/resources/tags/empty-json.md @@ -1,4 +1,4 @@ -When using JSON you might run into the following error: +When using JSON, you might run into the following error: ``` JSONDecodeError: Expecting value: line 1 column 1 (char 0) ``` @@ -6,6 +6,6 @@ This error could have appeared because you just created the JSON file and there Whilst having the data empty is no problem, the file itself may never be completely empty. -You most likely wanted to structure your JSON as a dictionary. For this change your JSON file to read `{}`. +You most likely wanted to structure your JSON as a dictionary. To do this, change your JSON to read `{}`. Different data types are also supported. If you wish to read more on these, please reffer to the following article: https://www.tutorialspoint.com/json/json_data_types.htm -- cgit v1.2.3 From 8d1a46c1866c12b719b991719c84a6c1d6f25bb4 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers <61157793+sebkuip@users.noreply.github.com> Date: Tue, 9 Feb 2021 11:08:06 +0100 Subject: A small typo Co-authored-by: Kieran Siek --- bot/resources/tags/empty-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md index ceb8c6eae..21b0860c7 100644 --- a/bot/resources/tags/empty-json.md +++ b/bot/resources/tags/empty-json.md @@ -8,4 +8,4 @@ Whilst having the data empty is no problem, the file itself may never be complet You most likely wanted to structure your JSON as a dictionary. To do this, change your JSON to read `{}`. -Different data types are also supported. If you wish to read more on these, please reffer to the following article: https://www.tutorialspoint.com/json/json_data_types.htm +Different data types are also supported. If you wish to read more on these, please refer to the following article: https://www.tutorialspoint.com/json/json_data_types.htm -- cgit v1.2.3 From 2627bc98da2c71a6a10a6b7039522d1938c08552 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers <61157793+sebkuip@users.noreply.github.com> Date: Tue, 9 Feb 2021 11:29:09 +0100 Subject: Hyperlink URL Suggestion of @Numelor Co-authored-by: Numerlor <25886452+Numerlor@users.noreply.github.com> --- bot/resources/tags/empty-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md index 21b0860c7..93e2cadba 100644 --- a/bot/resources/tags/empty-json.md +++ b/bot/resources/tags/empty-json.md @@ -8,4 +8,4 @@ Whilst having the data empty is no problem, the file itself may never be complet You most likely wanted to structure your JSON as a dictionary. To do this, change your JSON to read `{}`. -Different data types are also supported. If you wish to read more on these, please refer to the following article: https://www.tutorialspoint.com/json/json_data_types.htm +Different data types are also supported. If you wish to read more on these, please refer to [this article](https://www.tutorialspoint.com/json/json_data_types.htm). -- cgit v1.2.3 From 160bf89303436e3ba0ff566241a206a120a25d66 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 9 Feb 2021 13:37:28 +0300 Subject: Moves Off Topic Name Translator Breaks out the off topic name translation functionality into its own function. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/converters.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index 0d9a519df..80ce99459 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -357,27 +357,38 @@ class Duration(DurationDelta): class OffTopicName(Converter): """A converter that ensures an added off-topic name is valid.""" + ALLOWED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-" + + @classmethod + def translate_name(cls, name: str, *, from_unicode: bool = True) -> str: + """ + Translates `name` into a format that is allowed in discord channel names. + + If `from_unicode` is True, the name is translated from a discord-safe format, back to normalized text. + """ + if from_unicode: + table = str.maketrans(cls.ALLOWED_CHARACTERS, '𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹ǃ?’’-') + else: + table = str.maketrans('𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹ǃ?’’-', cls.ALLOWED_CHARACTERS) + + return name.translate(table) + async def convert(self, ctx: Context, argument: str) -> str: """Attempt to replace any invalid characters with their approximate Unicode equivalent.""" - allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-" - # Chain multiple words to a single one argument = "-".join(argument.split()) if not (2 <= len(argument) <= 96): raise BadArgument("Channel name must be between 2 and 96 chars long") - elif not all(c.isalnum() or c in allowed_characters for c in argument): + elif not all(c.isalnum() or c in self.ALLOWED_CHARACTERS for c in argument): raise BadArgument( "Channel name must only consist of " "alphanumeric characters, minus signs or apostrophes." ) # Replace invalid characters with unicode alternatives. - table = str.maketrans( - allowed_characters, '𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹ǃ?’’-' - ) - return argument.translate(table) + return self.translate_name(argument) class ISODateTime(Converter): -- cgit v1.2.3 From 66cda4fd2a0b26e2f9e983f1597a15bfb9527143 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 9 Feb 2021 13:38:12 +0300 Subject: Makes Off Topic Name Search Case Insensitive Modifies the off topic channel name search to match upper and lower cased letters, as well as punctuation. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/exts/fun/off_topic_names.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/bot/exts/fun/off_topic_names.py b/bot/exts/fun/off_topic_names.py index 7fc93b88c..845b8175c 100644 --- a/bot/exts/fun/off_topic_names.py +++ b/bot/exts/fun/off_topic_names.py @@ -139,10 +139,20 @@ class OffTopicNames(Cog): @has_any_role(*MODERATION_ROLES) async def search_command(self, ctx: Context, *, query: OffTopicName) -> None: """Search for an off-topic name.""" - result = await self.bot.api_client.get('bot/off-topic-channel-names') - in_matches = {name for name in result if query in name} - close_matches = difflib.get_close_matches(query, result, n=10, cutoff=0.70) - lines = sorted(f"• {name}" for name in in_matches.union(close_matches)) + query = OffTopicName.translate_name(query, from_unicode=False).lower() + + # Map normalized names to returned names for search purposes + result = { + OffTopicName.translate_name(name, from_unicode=False).lower(): name + for name in await self.bot.api_client.get('bot/off-topic-channel-names') + } + + # Search normalized keys + in_matches = {name for name in result.keys() if query in name} + close_matches = difflib.get_close_matches(query, result.keys(), n=10, cutoff=0.70) + + # Send Results + lines = sorted(f"• {result[name]}" for name in in_matches.union(close_matches)) embed = Embed( title="Query results", colour=Colour.blue() -- cgit v1.2.3 From 9d8162a688023a3b5e830057b09c2ab2e132582f Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 10 Feb 2021 01:07:43 +0000 Subject: Migrate API utilities to use internal DNS routing --- bot/api.py | 2 +- bot/constants.py | 1 + config-default.yml | 3 ++- 3 files changed, 4 insertions(+), 2 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/config-default.yml b/config-default.yml index d3b267159..c585151c9 100644 --- a/config-default.yml +++ b/config-default.yml @@ -335,9 +335,10 @@ 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_paste: &PASTE !JOIN ["paste.", *DOMAIN] site_schema: &SCHEMA "https://" + site_api_schema: "http://" site_staff: &STAFF !JOIN ["staff.", *DOMAIN] paste_service: !JOIN [*SCHEMA, *PASTE, "/{key}"] -- cgit v1.2.3 From 578a0e48514fd9f902cde45db557fa1f3425c289 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 10 Feb 2021 01:07:54 +0000 Subject: Migrate ping command to ping internal API --- bot/exts/utils/ping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/ping.py b/bot/exts/utils/ping.py index 572fc934b..e62811b91 100644 --- a/bot/exts/utils/ping.py +++ b/bot/exts/utils/ping.py @@ -37,7 +37,7 @@ class Latency(commands.Cog): bot_ping = f"{bot_ping:.{ROUND_LATENCY}f} ms" try: - delay = await aioping.ping(URLs.site, family=socket.AddressFamily.AF_INET) * 1000 + delay = await aioping.ping(URLs.site_api, family=socket.AddressFamily.AF_INET) * 1000 site_ping = f"{delay:.{ROUND_LATENCY}f} ms" except TimeoutError: -- cgit v1.2.3 From 9111f9f247fb1292add29929c660e9633e4f31da Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 10 Feb 2021 01:57:00 +0000 Subject: Alphabetical sorting in config-default.yml --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index c585151c9..d7415c821 100644 --- a/config-default.yml +++ b/config-default.yml @@ -336,9 +336,9 @@ urls: # PyDis site vars site: &DOMAIN "pythondiscord.com" site_api: &API "pydis-api.default.svc.cluster.local" + site_api_schema: "http://" site_paste: &PASTE !JOIN ["paste.", *DOMAIN] site_schema: &SCHEMA "https://" - site_api_schema: "http://" site_staff: &STAFF !JOIN ["staff.", *DOMAIN] paste_service: !JOIN [*SCHEMA, *PASTE, "/{key}"] -- cgit v1.2.3 From bafa6a9dbf61ae30ef235537408f0b073a88dd19 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 10 Feb 2021 02:13:42 +0000 Subject: ICMP is disabled in production, so we can't ping the API --- bot/exts/utils/ping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/ping.py b/bot/exts/utils/ping.py index e62811b91..572fc934b 100644 --- a/bot/exts/utils/ping.py +++ b/bot/exts/utils/ping.py @@ -37,7 +37,7 @@ class Latency(commands.Cog): bot_ping = f"{bot_ping:.{ROUND_LATENCY}f} ms" try: - delay = await aioping.ping(URLs.site_api, family=socket.AddressFamily.AF_INET) * 1000 + delay = await aioping.ping(URLs.site, family=socket.AddressFamily.AF_INET) * 1000 site_ping = f"{delay:.{ROUND_LATENCY}f} ms" except TimeoutError: -- cgit v1.2.3 From ea35aa9c77a81f46ea14acf36862c42f3ffe9016 Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Thu, 11 Feb 2021 09:37:33 +0400 Subject: Split example codeblock in two --- bot/resources/tags/dict-get.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/bot/resources/tags/dict-get.md b/bot/resources/tags/dict-get.md index 7657f420a..867f0b7d9 100644 --- a/bot/resources/tags/dict-get.md +++ b/bot/resources/tags/dict-get.md @@ -1,15 +1,17 @@ Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary.\ While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them. -__**The `dict.get` method**__ +**The `dict.get` method** -The [`dict.get`](https://docs.python.org/3/library/stdtypes.html#dict.get) method will return the value for the key if it exists, or None (or a default value that you specify) if the key doesn't exist. Hence it will _never raise_ a KeyError. +The [`dict.get`](https://docs.python.org/3/library/stdtypes.html#dict.get) method will return the value for the key if it exists, and None (or a default value that you specify) if the key doesn't exist. Hence it will _never raise_ a KeyError. ```py >>> my_dict = {"foo": 1, "bar": 2} >>> print(my_dict.get("foobar")) None ->>> print(my_dict.get("foobar", 3)) # here 3 is the default value to be returned, because the key doesn't exist +``` +Below, 3 is the default value to be returned, because the key doesn't exist- +```py +>>> print(my_dict.get("foobar", 3)) 3 ``` - -Some other methods that can be used for handling KeyErrors gracefully are the [`dict.setdefault`](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method, or by using [`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict) (check out the `!defaultdict` tag). +Some other methods for handling `KeyError`s gracefully are the [`dict.setdefault`](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method and [`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict) (check out the `!defaultdict` tag). -- cgit v1.2.3 From a3749786c3ff90397427032ef219b590ee4e2837 Mon Sep 17 00:00:00 2001 From: Anand Krishna <40204976+anand2312@users.noreply.github.com> Date: Thu, 11 Feb 2021 10:20:06 +0400 Subject: Remove reference to `try - except` --- bot/resources/tags/dict-get.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/resources/tags/dict-get.md b/bot/resources/tags/dict-get.md index 867f0b7d9..e02df03ab 100644 --- a/bot/resources/tags/dict-get.md +++ b/bot/resources/tags/dict-get.md @@ -1,5 +1,4 @@ -Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary.\ -While you can use a `try` and `except` block to catch the `KeyError`, Python also gives you some other neat ways to handle them. +Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary. Python gives you some neat ways to handle them. **The `dict.get` method** -- cgit v1.2.3 From 84c0aa4268f91027cd71016e01a00ffe59151cc2 Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 11 Feb 2021 02:40:42 -0800 Subject: Added base of the pypi command. --- bot/exts/info/pypi.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 bot/exts/info/pypi.py diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py new file mode 100644 index 000000000..9567516c2 --- /dev/null +++ b/bot/exts/info/pypi.py @@ -0,0 +1,35 @@ +from discord import Embed +from discord.ext.commands import Cog, Context, command + +from bot.bot import Bot +from bot.constants import NEGATIVE_REPLIES + +URL = "https://pypi.org/pypi/{package}/json" + + +class PyPi(Cog): + """Cog for getting information about PyPi packages.""" + + def __init__(self, bot: Bot): + self.bot = bot + + @command(name="pypi", aliases=("package", "pack")) + async def get_package_info(self, ctx: Context, package: str) -> None: + """Getting information about a specific package.""" + embed = Embed(title="PyPi package information") + + async with self.bot.http_session.get(URL.format(package_name=package)) as response: + if response.status == 404: + return await ctx.send(f"Package with name '{package}' could not be found.") + elif response.status == 200 and response.content_type == "application/json": + response_json = await response.json() + info = response_json["info"] + else: + return await ctx.send("There was an error when fetching your PyPi package.") + + await ctx.send(embed=embed) + + +def setup(bot: Bot) -> None: + """Load the PyPi cog.""" + bot.add_cog(PyPi(bot)) -- cgit v1.2.3 From 1610f330fbc583df2c161629b7d8d72b77b9253d Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 11 Feb 2021 03:49:59 -0800 Subject: Added more fields and responses. --- bot/exts/info/pypi.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index 9567516c2..e4c90090d 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -1,10 +1,16 @@ +import logging +from random import choice + from discord import Embed from discord.ext.commands import Cog, Context, command from bot.bot import Bot -from bot.constants import NEGATIVE_REPLIES +from bot.constants import NEGATIVE_REPLIES, Colours URL = "https://pypi.org/pypi/{package}/json" +FIELDS = ["author", "requires_python", "description", "license"] + +log = logging.getLogger(__name__) class PyPi(Cog): @@ -16,16 +22,30 @@ class PyPi(Cog): @command(name="pypi", aliases=("package", "pack")) async def get_package_info(self, ctx: Context, package: str) -> None: """Getting information about a specific package.""" - embed = Embed(title="PyPi package information") + embed = Embed(title=choice(NEGATIVE_REPLIES), colour=Colours.soft_red) - async with self.bot.http_session.get(URL.format(package_name=package)) as response: + async with self.bot.http_session.get(URL.format(package=package)) as response: if response.status == 404: - return await ctx.send(f"Package with name '{package}' could not be found.") + embed.description = f"Package could not be found." + elif response.status == 200 and response.content_type == "application/json": response_json = await response.json() info = response_json["info"] + + embed.title = "Python Package Index" + embed.colour = Colours.soft_green + embed.description = f"[{info['name']} v{info['version']}]({info['download_url']})\n" + + for field in FIELDS: + embed.add_field( + name=field.replace("_", " ").title(), + value=info[field], + inline=False, + ) + else: - return await ctx.send("There was an error when fetching your PyPi package.") + embed.description = "There was an error when fetching your PyPi package." + log.trace(f"Error when fetching PyPi package: {response.status}.") await ctx.send(embed=embed) -- cgit v1.2.3 From ded34bc8ab063064fbd50199d07bbeec1db884ad Mon Sep 17 00:00:00 2001 From: xithrius Date: Thu, 11 Feb 2021 03:54:40 -0800 Subject: Made flake8 very happy. --- bot/exts/info/pypi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index e4c90090d..544b52b49 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -5,7 +5,7 @@ from discord import Embed from discord.ext.commands import Cog, Context, command from bot.bot import Bot -from bot.constants import NEGATIVE_REPLIES, Colours +from bot.constants import Colours, NEGATIVE_REPLIES URL = "https://pypi.org/pypi/{package}/json" FIELDS = ["author", "requires_python", "description", "license"] @@ -26,7 +26,7 @@ class PyPi(Cog): async with self.bot.http_session.get(URL.format(package=package)) as response: if response.status == 404: - embed.description = f"Package could not be found." + embed.description = "Package could not be found." elif response.status == 200 and response.content_type == "application/json": response_json = await response.json() -- cgit v1.2.3 From ed7fde738db677ced25388a53ed9bd539f4490fb Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 12 Feb 2021 00:14:48 -0800 Subject: Empty fields have been accounted for by getting usually non-empty ones. --- bot/exts/info/pypi.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index 544b52b49..7a5d7f4b7 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -8,7 +8,7 @@ from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES URL = "https://pypi.org/pypi/{package}/json" -FIELDS = ["author", "requires_python", "description", "license"] +FIELDS = ["author", "requires_python", "summary", "license"] log = logging.getLogger(__name__) @@ -34,14 +34,15 @@ class PyPi(Cog): embed.title = "Python Package Index" embed.colour = Colours.soft_green - embed.description = f"[{info['name']} v{info['version']}]({info['download_url']})\n" + embed.description = f"[{info['name']} v{info['version']}]({info['package_url']})\n" for field in FIELDS: - embed.add_field( - name=field.replace("_", " ").title(), - value=info[field], - inline=False, - ) + if field_value := info[field]: + embed.add_field( + name=field.replace("_", " ").title(), + value=field_value, + inline=False, + ) else: embed.description = "There was an error when fetching your PyPi package." -- cgit v1.2.3 From 2a9f349429694d48cca86af972ef327a57af552d Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 12 Feb 2021 00:20:51 -0800 Subject: Accounting for completely empty fields that only contain whitespaces. --- bot/exts/info/pypi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index 7a5d7f4b7..990a5c905 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -37,7 +37,8 @@ class PyPi(Cog): embed.description = f"[{info['name']} v{info['version']}]({info['package_url']})\n" for field in FIELDS: - if field_value := info[field]: + # Field could be completely empty, in some cases can be a string with whitespaces. + if field_value := info[field].strip(): embed.add_field( name=field.replace("_", " ").title(), value=field_value, -- cgit v1.2.3 From 889de9b678a044331f02eef647c7d1c963f37edd Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 12 Feb 2021 00:49:27 -0800 Subject: Finalized logic to account for null cases. --- bot/exts/info/pypi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index 990a5c905..4ad72b673 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -37,11 +37,11 @@ class PyPi(Cog): embed.description = f"[{info['name']} v{info['version']}]({info['package_url']})\n" for field in FIELDS: - # Field could be completely empty, in some cases can be a string with whitespaces. - if field_value := info[field].strip(): + # Field could be completely empty, in some cases can be a string with whitespaces, or None. + if info[field] and not info[field].isspace(): embed.add_field( name=field.replace("_", " ").title(), - value=field_value, + value=info[field], inline=False, ) -- cgit v1.2.3 From bcab6614bba3ca71edeb134089846570e0e47547 Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 12 Feb 2021 01:22:12 -0800 Subject: Moved hyperlink to title. --- bot/exts/info/pypi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index 4ad72b673..c7ec22fc6 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -32,9 +32,9 @@ class PyPi(Cog): response_json = await response.json() info = response_json["info"] - embed.title = "Python Package Index" + embed.title = f"{info['name']} v{info['version']}" + embed.url = info['package_url'] embed.colour = Colours.soft_green - embed.description = f"[{info['name']} v{info['version']}]({info['package_url']})\n" for field in FIELDS: # Field could be completely empty, in some cases can be a string with whitespaces, or None. -- cgit v1.2.3 From 94ad4dd207226d7d1a2b080ffe47352e7c2b9e73 Mon Sep 17 00:00:00 2001 From: Xithrius <15021300+Xithrius@users.noreply.github.com> Date: Fri, 12 Feb 2021 02:09:17 -0800 Subject: Made docstring more specific. Co-authored-by: Shivansh-007 <69356296+Shivansh-007@users.noreply.github.com> --- bot/exts/info/pypi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index c7ec22fc6..79931c665 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -21,7 +21,7 @@ class PyPi(Cog): @command(name="pypi", aliases=("package", "pack")) async def get_package_info(self, ctx: Context, package: str) -> None: - """Getting information about a specific package.""" + """Provide information about a specific package from PyPI.""" embed = Embed(title=choice(NEGATIVE_REPLIES), colour=Colours.soft_red) async with self.bot.http_session.get(URL.format(package=package)) as response: -- cgit v1.2.3 From 9ce9ab617ba0fdacb1922e2ed2007ed05e53c526 Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 12 Feb 2021 13:10:25 -0800 Subject: Added colours yellow, blue, and white. --- bot/constants.py | 9 ++++++--- config-default.yml | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/bot/constants.py b/bot/constants.py index 91e41e334..8a93ff9cf 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -246,13 +246,16 @@ class Colours(metaclass=YAMLGetter): section = "style" subsection = "colours" + blue: int bright_green: int - soft_green: int - soft_orange: int - soft_red: int orange: int pink: int purple: int + soft_green: int + soft_orange: int + soft_red: int + white: int + yellow: int class DuckPond(metaclass=YAMLGetter): diff --git a/config-default.yml b/config-default.yml index d7415c821..25bbcc3c5 100644 --- a/config-default.yml +++ b/config-default.yml @@ -24,13 +24,16 @@ bot: style: colours: + blue: 0x3775a8 bright_green: 0x01d277 - soft_green: 0x68c290 - soft_orange: 0xf9cb54 - soft_red: 0xcd6d6d orange: 0xe67e22 pink: 0xcf84e0 purple: 0xb734eb + soft_green: 0x68c290 + soft_orange: 0xf9cb54 + soft_red: 0xcd6d6d + white: 0xfffffe + yellow: 0xffd241 emojis: badge_bug_hunter: "<:bug_hunter_lvl1:743882896372269137>" -- cgit v1.2.3 From aa0b60534d1b8cef2e34bbaf50709553c71a14ff Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 12 Feb 2021 13:12:24 -0800 Subject: Rotating colours in embed, title now links to package. --- bot/exts/info/pypi.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index c7ec22fc6..c7d4d321c 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -1,5 +1,6 @@ +import itertools import logging -from random import choice +import random from discord import Embed from discord.ext.commands import Cog, Context, command @@ -8,7 +9,9 @@ from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES URL = "https://pypi.org/pypi/{package}/json" -FIELDS = ["author", "requires_python", "summary", "license"] +FIELDS = ("author", "requires_python", "summary", "license") +PYPI_ICON = "https://cdn.discordapp.com/emojis/766274397257334814.png" +PYPI_COLOURS = itertools.cycle((Colours.yellow, Colours.blue, Colours.white)) log = logging.getLogger(__name__) @@ -21,8 +24,12 @@ class PyPi(Cog): @command(name="pypi", aliases=("package", "pack")) async def get_package_info(self, ctx: Context, package: str) -> None: - """Getting information about a specific package.""" - embed = Embed(title=choice(NEGATIVE_REPLIES), colour=Colours.soft_red) + """Provide information about a specific package from PyPI.""" + embed = Embed( + title=random.choice(NEGATIVE_REPLIES), + colour=Colours.soft_red + ) + embed.set_thumbnail(url=PYPI_ICON) async with self.bot.http_session.get(URL.format(package=package)) as response: if response.status == 404: @@ -34,7 +41,7 @@ class PyPi(Cog): embed.title = f"{info['name']} v{info['version']}" embed.url = info['package_url'] - embed.colour = Colours.soft_green + embed.colour = next(PYPI_COLOURS) for field in FIELDS: # Field could be completely empty, in some cases can be a string with whitespaces, or None. -- cgit v1.2.3 From 059940b5ae3cc2921303579ebf161835fe09076d Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 12 Feb 2021 15:47:06 -0800 Subject: Taking only the first line of multiline fields. --- bot/exts/info/pypi.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index c7d4d321c..cf45b068f 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -44,11 +44,16 @@ class PyPi(Cog): embed.colour = next(PYPI_COLOURS) for field in FIELDS: + field_data = info[field] + # Field could be completely empty, in some cases can be a string with whitespaces, or None. - if info[field] and not info[field].isspace(): + if field_data and not field_data.isspace(): + if '\n' in field_data and field == "license": + field_data = field_data.split('\n')[0] + embed.add_field( name=field.replace("_", " ").title(), - value=info[field], + value=field_data, inline=False, ) -- cgit v1.2.3 From 8fcb4c6ee7718143c949aa41627064635b2b364b Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 13 Feb 2021 08:24:04 +0200 Subject: Move Git SHA defining at end of Dockerfile to re-enable caching Defining SHA at the beginning of build breaks caching, so this should be avoided. --- Dockerfile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5d0380b44..994b8ee49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,10 @@ FROM python:3.8-slim -# Define Git SHA build argument -ARG git_sha="development" - # Set pip to have cleaner logs and no saved cache ENV PIP_NO_CACHE_DIR=false \ PIPENV_HIDE_EMOJIS=1 \ PIPENV_IGNORE_VIRTUALENVS=1 \ - PIPENV_NOSPIN=1 \ - GIT_SHA=$git_sha + PIPENV_NOSPIN=1 RUN apt-get -y update \ && apt-get install -y \ @@ -25,6 +21,12 @@ WORKDIR /bot COPY Pipfile* ./ RUN pipenv install --system --deploy +# Define Git SHA build argument +ARG git_sha="development" + +# Set Git SHA environment variable here to enable caching +ENV GIT_SHA=$git_sha + # Copy the source code in last to optimize rebuilding the image COPY . . -- cgit v1.2.3 From 88b5b32696c876ee0aa5299eb78bb0d775c5b800 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sat, 13 Feb 2021 00:25:09 -0800 Subject: Escaping markdown in all fields that are created. --- bot/exts/info/pypi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index cf45b068f..73ec31870 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -7,6 +7,7 @@ from discord.ext.commands import Cog, Context, command from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES +from discord.utils import escape_markdown URL = "https://pypi.org/pypi/{package}/json" FIELDS = ("author", "requires_python", "summary", "license") @@ -53,7 +54,7 @@ class PyPi(Cog): embed.add_field( name=field.replace("_", " ").title(), - value=field_data, + value=escape_markdown(field_data), inline=False, ) -- cgit v1.2.3 From f14c391e3a1228953cda29be7993c9a5ec51ca6f Mon Sep 17 00:00:00 2001 From: xithrius Date: Sat, 13 Feb 2021 00:34:33 -0800 Subject: Made flake8 even happier. --- bot/exts/info/pypi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/pypi.py b/bot/exts/info/pypi.py index 73ec31870..3e326e8bb 100644 --- a/bot/exts/info/pypi.py +++ b/bot/exts/info/pypi.py @@ -4,10 +4,10 @@ import random from discord import Embed from discord.ext.commands import Cog, Context, command +from discord.utils import escape_markdown from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES -from discord.utils import escape_markdown URL = "https://pypi.org/pypi/{package}/json" FIELDS = ("author", "requires_python", "summary", "license") -- cgit v1.2.3 From 55f7e7085fe821b4af7e59e811148808a3a40738 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 14 Feb 2021 22:06:44 +0000 Subject: Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 994b8ee49..1a75e5669 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN pipenv install --system --deploy # Define Git SHA build argument ARG git_sha="development" -# Set Git SHA environment variable here to enable caching +# Set Git SHA environment variable for Sentry ENV GIT_SHA=$git_sha # Copy the source code in last to optimize rebuilding the image -- cgit v1.2.3 From aa5e39c3866a9100fda242221106bf6d2caae38c Mon Sep 17 00:00:00 2001 From: Senjan21 <53477086+Senjan21@users.noreply.github.com> Date: Fri, 19 Feb 2021 10:07:35 +0100 Subject: Delete free.md Reasoning behind this is it is rarely used and when its used its often just to test something or as an attempt to close a help channel. --- bot/resources/tags/free.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 bot/resources/tags/free.md diff --git a/bot/resources/tags/free.md b/bot/resources/tags/free.md deleted file mode 100644 index 1493076c7..000000000 --- a/bot/resources/tags/free.md +++ /dev/null @@ -1,5 +0,0 @@ -**We have a new help channel system!** - -Please see <#704250143020417084> for further information. - -A more detailed guide can be found on [our website](https://pythondiscord.com/pages/resources/guides/help-channels/). -- cgit v1.2.3 From 0f4365e2430d40f17ab9a545d3e8614a4b3a9669 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 20 Feb 2021 11:54:52 +0200 Subject: Remove attachments check in duplicates filter --- bot/rules/duplicates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/rules/duplicates.py b/bot/rules/duplicates.py index 23aefd3dc..8e4fbc12d 100644 --- a/bot/rules/duplicates.py +++ b/bot/rules/duplicates.py @@ -13,7 +13,7 @@ async def apply( if ( msg.author == last_message.author and msg.content == last_message.content - and (msg.content and not msg.attachments) + and msg.content ) ) -- cgit v1.2.3 From 7f980be37a572f1998160ce6a2221504e414d285 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sat, 20 Feb 2021 11:55:09 +0200 Subject: Update CODEOWNERS --- .github/CODEOWNERS | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ad813d893..7217cb443 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,11 +7,15 @@ bot/exts/utils/extensions.py @MarkKoz bot/exts/utils/snekbox.py @MarkKoz @Akarys42 bot/exts/help_channels/** @MarkKoz @Akarys42 bot/exts/moderation/** @Akarys42 @mbaruh @Den4200 @ks129 -bot/exts/info/** @Akarys42 @mbaruh @Den4200 +bot/exts/info/** @Akarys42 @Den4200 +bot/exts/info/information.py @mbaruh bot/exts/filters/** @mbaruh bot/exts/fun/** @ks129 bot/exts/utils/** @ks129 +# Rules +bot/rules/** @mbaruh + # Utils bot/utils/extensions.py @MarkKoz bot/utils/function.py @MarkKoz -- cgit v1.2.3 From e3b980e53c13fd5dcaf51408f97c99b629c1a6ec Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 20 Feb 2021 11:55:26 +0200 Subject: Set max attachment from 3 -> 6 --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index d323a946d..e9dce7845 100644 --- a/config-default.yml +++ b/config-default.yml @@ -367,7 +367,7 @@ anti_spam: rules: attachments: interval: 10 - max: 3 + max: 6 burst: interval: 10 -- cgit v1.2.3 From 93c3327414dabd12236e47210be2be1151b71719 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sun, 21 Feb 2021 13:50:37 +0100 Subject: Show the last three characters of censored tokens --- bot/exts/filters/token_remover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/filters/token_remover.py b/bot/exts/filters/token_remover.py index bd6a1f97a..33b39cc2d 100644 --- a/bot/exts/filters/token_remover.py +++ b/bot/exts/filters/token_remover.py @@ -147,7 +147,7 @@ class TokenRemover(Cog): channel=msg.channel.mention, user_id=token.user_id, timestamp=token.timestamp, - hmac='x' * len(token.hmac), + hmac='x' * (len(token.hmac) - 3) + token.hmac[-3:], ) @classmethod -- cgit v1.2.3 From 04e233685e163d1e513a21acd236c2385536b0b7 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sun, 21 Feb 2021 13:51:57 +0100 Subject: Ping the mods if a token present in the server is found no matter the kind --- bot/exts/filters/token_remover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/filters/token_remover.py b/bot/exts/filters/token_remover.py index 33b39cc2d..93f1f3c33 100644 --- a/bot/exts/filters/token_remover.py +++ b/bot/exts/filters/token_remover.py @@ -135,7 +135,7 @@ class TokenRemover(Cog): user_id=user_id, user_name=str(user), kind="BOT" if user.bot else "USER", - ), not user.bot + ), True else: return UNKNOWN_USER_LOG_MESSAGE.format(user_id=user_id), False -- cgit v1.2.3 From 27e60e94bd1fe6784c2b7674433bb175255fa217 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sun, 21 Feb 2021 14:06:54 +0100 Subject: Update token remover unittests --- tests/bot/exts/filters/test_token_remover.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bot/exts/filters/test_token_remover.py b/tests/bot/exts/filters/test_token_remover.py index f99cc3370..51feae9cb 100644 --- a/tests/bot/exts/filters/test_token_remover.py +++ b/tests/bot/exts/filters/test_token_remover.py @@ -291,7 +291,7 @@ class TokenRemoverTests(unittest.IsolatedAsyncioTestCase): channel=self.msg.channel.mention, user_id=token.user_id, timestamp=token.timestamp, - hmac="x" * len(token.hmac), + hmac="xxxxxxxxxxxxxxxxxxxxxxxxjf4", ) @autospec("bot.exts.filters.token_remover", "UNKNOWN_USER_LOG_MESSAGE") @@ -318,7 +318,7 @@ class TokenRemoverTests(unittest.IsolatedAsyncioTestCase): return_value = TokenRemover.format_userid_log_message(msg, token) - self.assertEqual(return_value, (known_user_log_message.format.return_value, False)) + self.assertEqual(return_value, (known_user_log_message.format.return_value, True)) known_user_log_message.format.assert_called_once_with( user_id=472265943062413332, -- cgit v1.2.3 From 584ed52c7107c7d3e3b838ee1e8df3a22ae95e35 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 21 Feb 2021 23:07:38 +0000 Subject: Update max available channels to 3 Partially resolves #1427 --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index beaf89f2c..8e9a29a51 100644 --- a/config-default.yml +++ b/config-default.yml @@ -470,7 +470,7 @@ help_channels: deleted_idle_minutes: 5 # Maximum number of channels to put in the available category - max_available: 2 + max_available: 3 # Maximum number of channels across all 3 categories # Note Discord has a hard limit of 50 channels per category, so this shouldn't be > 50 -- cgit v1.2.3 From 1daf01ef9a3853252d4cadab5fc6abce14df3557 Mon Sep 17 00:00:00 2001 From: wookie184 Date: Mon, 22 Feb 2021 11:35:12 +0000 Subject: Rewrite inline codeblock tag --- bot/resources/tags/inline.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/bot/resources/tags/inline.md b/bot/resources/tags/inline.md index a6a7c35d6..4ece74ef7 100644 --- a/bot/resources/tags/inline.md +++ b/bot/resources/tags/inline.md @@ -1,16 +1,7 @@ **Inline codeblocks** -In addition to multi-line codeblocks, discord has support for inline codeblocks as well. These are small codeblocks that are usually a single line, that can fit between non-codeblocks on the same line. +Inline codeblocks look `like this`. To create them you surround text with single backticks, so \`hello\` would become `hello`. -The following is an example of how it's done: +Note that backticks are not quotes, see [this](https://superuser.com/questions/254076/how-do-i-type-the-tick-and-backtick-characters-on-windows/254077#254077) if you are struggling to find the backtick key. -The \`\_\_init\_\_\` method customizes the newly created instance. - -And results in the following: - -The `__init__` method customizes the newly created instance. - -**Note:** -• These are **backticks** not quotes -• Avoid using them for multiple lines -• Useful for negating formatting you don't want +For how to make multiline codeblocks see the `!codeblock` tag. -- cgit v1.2.3 From b116688be7d8b3d83c88a78969e2118e0504fadc Mon Sep 17 00:00:00 2001 From: wookie184 Date: Mon, 22 Feb 2021 11:38:45 +0000 Subject: Add pep 8 song to pep 8 tag --- bot/resources/tags/pep8.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/resources/tags/pep8.md b/bot/resources/tags/pep8.md index cab4c4db8..57b176122 100644 --- a/bot/resources/tags/pep8.md +++ b/bot/resources/tags/pep8.md @@ -1,3 +1,5 @@ -**PEP 8** is the official style guide for Python. It includes comprehensive guidelines for code formatting, variable naming, and making your code easy to read. Professional Python developers are usually required to follow the guidelines, and will often use code-linters like `flake8` to verify that the code they\'re writing complies with the style guide. +**PEP 8** is the official style guide for Python. It includes comprehensive guidelines for code formatting, variable naming, and making your code easy to read. Professional Python developers are usually required to follow the guidelines, and will often use code-linters like flake8 to verify that the code they're writing complies with the style guide. -You can find the PEP 8 document [here](https://www.python.org/dev/peps/pep-0008). +More information: +• [PEP 8 document](https://www.python.org/dev/peps/pep-0008) +• [Our PEP 8 song!](https://www.youtube.com/watch?v=hgI0p1zf31k) :notes: -- cgit v1.2.3 From 0b11d7dfb408f4e5fe6248ae8377ddc7aa1aa5ee Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Tue, 23 Feb 2021 03:48:35 +0100 Subject: Add truncate_message util --- bot/utils/messages.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 077dd9569..c01fa5d0e 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -154,3 +154,12 @@ async def send_denial(ctx: Context, reason: str) -> None: def format_user(user: discord.abc.User) -> str: """Return a string for `user` which has their mention and ID.""" return f"{user.mention} (`{user.id}`)" + + +def truncate_message(message: discord.Message, limit: int) -> str: + """Returns a truncated version of the message content, up to the specified limit.""" + text = message.content + if len(text) > limit: + return text[:limit-3] + "..." + else: + return text -- cgit v1.2.3 From e1d269d82eed8a01d3d3b0ff33d05e3c79324007 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Tue, 23 Feb 2021 04:00:01 +0100 Subject: Add function to DM users when opening help channel --- bot/exts/help_channels/_message.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/bot/exts/help_channels/_message.py b/bot/exts/help_channels/_message.py index 2bbd4bdd6..12ac4035d 100644 --- a/bot/exts/help_channels/_message.py +++ b/bot/exts/help_channels/_message.py @@ -8,6 +8,7 @@ import bot from bot import constants from bot.exts.help_channels import _caches from bot.utils.channel import is_in_category +from bot.utils.messages import truncate_message log = logging.getLogger(__name__) @@ -92,6 +93,38 @@ async def is_empty(channel: discord.TextChannel) -> bool: return False +async def dm_on_open(message: discord.Message) -> None: + """ + DM claimant with a link to the claimed channel's first message, with a 100 letter preview of the message. + + Does nothing if the user has DMs disabled. + """ + embed = discord.Embed( + title="Help channel opened", + description=f"You claimed {message.channel.mention}.", + colour=bot.constants.Colours.bright_green, + timestamp=message.created_at, + ) + + embed.set_thumbnail(url=constants.Icons.green_questionmark) + embed.add_field( + name="Your message", value=truncate_message(message, limit=100), inline=False + ) + embed.add_field( + name="Want to go there?", + value=f"[Jump to message!]({message.jump_url})", + inline=False, + ) + + try: + await message.author.send(embed=embed) + log.trace(f"Sent DM to {message.author.id} after claiming help channel.") + except discord.errors.Forbidden: + log.trace( + f"Ignoring to send DM to {message.author.id} after claiming help channel: DMs disabled." + ) + + async def notify(channel: discord.TextChannel, last_notification: t.Optional[datetime]) -> t.Optional[datetime]: """ Send a message in `channel` notifying about a lack of available help channels. -- cgit v1.2.3 From e6483d633ac6ecc2a88051442108d9c88e5f7745 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Tue, 23 Feb 2021 04:00:58 +0100 Subject: Add green question mark to default config Add green question mark to default config Add green question mark to config --- bot/constants.py | 1 + config-default.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index 8a93ff9cf..69bc82b89 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -326,6 +326,7 @@ class Icons(metaclass=YAMLGetter): filtering: str green_checkmark: str + green_questionmark: str guild_update: str hash_blurple: str diff --git a/config-default.yml b/config-default.yml index 8e9a29a51..7d9afaa0e 100644 --- a/config-default.yml +++ b/config-default.yml @@ -90,6 +90,7 @@ style: filtering: "https://cdn.discordapp.com/emojis/472472638594482195.png" green_checkmark: "https://raw.githubusercontent.com/python-discord/branding/master/icons/checkmark/green-checkmark-dist.png" + green_questionmark: "https://raw.githubusercontent.com/python-discord/branding/master/icons/checkmark/green-question-mark-dist.png" guild_update: "https://cdn.discordapp.com/emojis/469954765141442561.png" hash_blurple: "https://cdn.discordapp.com/emojis/469950142942806017.png" -- cgit v1.2.3 From e34ea2f1c108d1900e251d17b38563536345d2de Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Tue, 23 Feb 2021 04:07:05 +0100 Subject: Send DM when user claims help channel --- bot/exts/help_channels/_cog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 0995c8a79..a18ddc900 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -102,6 +102,7 @@ class HelpChannels(commands.Cog): await _cooldown.revoke_send_permissions(message.author, self.scheduler) await _message.pin(message) + await _message.dm_on_open(message) # Add user with channel for dormant check. await _caches.claimants.set(message.channel.id, message.author.id) -- cgit v1.2.3 From bb9e56c3cb874ef76ab82db02ce8242117e0da92 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Tue, 23 Feb 2021 11:08:41 +0100 Subject: Update embed field title to be more formal --- bot/exts/help_channels/_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/help_channels/_message.py b/bot/exts/help_channels/_message.py index 12ac4035d..95aca067a 100644 --- a/bot/exts/help_channels/_message.py +++ b/bot/exts/help_channels/_message.py @@ -111,7 +111,7 @@ async def dm_on_open(message: discord.Message) -> None: name="Your message", value=truncate_message(message, limit=100), inline=False ) embed.add_field( - name="Want to go there?", + name="Conversation", value=f"[Jump to message!]({message.jump_url})", inline=False, ) -- cgit v1.2.3 From cae0d84757e026976f1a9e87d52c581669b7b8e8 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Tue, 23 Feb 2021 11:14:31 +0100 Subject: Use textwrap.shorten instead of custom function This applies to the help channel DM embed, where the user is sent a truncated version of their message. --- bot/exts/help_channels/_message.py | 6 ++++-- bot/utils/messages.py | 9 --------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/bot/exts/help_channels/_message.py b/bot/exts/help_channels/_message.py index 95aca067a..4113e51c5 100644 --- a/bot/exts/help_channels/_message.py +++ b/bot/exts/help_channels/_message.py @@ -1,4 +1,5 @@ import logging +import textwrap import typing as t from datetime import datetime @@ -8,7 +9,6 @@ import bot from bot import constants from bot.exts.help_channels import _caches from bot.utils.channel import is_in_category -from bot.utils.messages import truncate_message log = logging.getLogger(__name__) @@ -108,7 +108,9 @@ async def dm_on_open(message: discord.Message) -> None: embed.set_thumbnail(url=constants.Icons.green_questionmark) embed.add_field( - name="Your message", value=truncate_message(message, limit=100), inline=False + name="Your message", + value=textwrap.shorten(message.content, width=100, placeholder="..."), + inline=False, ) embed.add_field( name="Conversation", diff --git a/bot/utils/messages.py b/bot/utils/messages.py index c01fa5d0e..077dd9569 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -154,12 +154,3 @@ async def send_denial(ctx: Context, reason: str) -> None: def format_user(user: discord.abc.User) -> str: """Return a string for `user` which has their mention and ID.""" return f"{user.mention} (`{user.id}`)" - - -def truncate_message(message: discord.Message, limit: int) -> str: - """Returns a truncated version of the message content, up to the specified limit.""" - text = message.content - if len(text) > limit: - return text[:limit-3] + "..." - else: - return text -- cgit v1.2.3 From d71ac9f6e240ffd2d4195d9dbbf5740a0c2413a1 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 23 Feb 2021 19:24:18 +0300 Subject: Fixes Problems With Help Channel DM Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/exts/help_channels/_cog.py | 5 ++++- bot/exts/help_channels/_message.py | 8 +++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index a18ddc900..6abf99810 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -102,7 +102,10 @@ class HelpChannels(commands.Cog): await _cooldown.revoke_send_permissions(message.author, self.scheduler) await _message.pin(message) - await _message.dm_on_open(message) + try: + await _message.dm_on_open(message) + except Exception as e: + log.warning("Error occurred while sending DM:", exc_info=e) # Add user with channel for dormant check. await _caches.claimants.set(message.channel.id, message.author.id) diff --git a/bot/exts/help_channels/_message.py b/bot/exts/help_channels/_message.py index 4113e51c5..36388f9bd 100644 --- a/bot/exts/help_channels/_message.py +++ b/bot/exts/help_channels/_message.py @@ -107,11 +107,9 @@ async def dm_on_open(message: discord.Message) -> None: ) embed.set_thumbnail(url=constants.Icons.green_questionmark) - embed.add_field( - name="Your message", - value=textwrap.shorten(message.content, width=100, placeholder="..."), - inline=False, - ) + formatted_message = textwrap.shorten(message.content, width=100, placeholder="...") + if formatted_message: + embed.add_field(name="Your message", value=formatted_message, inline=False) embed.add_field( name="Conversation", value=f"[Jump to message!]({message.jump_url})", -- cgit v1.2.3 From 44eb00ca03dae1b3d5faf40be63fae04ca515790 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Wed, 24 Feb 2021 18:27:25 +0100 Subject: Add off-topic etiquette to the off-topic tag --- bot/resources/tags/off-topic.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/resources/tags/off-topic.md b/bot/resources/tags/off-topic.md index c7f98a813..6a864a1d5 100644 --- a/bot/resources/tags/off-topic.md +++ b/bot/resources/tags/off-topic.md @@ -6,3 +6,5 @@ There are three off-topic channels: • <#463035268514185226> Their names change randomly every 24 hours, but you can always find them under the `OFF-TOPIC/GENERAL` category in the channel list. + +Please read our [off-topic etiquette](https://pythondiscord.com/pages/resources/guides/off-topic-etiquette/) before participating in conversations. -- cgit v1.2.3 From c5e113734d16e8d5ac2eede6c1f29e019cfc2f28 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Thu, 25 Feb 2021 12:58:15 +0300 Subject: Adds More Descriptive Startup Error Messages Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/__main__.py | 24 ++++++++++++++++++++---- bot/bot.py | 13 ++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/bot/__main__.py b/bot/__main__.py index 257216fa7..e4df4b77d 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,10 +1,26 @@ +import logging + +import aiohttp + import bot from bot import constants -from bot.bot import Bot +from bot.bot import Bot, StartupError from bot.log import setup_sentry setup_sentry() -bot.instance = Bot.create() -bot.instance.load_extensions() -bot.instance.run(constants.Bot.token) +try: + bot.instance = Bot.create() + bot.instance.load_extensions() + bot.instance.run(constants.Bot.token) +except StartupError as e: + message = "Unknown Startup Error Occurred." + if isinstance(e.exception, aiohttp.ClientConnectorError): + message = "Could not connect to site API. Is it running?" + elif isinstance(e.exception, OSError): + message = "Could not connect to Redis. Is it running?" + + # The exception is logged with an empty message so the actual message is visible at the bottom + log = logging.getLogger("bot") + log.fatal("", exc_info=e.exception) + log.fatal(message) diff --git a/bot/bot.py b/bot/bot.py index d5f108575..df80868ee 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -19,6 +19,14 @@ log = logging.getLogger('bot') LOCALHOST = "127.0.0.1" +class StartupError(Exception): + """Exception class for startup errors.""" + + def __init__(self, base: Exception): + super() + self.exception = base + + class Bot(commands.Bot): """A subclass of `discord.ext.commands.Bot` with an aiohttp session and an API client.""" @@ -318,5 +326,8 @@ def _create_redis_session(loop: asyncio.AbstractEventLoop) -> RedisSession: use_fakeredis=constants.Redis.use_fakeredis, global_namespace="bot", ) - loop.run_until_complete(redis_session.connect()) + try: + loop.run_until_complete(redis_session.connect()) + except OSError as e: + raise StartupError(e) return redis_session -- cgit v1.2.3 From 866f3156cb05e49a8ca2c9ebdb13688829f15914 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Thu, 25 Feb 2021 12:59:37 +0300 Subject: Adds Site Readiness Checks Attempts to connect to the site multiple times before throwing an exception to allow the site to warm up when running in docker. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/bot.py | 21 +++++++++++++++++++++ bot/constants.py | 2 ++ config-default.yml | 2 ++ 3 files changed, 25 insertions(+) diff --git a/bot/bot.py b/bot/bot.py index df80868ee..cd8e26325 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -89,6 +89,22 @@ class Bot(commands.Bot): for item in full_cache: self.insert_item_into_filter_list_cache(item) + async def ping_services(self) -> None: + """A helper to make sure all the services the bot relies on are available on startup.""" + # Connect Site/API + attempts = 0 + while True: + try: + log.info(f"Attempting site connection: {attempts + 1}/{constants.URLs.connect_max_retries}") + await self.api_client.get("healthcheck") + break + + except aiohttp.ClientConnectorError as e: + attempts += 1 + if attempts == constants.URLs.connect_max_retries: + raise e + await asyncio.sleep(constants.URLs.connect_cooldown) + @classmethod def create(cls) -> "Bot": """Create and return an instance of a Bot.""" @@ -231,6 +247,11 @@ class Bot(commands.Bot): # here. Normally, this shouldn't happen. await self.redis_session.connect() + try: + await self.ping_services() + except Exception as e: + raise StartupError(e) + # Build the FilterList cache await self.cache_filter_list_data() diff --git a/bot/constants.py b/bot/constants.py index 69bc82b89..7cf31e835 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -531,6 +531,8 @@ class URLs(metaclass=YAMLGetter): github_bot_repo: str # Base site vars + connect_max_retries: int + connect_cooldown: int site: str site_api: str site_schema: str diff --git a/config-default.yml b/config-default.yml index 7d9afaa0e..a9fb2262e 100644 --- a/config-default.yml +++ b/config-default.yml @@ -338,6 +338,8 @@ keys: urls: # PyDis site vars + connect_max_retries: 3 + connect_cooldown: 5 site: &DOMAIN "pythondiscord.com" site_api: &API "pydis-api.default.svc.cluster.local" site_api_schema: "http://" -- cgit v1.2.3 From 900923cc6a9b4d40b625b8f33e8bef18a286a84f Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Thu, 25 Feb 2021 13:23:14 +0300 Subject: Catches All Site Startup Issues Adds a missing exception when trying to connect to the site on startup. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/__main__.py | 2 +- bot/bot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/__main__.py b/bot/__main__.py index e4df4b77d..d3abcd7b2 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -15,7 +15,7 @@ try: bot.instance.run(constants.Bot.token) except StartupError as e: message = "Unknown Startup Error Occurred." - if isinstance(e.exception, aiohttp.ClientConnectorError): + if type(e.exception) in [aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError]: message = "Could not connect to site API. Is it running?" elif isinstance(e.exception, OSError): message = "Could not connect to Redis. Is it running?" diff --git a/bot/bot.py b/bot/bot.py index cd8e26325..1a815c31e 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -99,7 +99,7 @@ class Bot(commands.Bot): await self.api_client.get("healthcheck") break - except aiohttp.ClientConnectorError as e: + except (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError) as e: attempts += 1 if attempts == constants.URLs.connect_max_retries: raise e -- cgit v1.2.3 From 4c566bb2445d0bc637e11242c44a69baa8a39e48 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Thu, 25 Feb 2021 13:42:22 +0300 Subject: Cleans Up Startup Error Handler Code Style Co-authored-by: Akarys42 Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/__main__.py | 4 +++- bot/bot.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/__main__.py b/bot/__main__.py index d3abcd7b2..9317563c8 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -15,7 +15,7 @@ try: bot.instance.run(constants.Bot.token) except StartupError as e: message = "Unknown Startup Error Occurred." - if type(e.exception) in [aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError]: + if isinstance(e.exception, (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError)): message = "Could not connect to site API. Is it running?" elif isinstance(e.exception, OSError): message = "Could not connect to Redis. Is it running?" @@ -24,3 +24,5 @@ except StartupError as e: log = logging.getLogger("bot") log.fatal("", exc_info=e.exception) log.fatal(message) + + exit(69) diff --git a/bot/bot.py b/bot/bot.py index 1a815c31e..3218a60b4 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -102,7 +102,7 @@ class Bot(commands.Bot): except (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError) as e: attempts += 1 if attempts == constants.URLs.connect_max_retries: - raise e + raise await asyncio.sleep(constants.URLs.connect_cooldown) @classmethod -- cgit v1.2.3 From 283857f543ca50e188f39a9b880cef9963f486db Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Thu, 25 Feb 2021 13:44:18 +0300 Subject: Call Super __init__ in Startup Error Co-authored-by: Matteo Bertucci --- bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/bot.py b/bot/bot.py index 3218a60b4..1b4037076 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -23,7 +23,7 @@ class StartupError(Exception): """Exception class for startup errors.""" def __init__(self, base: Exception): - super() + super().__init__() self.exception = base -- cgit v1.2.3 From fb7e21a0897e6de4964ff883f1cd52a9dd443722 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Thu, 25 Feb 2021 13:48:52 +0300 Subject: Removes Unused Variable Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/bot.py b/bot/bot.py index 1b4037076..3a2af472d 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -99,7 +99,7 @@ class Bot(commands.Bot): await self.api_client.get("healthcheck") break - except (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError) as e: + except (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError): attempts += 1 if attempts == constants.URLs.connect_max_retries: raise -- cgit v1.2.3 From ad2bc5d2d1d94ac3ef60d9b60e6f716be5827bf2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuipers <61157793+sebkuip@users.noreply.github.com> Date: Thu, 25 Feb 2021 17:17:00 +0100 Subject: Apply suggestions from code review Co-authored-by: Mark --- bot/resources/tags/empty-json.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/resources/tags/empty-json.md b/bot/resources/tags/empty-json.md index 93e2cadba..935544bb7 100644 --- a/bot/resources/tags/empty-json.md +++ b/bot/resources/tags/empty-json.md @@ -4,8 +4,8 @@ JSONDecodeError: Expecting value: line 1 column 1 (char 0) ``` This error could have appeared because you just created the JSON file and there is nothing in it at the moment. -Whilst having the data empty is no problem, the file itself may never be completely empty. +Whilst having empty data is no problem, the file itself may never be completely empty. -You most likely wanted to structure your JSON as a dictionary. To do this, change your JSON to read `{}`. +You most likely wanted to structure your JSON as a dictionary. To do this, edit your empty JSON file so that it instead contains `{}`. Different data types are also supported. If you wish to read more on these, please refer to [this article](https://www.tutorialspoint.com/json/json_data_types.htm). -- cgit v1.2.3 From 82190ee57bd25a1e999b7a8fb323513696e7e042 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Feb 2021 02:25:18 +0000 Subject: Bump aiohttp from 3.7.3 to 3.7.4 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.7.3 to 3.7.4. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.7.3...v3.7.4) Signed-off-by: dependabot[bot] --- Pipfile | 2 +- Pipfile.lock | 282 ++++++++++++++++++++++++++++------------------------------- 2 files changed, 133 insertions(+), 151 deletions(-) diff --git a/Pipfile b/Pipfile index efdd46522..0a94fb888 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,7 @@ name = "pypi" [packages] aio-pika = "~=6.1" aiodns = "~=2.0" -aiohttp = "~=3.5" +aiohttp = "~=3.7" aioping = "~=0.3.1" aioredis = "~=1.3.1" "async-rediscache[fakeredis]" = "~=0.1.2" diff --git a/Pipfile.lock b/Pipfile.lock index 636d07b1a..f8cedb08f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "26c8089f17d6d6bac11dbed366b1b46818b4546f243af756a106a32af5d9d8f6" + "sha256": "228ae55fe5700ac3827ba6b661933b60b1d06f44fea8bcbe8c5a769fa10ab2fd" }, "pipfile-spec": 6, "requires": { @@ -34,46 +34,46 @@ }, "aiohttp": { "hashes": [ - "sha256:0b795072bb1bf87b8620120a6373a3c61bfcb8da7e5c2377f4bb23ff4f0b62c9", - "sha256:0d438c8ca703b1b714e82ed5b7a4412c82577040dadff479c08405e2a715564f", - "sha256:16a3cb5df5c56f696234ea9e65e227d1ebe9c18aa774d36ff42f532139066a5f", - "sha256:1edfd82a98c5161497bbb111b2b70c0813102ad7e0aa81cbeb34e64c93863005", - "sha256:2406dc1dda01c7f6060ab586e4601f18affb7a6b965c50a8c90ff07569cf782a", - "sha256:2858b2504c8697beb9357be01dc47ef86438cc1cb36ecb6991796d19475faa3e", - "sha256:2a7b7640167ab536c3cb90cfc3977c7094f1c5890d7eeede8b273c175c3910fd", - "sha256:3228b7a51e3ed533f5472f54f70fd0b0a64c48dc1649a0f0e809bec312934d7a", - "sha256:328b552513d4f95b0a2eea4c8573e112866107227661834652a8984766aa7656", - "sha256:39f4b0a6ae22a1c567cb0630c30dd082481f95c13ca528dc501a7766b9c718c0", - "sha256:3b0036c978cbcc4a4512278e98e3e6d9e6b834dc973206162eddf98b586ef1c6", - "sha256:3ea8c252d8df5e9166bcf3d9edced2af132f4ead8ac422eac723c5781063709a", - "sha256:41608c0acbe0899c852281978492f9ce2c6fbfaf60aff0cefc54a7c4516b822c", - "sha256:59d11674964b74a81b149d4ceaff2b674b3b0e4d0f10f0be1533e49c4a28408b", - "sha256:5e479df4b2d0f8f02133b7e4430098699450e1b2a826438af6bec9a400530957", - "sha256:684850fb1e3e55c9220aad007f8386d8e3e477c4ec9211ae54d968ecdca8c6f9", - "sha256:6ccc43d68b81c424e46192a778f97da94ee0630337c9bbe5b2ecc9b0c1c59001", - "sha256:6d42debaf55450643146fabe4b6817bb2a55b23698b0434107e892a43117285e", - "sha256:710376bf67d8ff4500a31d0c207b8941ff4fba5de6890a701d71680474fe2a60", - "sha256:756ae7efddd68d4ea7d89c636b703e14a0c686688d42f588b90778a3c2fc0564", - "sha256:77149002d9386fae303a4a162e6bce75cc2161347ad2ba06c2f0182561875d45", - "sha256:78e2f18a82b88cbc37d22365cf8d2b879a492faedb3f2975adb4ed8dfe994d3a", - "sha256:7d9b42127a6c0bdcc25c3dcf252bb3ddc70454fac593b1b6933ae091396deb13", - "sha256:8389d6044ee4e2037dca83e3f6994738550f6ee8cfb746762283fad9b932868f", - "sha256:9c1a81af067e72261c9cbe33ea792893e83bc6aa987bfbd6fdc1e5e7b22777c4", - "sha256:c1e0920909d916d3375c7a1fdb0b1c78e46170e8bb42792312b6eb6676b2f87f", - "sha256:c68fdf21c6f3573ae19c7ee65f9ff185649a060c9a06535e9c3a0ee0bbac9235", - "sha256:c733ef3bdcfe52a1a75564389bad4064352274036e7e234730526d155f04d914", - "sha256:c9c58b0b84055d8bc27b7df5a9d141df4ee6ff59821f922dd73155861282f6a3", - "sha256:d03abec50df423b026a5aa09656bd9d37f1e6a49271f123f31f9b8aed5dc3ea3", - "sha256:d2cfac21e31e841d60dc28c0ec7d4ec47a35c608cb8906435d47ef83ffb22150", - "sha256:dcc119db14757b0c7bce64042158307b9b1c76471e655751a61b57f5a0e4d78e", - "sha256:df3a7b258cc230a65245167a202dd07320a5af05f3d41da1488ba0fa05bc9347", - "sha256:df48a623c58180874d7407b4d9ec06a19b84ed47f60a3884345b1a5099c1818b", - "sha256:e1b95972a0ae3f248a899cdbac92ba2e01d731225f566569311043ce2226f5e7", - "sha256:f326b3c1bbfda5b9308252ee0dcb30b612ee92b0e105d4abec70335fab5b1245", - "sha256:f411cb22115cb15452d099fec0ee636b06cf81bfb40ed9c02d30c8dc2bc2e3d1" + "sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0", + "sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6", + "sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf", + "sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9", + "sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e", + "sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0", + "sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329", + "sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2", + "sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40", + "sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a", + "sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4", + "sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de", + "sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9", + "sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9", + "sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb", + "sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076", + "sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de", + "sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907", + "sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d", + "sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536", + "sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d", + "sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54", + "sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc", + "sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212", + "sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9", + "sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d", + "sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b", + "sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7", + "sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81", + "sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c", + "sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895", + "sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297", + "sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb", + "sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe", + "sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242", + "sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0", + "sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2" ], "index": "pypi", - "version": "==3.7.3" + "version": "==3.7.4" }, "aioping": { "hashes": [ @@ -96,7 +96,6 @@ "sha256:8218dd9f7198d6e7935855468326bbacf0089f926c70baa8dd92944cb2496573", "sha256:e584dac13a242589aaf42470fd3006cb0dc5aed6506cbd20357c7ec8bbe4a89e" ], - "markers": "python_version >= '3.6'", "version": "==3.3.1" }, "alabaster": { @@ -123,7 +122,6 @@ "sha256:c25e4fff73f64d20645254783c3224a4c49e083e3fab67c44f17af944c5e26af" ], "index": "pypi", - "markers": "python_version ~= '3.7'", "version": "==0.1.4" }, "async-timeout": { @@ -131,7 +129,6 @@ "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" ], - "markers": "python_full_version >= '3.5.3'", "version": "==3.0.1" }, "attrs": { @@ -139,7 +136,6 @@ "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.3.0" }, "babel": { @@ -147,7 +143,6 @@ "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5", "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.0" }, "beautifulsoup4": { @@ -168,44 +163,45 @@ }, "cffi": { "hashes": [ - "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e", - "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d", - "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a", - "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec", - "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362", - "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668", - "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c", - "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b", - "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06", - "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698", - "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2", - "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c", - "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7", - "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009", - "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03", - "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b", - "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909", - "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53", - "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35", - "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26", - "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b", - "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01", - "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb", - "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293", - "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd", - "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d", - "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3", - "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d", - "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e", - "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca", - "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d", - "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775", - "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375", - "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b", - "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b", - "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f" - ], - "version": "==1.14.4" + "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", + "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", + "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", + "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", + "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", + "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", + "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", + "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", + "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", + "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", + "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", + "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", + "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", + "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", + "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", + "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", + "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", + "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", + "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", + "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", + "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", + "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", + "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", + "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", + "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", + "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", + "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", + "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", + "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", + "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", + "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", + "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", + "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", + "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", + "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", + "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", + "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" + ], + "version": "==1.14.5" }, "chardet": { "hashes": [ @@ -219,6 +215,7 @@ "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" ], + "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==0.4.4" }, @@ -251,7 +248,6 @@ "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==0.16" }, "emoji": { @@ -334,7 +330,6 @@ "sha256:e64be68255234bb489a574c4f2f8df7029c98c81ec4d160d6cd836e7f0679390", "sha256:e82d6b930e02e80e5109b678c663a9ed210680ded81c1abaf54635d88d1da298" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.0" }, "humanfriendly": { @@ -342,7 +337,6 @@ "sha256:066562956639ab21ff2676d1fda0b5987e985c534fc76700a19bd54bcb81121d", "sha256:d5c731705114b9ad673754f3317d9fa4c23212f36b29bdc4272a892eafc9bc72" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==9.1" }, "idna": { @@ -350,7 +344,6 @@ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "imagesize": { @@ -358,16 +351,14 @@ "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.2.0" }, "jinja2": { "hashes": [ - "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", - "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" + "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", + "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.11.2" + "version": "==2.11.3" }, "lxml": { "hashes": [ @@ -427,8 +418,12 @@ "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", + "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", + "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", @@ -437,26 +432,40 @@ "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", + "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", + "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", + "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", + "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", + "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", + "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.1" }, "more-itertools": { @@ -507,23 +516,20 @@ "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281", "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80" ], - "markers": "python_version >= '3.6'", "version": "==5.1.0" }, "ordered-set": { "hashes": [ "sha256:ba93b2df055bca202116ec44b9bead3df33ea63a7d5827ff8e16738b97f33a95" ], - "markers": "python_version >= '3.5'", "version": "==4.0.2" }, "packaging": { "hashes": [ - "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858", - "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093" + "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", + "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.8" + "version": "==20.9" }, "pamqp": { "hashes": [ @@ -571,23 +577,20 @@ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.20" }, "pygments": { "hashes": [ - "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435", - "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337" + "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0", + "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88" ], - "markers": "python_version >= '3.5'", - "version": "==2.7.4" + "version": "==2.8.0" }, "pyparsing": { "hashes": [ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.4.7" }, "python-dateutil": { @@ -600,10 +603,10 @@ }, "pytz": { "hashes": [ - "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4", - "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5" + "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", + "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" ], - "version": "==2020.5" + "version": "==2021.1" }, "pyyaml": { "hashes": [ @@ -629,7 +632,6 @@ "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==3.5.3" }, "requests": { @@ -653,15 +655,14 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.15.0" }, "snowballstemmer": { "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2", + "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "sortedcontainers": { "hashes": [ @@ -672,11 +673,11 @@ }, "soupsieve": { "hashes": [ - "sha256:4bb21a6ee4707bf43b61230e80740e71bfe56e55d1f1f50924b087bb2975c851", - "sha256:6dc52924dc0bc710a5d16794e6b3480b2c7c08b07729505feab2b2c16661ff6e" + "sha256:407fa1e8eb3458d1b5614df51d9651a1180ea5fedf07feb46e45d7e25e6d6cdd", + "sha256:d3a5ea5b350423f47d07639f74475afedad48cf41c0ad7a82ca13a3928af34f6" ], "markers": "python_version >= '3.0'", - "version": "==2.1" + "version": "==2.2" }, "sphinx": { "hashes": [ @@ -691,7 +692,6 @@ "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" ], - "markers": "python_version >= '3.5'", "version": "==1.0.2" }, "sphinxcontrib-devhelp": { @@ -699,7 +699,6 @@ "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" ], - "markers": "python_version >= '3.5'", "version": "==1.0.2" }, "sphinxcontrib-htmlhelp": { @@ -707,7 +706,6 @@ "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f", "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b" ], - "markers": "python_version >= '3.5'", "version": "==1.0.3" }, "sphinxcontrib-jsmath": { @@ -715,7 +713,6 @@ "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" ], - "markers": "python_version >= '3.5'", "version": "==1.0.1" }, "sphinxcontrib-qthelp": { @@ -723,7 +720,6 @@ "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" ], - "markers": "python_version >= '3.5'", "version": "==1.0.3" }, "sphinxcontrib-serializinghtml": { @@ -731,7 +727,6 @@ "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc", "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a" ], - "markers": "python_version >= '3.5'", "version": "==1.1.4" }, "statsd": { @@ -752,11 +747,10 @@ }, "urllib3": { "hashes": [ - "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", - "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" + "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", + "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.2" + "version": "==1.26.3" }, "yarl": { "hashes": [ @@ -798,7 +792,6 @@ "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" ], - "markers": "python_version >= '3.6'", "version": "==1.6.3" } }, @@ -815,7 +808,6 @@ "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.3.0" }, "certifi": { @@ -830,7 +822,6 @@ "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d", "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1" ], - "markers": "python_full_version >= '3.6.1'", "version": "==3.2.0" }, "chardet": { @@ -995,18 +986,16 @@ }, "identify": { "hashes": [ - "sha256:18994e850ba50c37bcaed4832be8b354d6a06c8fb31f54e0e7ece76d32f69bc8", - "sha256:892473bf12e655884132a3a32aca737a3cbefaa34a850ff52d501773a45837bc" + "sha256:de7129142a5c86d75a52b96f394d94d96d497881d2aaf8eafe320cdbe8ac4bcc", + "sha256:e0dae57c0397629ce13c289f6ddde0204edf518f557bfdb1e56474aa143e77c3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.5.12" + "version": "==1.5.14" }, "idna": { "hashes": [ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "mccabe": { @@ -1044,7 +1033,6 @@ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.6.0" }, "pydocstyle": { @@ -1052,7 +1040,6 @@ "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325", "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678" ], - "markers": "python_version >= '3.5'", "version": "==5.1.1" }, "pyflakes": { @@ -1060,7 +1047,6 @@ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.2.0" }, "pyyaml": { @@ -1095,39 +1081,35 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.15.0" }, "snowballstemmer": { "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2", + "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "toml": { "hashes": [ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "urllib3": { "hashes": [ - "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", - "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" + "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", + "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.2" + "version": "==1.26.3" }, "virtualenv": { "hashes": [ - "sha256:0c111a2236b191422b37fe8c28b8c828ced39aab4bf5627fa5c331aeffb570d9", - "sha256:14b34341e742bdca219e10708198e704e8a7064dd32f474fc16aca68ac53a306" + "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d", + "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.1" + "version": "==20.4.2" } } } -- cgit v1.2.3 From 64e85ddcc57e2789627c4a4a7869424d7583dc17 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Fri, 26 Feb 2021 21:02:31 +0000 Subject: !int socketstats improvements - Comma separate event values - Make fields inline for smaller embed --- bot/exts/utils/internal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/internal.py b/bot/exts/utils/internal.py index a7ab43f37..d193e4d4f 100644 --- a/bot/exts/utils/internal.py +++ b/bot/exts/utils/internal.py @@ -245,7 +245,7 @@ async def func(): # (None,) -> Any ) for event_type, count in self.socket_events.most_common(25): - stats_embed.add_field(name=event_type, value=count, inline=False) + stats_embed.add_field(name=event_type, value=f"{count:,}", inline=True) await ctx.send(embed=stats_embed) -- cgit v1.2.3 From de226ea845e8f68735ce6d20193bece9f50b1d5f Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Fri, 26 Feb 2021 22:24:50 +0100 Subject: Make "event" plural in socketstats embed --- bot/exts/utils/internal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/internal.py b/bot/exts/utils/internal.py index d193e4d4f..6f2da3131 100644 --- a/bot/exts/utils/internal.py +++ b/bot/exts/utils/internal.py @@ -240,7 +240,7 @@ async def func(): # (None,) -> Any stats_embed = discord.Embed( title="WebSocket statistics", - description=f"Receiving {per_s:0.2f} event per second.", + description=f"Receiving {per_s:0.2f} events per second.", color=discord.Color.blurple() ) -- cgit v1.2.3 From e82429c88f8643f8eaa89ea5541d0ffe860ec338 Mon Sep 17 00:00:00 2001 From: SavagePastaMan <69145546+SavagePastaMan@users.noreply.github.com> Date: Sat, 27 Feb 2021 09:54:50 -0500 Subject: Create comparison.md --- bot/resources/tags/comparison.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 bot/resources/tags/comparison.md diff --git a/bot/resources/tags/comparison.md b/bot/resources/tags/comparison.md new file mode 100644 index 000000000..12844bd2f --- /dev/null +++ b/bot/resources/tags/comparison.md @@ -0,0 +1,12 @@ +**Assignment vs. Comparison** + +The assignment operator (`=`) is used to assign variables. +```python +x = 5 +print(x) # Prints 5 +``` +The equality operator (`==`) is used to compare values. +```python +if x == 5: + print("The value of x is 5") +``` -- cgit v1.2.3 From c3a9e704080bfc670993b396d721ffd762348591 Mon Sep 17 00:00:00 2001 From: Bast Date: Mon, 1 Mar 2021 02:20:53 -0800 Subject: Add alias !u for !user --- bot/exts/info/information.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 4499e4c25..88e904d03 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -202,7 +202,7 @@ class Information(Cog): await ctx.send(embed=embed) - @command(name="user", aliases=["user_info", "member", "member_info"]) + @command(name="user", aliases=["user_info", "member", "member_info", "u"]) async def user_info(self, ctx: Context, user: FetchedMember = None) -> None: """Returns info about a user.""" if user is None: -- cgit v1.2.3 From 5b31aa992db27cd1798e4dce5f1c4256aa8848fa Mon Sep 17 00:00:00 2001 From: Bast Date: Mon, 1 Mar 2021 02:22:59 -0800 Subject: Add alias !tban for !tempban --- bot/exts/moderation/infraction/infractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 7349d65f2..406c6b53f 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -126,7 +126,7 @@ class Infractions(InfractionScheduler, commands.Cog): duration = await Duration().convert(ctx, "1h") await self.apply_mute(ctx, user, reason, expires_at=duration) - @command() + @command(aliases=("tban",)) async def tempban( self, ctx: Context, -- cgit v1.2.3 From 58c37361d0a322b308869492d50f2008ae497b3d Mon Sep 17 00:00:00 2001 From: Bast Date: Mon, 1 Mar 2021 02:23:46 -0800 Subject: Add !superstar and !unsuperstar aliases for !superstarify --- bot/exts/moderation/infraction/superstarify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index ffc470c54..704dddf9c 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -104,7 +104,7 @@ class Superstarify(InfractionScheduler, Cog): await self.reapply_infraction(infraction, action) - @command(name="superstarify", aliases=("force_nick", "star", "starify")) + @command(name="superstarify", aliases=("force_nick", "star", "starify", "superstar")) async def superstarify( self, ctx: Context, @@ -183,7 +183,7 @@ class Superstarify(InfractionScheduler, Cog): ) await ctx.send(embed=embed) - @command(name="unsuperstarify", aliases=("release_nick", "unstar", "unstarify")) + @command(name="unsuperstarify", aliases=("release_nick", "unstar", "unstarify", "unsuperstar")) async def unsuperstarify(self, ctx: Context, member: Member) -> None: """Remove the superstarify infraction and allow the user to change their nickname.""" await self.pardon_infraction(ctx, "superstar", member) -- cgit v1.2.3 From dec9a9dba77aa4322f9dc37b6493a8410e7482ec Mon Sep 17 00:00:00 2001 From: Bast Date: Mon, 1 Mar 2021 02:38:41 -0800 Subject: Add !stban alias for !shadowtempban --- bot/exts/moderation/infraction/infractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 406c6b53f..3b5b1df45 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -198,7 +198,7 @@ class Infractions(InfractionScheduler, commands.Cog): # endregion # region: Temporary shadow infractions - @command(hidden=True, aliases=["shadowtempban", "stempban"]) + @command(hidden=True, aliases=["shadowtempban", "stempban", "stban"]) async def shadow_tempban( self, ctx: Context, -- cgit v1.2.3