From 1a40e4b17daa64179f14358b4f86f0602131d799 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 5 Dec 2018 00:49:44 +0100 Subject: Fixed the !user command so users can still use it if they target themselves. Also restricted it to #bot-commands for anyone except mods and up. --- bot/cogs/information.py | 32 +++++++++++++++++++++++++--- bot/cogs/tags.py | 2 +- bot/decorators.py | 46 +++++++++++++++++----------------------- bot/utils/checks.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 31 deletions(-) create mode 100644 bot/utils/checks.py diff --git a/bot/cogs/information.py b/bot/cogs/information.py index 0f1aa75c5..fa6f56b3e 100644 --- a/bot/cogs/information.py +++ b/bot/cogs/information.py @@ -1,11 +1,13 @@ import logging +import random import textwrap from discord import CategoryChannel, Colour, Embed, Member, TextChannel, VoiceChannel -from discord.ext.commands import Bot, Context, command +from discord.ext.commands import BadArgument, Bot, CommandError, Context, MissingPermissions, command -from bot.constants import Emojis, Keys, Roles, URLs +from bot.constants import Channels, Emojis, Keys, NEGATIVE_REPLIES, Roles, URLs from bot.decorators import with_role +from bot.utils.checks import with_role_check from bot.utils.time import time_since log = logging.getLogger(__name__) @@ -121,13 +123,23 @@ class Information: await ctx.send(embed=embed) - @with_role(*MODERATION_ROLES) @command(name="user", aliases=["user_info", "member", "member_info"]) async def user_info(self, ctx: Context, user: Member = None, hidden: bool = False): """ Returns info about a user. """ + # Do a role check if this is being executed on + # someone other than the caller + if user and user != ctx.author: + if not with_role_check(ctx, *MODERATION_ROLES): + raise BadArgument("Only mods can do target other users.") + + # Non-moderators may only do this in #bot-commands + if not with_role_check(ctx, *MODERATION_ROLES): + if not ctx.channel == Channels.bot: + raise MissingPermissions("You can't do that here!") + # Validates hidden input hidden = str(hidden) @@ -192,6 +204,20 @@ class Information: await ctx.send(embed=embed) + @user_info.error + async def eval_command_error(self, ctx: Context, error: CommandError): + embed = Embed(colour=Colour.red()) + + if isinstance(error, BadArgument): + embed.title = random.choice(NEGATIVE_REPLIES) + embed.description = "You do not have permission to use this command on users other than yourself." + await ctx.send(embed=embed) + + elif isinstance(error, MissingPermissions): + embed.title = random.choice(NEGATIVE_REPLIES) + embed.description = f"Sorry, but you may only use this command within <#{Channels.bot}>." + await ctx.send(embed=embed) + def setup(bot): bot.add_cog(Information(bot)) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index a0ba7fdd1..ba4edeef7 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -149,7 +149,7 @@ class Tags: tags = [] - embed = Embed() + embed: Embed = Embed() embed.colour = Colour.red() tag_data = await self.get_tag_data(tag_name) diff --git a/bot/decorators.py b/bot/decorators.py index fe974cbd3..630c2267f 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -9,49 +9,41 @@ from discord.ext import commands from discord.ext.commands import Context from bot.constants import ERROR_REPLIES +from bot.utils.checks import in_channel_check, with_role_check, without_role_check log = logging.getLogger(__name__) def with_role(*role_ids: int): + """ + Returns True if the user has any one + of the roles in role_ids. + """ + async def predicate(ctx: Context): - if not ctx.guild: # Return False in a DM - log.debug(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " - "This command is restricted by the with_role decorator. Rejecting request.") - return False - - for role in ctx.author.roles: - if role.id in role_ids: - log.debug(f"{ctx.author} has the '{role.name}' role, and passes the check.") - return True - - log.debug(f"{ctx.author} does not have the required role to use " - f"the '{ctx.command.name}' command, so the request is rejected.") - return False + return with_role_check(ctx, *role_ids) return commands.check(predicate) def without_role(*role_ids: int): + """ + Returns True if the user does not have any + of the roles in role_ids. + """ + async def predicate(ctx: Context): - if not ctx.guild: # Return False in a DM - log.debug(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " - "This command is restricted by the without_role decorator. Rejecting request.") - return False - - author_roles = [role.id for role in ctx.author.roles] - check = all(role not in author_roles for role in role_ids) - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The result of the without_role check was {check}.") - return check + return without_role_check(ctx, *role_ids) return commands.check(predicate) def in_channel(channel_id): + """ + Checks if the command was executed + inside of the specified channel. + """ + async def predicate(ctx: Context): - check = ctx.channel.id == channel_id - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The result of the in_channel check was {check}.") - return check + return in_channel_check(ctx, channel_id) return commands.check(predicate) diff --git a/bot/utils/checks.py b/bot/utils/checks.py new file mode 100644 index 000000000..d07b09fc8 --- /dev/null +++ b/bot/utils/checks.py @@ -0,0 +1,56 @@ +import logging + +from discord.ext.commands import Context + +log = logging.getLogger(__name__) + + +def with_role_check(ctx: Context, *role_ids: int) -> bool: + """ + Returns True if the user has any one + of the roles in role_ids. + """ + + if not ctx.guild: # Return False in a DM + log.debug(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " + "This command is restricted by the with_role decorator. Rejecting request.") + return False + + for role in ctx.author.roles: + if role.id in role_ids: + log.debug(f"{ctx.author} has the '{role.name}' role, and passes the check.") + return True + + log.debug(f"{ctx.author} does not have the required role to use " + f"the '{ctx.command.name}' command, so the request is rejected.") + return False + + +def without_role_check(ctx: Context, *role_ids: int) -> bool: + """ + Returns True if the user does not have any + of the roles in role_ids. + """ + + if not ctx.guild: # Return False in a DM + log.debug(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " + "This command is restricted by the without_role decorator. Rejecting request.") + return False + + author_roles = [role.id for role in ctx.author.roles] + check = all(role not in author_roles for role in role_ids) + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The result of the without_role check was {check}.") + return check + + +def in_channel_check(ctx: Context, channel_id: int) -> bool: + """ + Checks if the command was executed + inside of the specified channel. + """ + + check = ctx.channel.id == channel_id + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The result of the in_channel check was {check}.") + return check -- cgit v1.2.3 From 393455a1407ee822f28b48e56618030dd6274614 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Tue, 18 Dec 2018 22:16:45 +0100 Subject: Update bot/cogs/information.py Co-Authored-By: heavysaturn --- bot/cogs/information.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/information.py b/bot/cogs/information.py index c3040d8e2..c0401fe19 100644 --- a/bot/cogs/information.py +++ b/bot/cogs/information.py @@ -133,7 +133,7 @@ class Information: # someone other than the caller if user and user != ctx.author: if not with_role_check(ctx, *MODERATION_ROLES): - raise BadArgument("Only mods can do target other users.") + raise BadArgument("Only mods can target other users.") # Non-moderators may only do this in #bot-commands if not with_role_check(ctx, *MODERATION_ROLES): -- cgit v1.2.3 From 03ee66a46e75613dd941a021e38460c7f012e486 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 27 Dec 2018 21:42:12 +0100 Subject: Addressing jchrists review comments. --- Pipfile.lock | 176 +++++++++++++++++++++++++++++----------------------- bot/utils/checks.py | 14 ++--- 2 files changed, 107 insertions(+), 83 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 506b17065..02b9d0359 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "aio-pika": { "hashes": [ - "sha256:6438e72963e459552f196a07a081a5f6dc54d42a474292b8497bd4a59554fc85", - "sha256:dc15b451dca6d2b1c504ab353e3f2fe7e7e252fdb1c219261b5412e1cafbc72d" + "sha256:c3eb639f7fc5c96355e7a227380989c9e0f342bb6612e6671ea76d188813ba45", + "sha256:ea26efd262d7c4cd4ac00fb968ede89e82c00ad331b47415e3c2353a4b91cbe0" ], "index": "pypi", - "version": "==4.6.3" + "version": "==4.9.1" }, "aiodns": { "hashes": [ @@ -57,6 +57,7 @@ "sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0", "sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07" ], + "index": "pypi", "version": "==3.4.4" }, "alabaster": { @@ -97,10 +98,10 @@ }, "certifi": { "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" ], - "version": "==2018.10.15" + "version": "==2018.11.29" }, "cffi": { "hashes": [ @@ -188,10 +189,10 @@ }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.7" + "version": "==2.8" }, "idna-ssl": { "hashes": [ @@ -307,37 +308,37 @@ }, "multidict": { "hashes": [ - "sha256:013eb6591ab95173fd3deb7667d80951abac80100335b3e97b5fa778c1bb4b91", - "sha256:0bffbbbb48db35f57dfb4733e943ac8178efb31aab5601cb7b303ee228ce96af", - "sha256:1a34aab1dfba492407c757532f665ba3282ec4a40b0d2f678bda828ef422ebb7", - "sha256:1b4b46a33f459a2951b0fd26c2d80639810631eb99b3d846d298b02d28a3e31d", - "sha256:1d616d80c37a388891bf760d64bc50cac7c61dbb7d7013f2373aa4b44936e9f0", - "sha256:225aefa7befbe05bd0116ef87e8cd76cbf4ac39457a66faf7fb5f3c2d7bea19a", - "sha256:2c9b28985ef7c830d5c7ea344d068bcdee22f8b6c251369dea98c3a814713d44", - "sha256:39e0600f8dd72acb011d09960da560ba3451b1eca8de5557c15705afc9d35f0e", - "sha256:3c642c40ea1ca074397698446893a45cd6059d5d071fc3ba3915c430c125320f", - "sha256:42357c90b488fac38852bcd7b31dcd36b1e2325413960304c28b8d98e6ff5fd4", - "sha256:6ac668f27dbdf8a69c31252f501e128a69a60b43a44e43d712fb58ce3e5dfcca", - "sha256:713683da2e3f1dd81a920c995df5dda51f1fff2b3995f5864c3ee782fcdcb96c", - "sha256:73b6e7853b6d3bc0eac795044e700467631dff37a5a33d3230122b03076ac2f9", - "sha256:77534c1b9f4a5d0962392cad3f668d1a04036b807618e3357eb2c50d8b05f7f7", - "sha256:77b579ef57e27457064bb6bb4c8e5ede866af071af60fe3576226136048c6dfa", - "sha256:82cf28f18c935d66c15a6f82fda766a4138d21e78532a1946b8ec603019ba0b8", - "sha256:937e8f12f9edc0d2e351c09fc3e7335a65eefb75406339d488ee46ef241f75d8", - "sha256:985dbf59e92f475573a04598f9a00f92b4fdb64fc41f1df2ea6f33b689319537", - "sha256:9c4fab7599ba8c0dbf829272c48c519625c2b7f5630b49925802f1af3a77f1f4", - "sha256:9e8772be8455b49a85ad6dbf6ce433da7856ba481d6db36f53507ae540823b15", - "sha256:a06d6d88ce3be4b54deabd078810e3c077a8b2e20f0ce541c979b5dd49337031", - "sha256:a1da0cdc3bc45315d313af976dab900888dbb477d812997ee0e6e4ea43d325e5", - "sha256:a6652466a4800e9fde04bf0252e914fff5f05e2a40ee1453db898149624dfe04", - "sha256:a7f23523ea6a01f77e0c6da8aae37ab7943e35630a8d2eda7e49502f36b51b46", - "sha256:a87429da49f4c9fb37a6a171fa38b59a99efdeabffb34b4255a7a849ffd74a20", - "sha256:c26bb81d0d19619367a96593a097baec2d5a7b3a0cfd1e3a9470277505a465c2", - "sha256:d4f4545edb4987f00fde44241cef436bf6471aaac7d21c6bbd497cca6049f613", - "sha256:daabc2766a2b76b3bec2086954c48d5f215f75a335eaee1e89c8357922a3c4d5", - "sha256:f08c1dcac70b558183b3b755b92f1135a76fd1caa04009b89ddea57a815599aa" - ], - "version": "==4.5.1" + "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", + "sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3", + "sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef", + "sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b", + "sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73", + "sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc", + "sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3", + "sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd", + "sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351", + "sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941", + "sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d", + "sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1", + "sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b", + "sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a", + "sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3", + "sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7", + "sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0", + "sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0", + "sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014", + "sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5", + "sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036", + "sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d", + "sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a", + "sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce", + "sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1", + "sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a", + "sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9", + "sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7", + "sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b" + ], + "version": "==4.5.2" }, "packaging": { "hashes": [ @@ -350,13 +351,23 @@ "hashes": [ "sha256:00203f406818c3f45d47bb8fe7e67d3feddb8dcbbd45a289a1de7dd789226360", "sha256:0616f800f348664e694dddb0b0c88d26761dd5e9f34e1ed7b7a7d2da14b40cb7", + "sha256:091136f2a37e9ed6bd8ce96fbf5269199ba6edee490d64de7ac934316f31ecca", + "sha256:0d67ae9a5937b1348fa1d97c7dcb6b56aaef828ca6655298e96f2f3114ad829d", + "sha256:0e1aaddd00ee9014fe7a61b9da61427233fcd7c7f193b5efd6689e0ec36bc42f", "sha256:1f7908aab90c92ad85af9d2fec5fc79456a89b3adcc26314d2cde0e238bd789e", "sha256:2ea3517cd5779843de8a759c2349a3cd8d3893e03ab47053b66d5ec6f8bc4f93", + "sha256:39b662f65a067709a62943003c1e807d140e7fcf631fcfc66ebe905f8149b9f4", + "sha256:3ddc19447cf42ef3ec564ab7ebbd4f67838ba9816d739befe29dd70149c775bd", "sha256:48a9f0538c91fc136b3a576bee0e7cd174773dc9920b310c21dcb5519722e82c", "sha256:5280ebc42641a1283b7b1f2c20e5b936692198b9dd9995527c18b794850be1a8", + "sha256:576a8a7a57065dab968d9d18befa2594a7673dcdab78c9b1f34248410cc6118f", + "sha256:5e334a23c8f7cb6079987a2ed9978821a42b4323a3a3bdbc132945348737f9a9", "sha256:5e34e4b5764af65551647f5cc67cf5198c1d05621781d5173b342e5e55bf023b", "sha256:63b120421ab85cad909792583f83b6ca3584610c2fe70751e23f606a3c2e87f0", "sha256:696b5e0109fe368d0057f484e2e91717b49a03f1e310f857f133a4acec9f91dd", + "sha256:6cb528de694f503ea164541c151da6c18267727a7558e0c9716cc0383d89658a", + "sha256:7306d851d5a0cfac9ea07f1177783836f4b37292e5f224a534a52111cb6a6451", + "sha256:7e3e32346d991f1788026917d0a9c182d6d32dc757163eee7ca990f1f831499e", "sha256:870ed021a42b1b02b5fe4a739ea735f671a84128c0a666c705db2cb9abd528eb", "sha256:916da1c19e4012d06a372127d7140dae894806fad67ef44330e5600d77833581", "sha256:9303a289fa0811e1c6abd9ddebfc770556d7c3311cb2b32eff72164ddc49bc64", @@ -364,20 +375,30 @@ "sha256:987e1c94a33c93d9b209315bfda9faa54b8edfce6438a1e93ae866ba20de5956", "sha256:99a3bbdbb844f4fb5d6dd59fac836a40749781c1fa63c563bc216c27aef63f60", "sha256:99db8dc3097ceafbcff9cb2bff384b974795edeb11d167d391a02c7bfeeb6e16", + "sha256:a379526415f54f9462bc65a4da76fb0acc05e3b2a21717dde79621cf4377e0e6", "sha256:a5a96cf49eb580756a44ecf12949e52f211e20bffbf5a95760ac14b1e499cd37", + "sha256:a844b5d8120f99fb7cd276ff544ac5bd562b0c053760d59694e6bf747c6ca7f5", + "sha256:a9284368e81a67a7f47d5ef1ef7e4f11a4f688485879f44cf5f9090bba1f9d94", "sha256:aa6ca3eb56704cdc0d876fc6047ffd5ee960caad52452fbee0f99908a141a0ae", "sha256:aade5e66795c94e4a2b2624affeea8979648d1b0ae3fcee17e74e2c647fc4a8a", "sha256:b78905860336c1d292409e3df6ad39cc1f1c7f0964e66844bbc2ebfca434d073", "sha256:b92f521cdc4e4a3041cc343625b699f20b0b5f976793fb45681aac1efda565f8", + "sha256:bb2baf44e97811687893873eab8cf9f18b40321cc15d15ff9f91dc031e30631f", "sha256:bfde84bbd6ae5f782206d454b67b7ee8f7f818c29b99fd02bf022fd33bab14cb", "sha256:c2b62d3df80e694c0e4a0ed47754c9480521e25642251b3ab1dff050a4e60409", + "sha256:c55d348c1c65896c1bd804527de4880d251ae832acf90d74ad525bb79e77d55c", "sha256:c5e2be6c263b64f6f7656e23e18a4a9980cffc671442795682e8c4e4f815dd9f", "sha256:c99aa3c63104e0818ec566f8ff3942fb7c7a8f35f9912cb63fd8e12318b214b2", "sha256:dae06620d3978da346375ebf88b9e2dd7d151335ba668c995aea9ed07af7add4", "sha256:db5499d0710823fa4fb88206050d46544e8f0e0136a9a5f5570b026584c8fd74", + "sha256:dcd3cd17d291e01e47636101c4a6638ffb44c842d009973e3b5c1b67ff718c58", + "sha256:f12df6b45abc18f27f6e21ce26f7cbf7aa19820911462e46536e22085658ca1e", "sha256:f36baafd82119c4a114b9518202f2a983819101dcc14b26e43fc12cbefdce00e", "sha256:f52b79c8796d81391ab295b04e520bda6feed54d54931708872e8f9ae9db0ea1", - "sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888" + "sha256:fa2a50f762d06d84125db0b95d0121e9c640afa7edc23fc0848896760a390f8e", + "sha256:fa49bb60792b542b95ca93a39041e7113843093ce3cfd216870118eb3798fcc9", + "sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888", + "sha256:ffbccfe1c077b5f41738bd719518213c217be7a7a12a7e74113d05a0d6617390" ], "index": "pypi", "version": "==5.3.0" @@ -416,10 +437,10 @@ }, "pygments": { "hashes": [ - "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", - "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", + "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" ], - "version": "==2.2.0" + "version": "==2.3.1" }, "pynacl": { "hashes": [ @@ -505,11 +526,11 @@ }, "requests": { "hashes": [ - "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", - "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], "index": "pypi", - "version": "==2.20.1" + "version": "==2.21.0" }, "shortuuid": { "hashes": [ @@ -519,10 +540,10 @@ }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], - "version": "==1.11.0" + "version": "==1.12.0" }, "snowballstemmer": { "hashes": [ @@ -533,11 +554,11 @@ }, "sphinx": { "hashes": [ - "sha256:120732cbddb1b2364471c3d9f8bfd4b0c5b550862f99a65736c77f970b142aea", - "sha256:b348790776490894e0424101af9c8413f2a86831524bd55c5f379d3e3e12ca64" + "sha256:429e3172466df289f0f742471d7e30ba3ee11f3b5aecd9a840480d03f14bcfe5", + "sha256:c4cb17ba44acffae3d3209646b6baec1e215cad3065e852c68cc569d4df1b9f8" ], "index": "pypi", - "version": "==1.8.2" + "version": "==1.8.3" }, "sphinxcontrib-websupport": { "hashes": [ @@ -581,17 +602,19 @@ }, "yarl": { "hashes": [ - "sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9", - "sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee", - "sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308", - "sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357", - "sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78", - "sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8", - "sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1", - "sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4", - "sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7" - ], - "version": "==1.2.6" + "sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9", + "sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f", + "sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb", + "sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320", + "sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842", + "sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0", + "sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829", + "sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310", + "sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4", + "sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8", + "sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1" + ], + "version": "==1.3.0" } }, "develop": { @@ -604,10 +627,10 @@ }, "certifi": { "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" ], - "version": "==2018.10.15" + "version": "==2018.11.29" }, "chardet": { "hashes": [ @@ -686,10 +709,10 @@ }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.7" + "version": "==2.8" }, "mccabe": { "hashes": [ @@ -707,6 +730,7 @@ }, "pycodestyle": { "hashes": [ + "sha256:74abc4e221d393ea5ce1f129ea6903209940c1ecd29e002e8c6933c2b21026e0", "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" ], @@ -745,11 +769,11 @@ }, "requests": { "hashes": [ - "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", - "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], "index": "pypi", - "version": "==2.20.1" + "version": "==2.21.0" }, "safety": { "hashes": [ @@ -761,10 +785,10 @@ }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], - "version": "==1.11.0" + "version": "==1.12.0" }, "urllib3": { "hashes": [ diff --git a/bot/utils/checks.py b/bot/utils/checks.py index d07b09fc8..37dc657f7 100644 --- a/bot/utils/checks.py +++ b/bot/utils/checks.py @@ -12,16 +12,16 @@ def with_role_check(ctx: Context, *role_ids: int) -> bool: """ if not ctx.guild: # Return False in a DM - log.debug(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " + log.trace(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " "This command is restricted by the with_role decorator. Rejecting request.") return False for role in ctx.author.roles: if role.id in role_ids: - log.debug(f"{ctx.author} has the '{role.name}' role, and passes the check.") + log.trace(f"{ctx.author} has the '{role.name}' role, and passes the check.") return True - log.debug(f"{ctx.author} does not have the required role to use " + log.trace(f"{ctx.author} does not have the required role to use " f"the '{ctx.command.name}' command, so the request is rejected.") return False @@ -33,13 +33,13 @@ def without_role_check(ctx: Context, *role_ids: int) -> bool: """ if not ctx.guild: # Return False in a DM - log.debug(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " + log.trace(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " "This command is restricted by the without_role decorator. Rejecting request.") return False - author_roles = [role.id for role in ctx.author.roles] + author_roles = (role.id for role in ctx.author.roles) check = all(role not in author_roles for role in role_ids) - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + log.trace(f"{ctx.author} tried to call the '{ctx.command.name}' command. " f"The result of the without_role check was {check}.") return check @@ -51,6 +51,6 @@ def in_channel_check(ctx: Context, channel_id: int) -> bool: """ check = ctx.channel.id == channel_id - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + log.trace(f"{ctx.author} tried to call the '{ctx.command.name}' command. " f"The result of the in_channel check was {check}.") return check -- cgit v1.2.3 From 9bec011dee4ef3f0968aa44d2459a9367d25313c Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 29 Dec 2018 13:10:38 -0500 Subject: Add optional return to modlog post coro Enables generation of a context for AntiSpam to reference for posting infractions --- bot/cogs/modlog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 1d1546d5b..ef4544f81 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -123,7 +123,8 @@ class ModLog: if ping_everyone: content = "@everyone" - await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) + log_message = await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) + return self.bot.get_context(log_message) # Optionally return for use with antispam async def on_guild_channel_create(self, channel: GUILD_CHANNEL): if channel.guild.id != GuildConstant.id: -- cgit v1.2.3 From 9698cf42a22e0637c1afc21dbf3c8282829ccff0 Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 29 Dec 2018 13:58:03 -0500 Subject: Add infraction posting for AntiSpam mutes --- bot/cogs/antispam.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index d5b72718c..052fd48b2 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -15,6 +15,7 @@ from bot.constants import ( Colours, DEBUG_MODE, Event, Guild as GuildConfig, Icons, Roles, ) +from bot.utils.moderation import post_infraction from bot.utils.time import humanize_delta @@ -133,7 +134,8 @@ class AntiSpam: mod_alert_message += f"{content}" - await self.mod_log.send_log_message( + # Return the mod log message Context that we can use to post the infraction + mod_log_message = await self.mod_log.send_log_message( icon_url=Icons.filtering, colour=Colour(Colours.soft_red), title=f"Spam detected!", @@ -143,28 +145,30 @@ class AntiSpam: ping_everyone=AntiSpamConfig.ping_everyone ) - await member.add_roles(self.muted_role, reason=reason) + # Post AntiSpam mute as a regular infraction so it can be reversed + ctx = await self.bot.get_context(mod_log_message) + response_object = await post_infraction(ctx, member, type="mute", reason=reason, duration=remove_role_after) + if response_object is None: + return # Appropriate error(s) are already raised by post_infraction + + self.mod_log.ignore(Event.member_update, member.id) + await member.add_roles(self._muted_role, reason=reason) + + loop = asyncio.get_event_loop() + infraction_object = response_object["infraction"] + self.schedule_task(loop, infraction_object["id"], infraction_object) + description = textwrap.dedent(f""" **Channel**: {msg.channel.mention} **User**: {msg.author.mention} (`{msg.author.id}`) **Reason**: {reason} Role will be removed after {human_duration}. """) - await self.mod_log.send_log_message( icon_url=Icons.user_mute, colour=Colour(Colours.soft_red), title="User muted", text=description ) - await asyncio.sleep(remove_role_after) - await member.remove_roles(self.muted_role, reason="AntiSpam mute expired") - - await self.mod_log.send_log_message( - icon_url=Icons.user_mute, colour=Colour(Colours.soft_green), - title="User unmuted", - text=f"Was muted by `AntiSpam` cog for {human_duration}." - ) - async def maybe_delete_messages(self, channel: TextChannel, messages: List[Message]): # Is deletion of offending messages actually enabled? if AntiSpamConfig.clean_offending: -- cgit v1.2.3 From 1e546b037a9a4159e047750cd3b389e23f35b4a3 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Wed, 2 Jan 2019 02:25:33 +1000 Subject: Tidy, correct order of actions and notifications. --- bot/cogs/moderation.py | 535 ++++++++++++++++++++++++++++++------------------- bot/cogs/modlog.py | 5 +- 2 files changed, 335 insertions(+), 205 deletions(-) diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py index ac08d3dd4..e9acc27b9 100644 --- a/bot/cogs/moderation.py +++ b/bot/cogs/moderation.py @@ -45,7 +45,7 @@ def proxy_user(user_id: str) -> Object: class Moderation(Scheduler): """ - Rowboat replacement moderation tools. + Server moderation tools. """ def __init__(self, bot: Bot): @@ -66,32 +66,32 @@ class Moderation(Scheduler): headers=self.headers ) infraction_list = await response.json() - loop = asyncio.get_event_loop() for infraction_object in infraction_list: if infraction_object["expires_at"] is not None: - self.schedule_task(loop, infraction_object["id"], infraction_object) + self.schedule_task(self.bot.loop, infraction_object["id"], infraction_object) # region: Permanent infractions @with_role(*MODERATION_ROLES) - @command(name="warn") + @command() async def warn(self, ctx: Context, user: Union[User, proxy_user], *, reason: str = None): """ Create a warning infraction in the database for a user. - :param user: accepts user mention, ID, etc. - :param reason: The reason for the warning. + + **`user`:** Accepts user mention, ID, etc. + **`reason`:** The reason for the warning. """ + response_object = await post_infraction(ctx, user, type="warning", reason=reason) + if response_object is None: + return + notified = await self.notify_infraction( user=user, infr_type="Warning", reason=reason ) - response_object = await post_infraction(ctx, user, type="warning", reason=reason) - if response_object is None: - return - dm_result = ":incoming_envelope: " if notified else "" action = f"{dm_result}:ok_hand: warned {user.mention}" @@ -100,10 +100,13 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") - if not notified: - await self.log_notify_failure(user, ctx.author, "warning") + if notified: + dm_status = "Sent" + log_content = None + else: + dm_status = "**Failed**" + log_content = ctx.author.mention - # Send a message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_warn, colour=Colour(Colours.soft_red), @@ -111,32 +114,41 @@ class Moderation(Scheduler): thumbnail=user.avatar_url_as(static_format="png"), text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) - Actor: {ctx.message.author} + Actor: {ctx.author} + DM: {dm_status} Reason: {reason} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) @with_role(*MODERATION_ROLES) - @command(name="kick") + @command() async def kick(self, ctx: Context, user: Member, *, reason: str = None): """ Kicks a user. - :param user: accepts user mention, ID, etc. - :param reason: The reason for the kick. + + **`user`:** Accepts user mention, ID, etc. + **`reason`:** The reason for the kick. """ + response_object = await post_infraction(ctx, user, type="kick", reason=reason) + if response_object is None: + return + notified = await self.notify_infraction( user=user, infr_type="Kick", reason=reason ) - response_object = await post_infraction(ctx, user, type="kick", reason=reason) - if response_object is None: - return - self.mod_log.ignore(Event.member_remove, user.id) - await user.kick(reason=reason) + + try: + await user.kick(reason=reason) + action_result = True + except Forbidden: + action_result = False dm_result = ":incoming_envelope: " if notified else "" action = f"{dm_result}:ok_hand: kicked {user.mention}" @@ -146,31 +158,39 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") - if not notified: - await self.log_notify_failure(user, ctx.author, "kick") + dm_status = "Sent" if notified else "**Failed**" + title = "Member kicked" if action_result else "Member kicked (Failed)" + log_content = None if all((notified, action_result)) else ctx.author.mention - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.sign_out, colour=Colour(Colours.soft_red), - title="Member kicked", + title=title, thumbnail=user.avatar_url_as(static_format="png"), text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} + DM: {dm_status} Reason: {reason} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) @with_role(*MODERATION_ROLES) - @command(name="ban") + @command() async def ban(self, ctx: Context, user: Union[User, proxy_user], *, reason: str = None): """ Create a permanent ban infraction in the database for a user. - :param user: Accepts user mention, ID, etc. - :param reason: The reason for the ban. + + **`user`:** Accepts user mention, ID, etc. + **`reason`:** The reason for the ban. """ + response_object = await post_infraction(ctx, user, type="ban", reason=reason) + if response_object is None: + return + notified = await self.notify_infraction( user=user, infr_type="Ban", @@ -178,13 +198,14 @@ class Moderation(Scheduler): reason=reason ) - response_object = await post_infraction(ctx, user, type="ban", reason=reason) - if response_object is None: - return - self.mod_log.ignore(Event.member_ban, user.id) self.mod_log.ignore(Event.member_remove, user.id) - await ctx.guild.ban(user, reason=reason, delete_message_days=0) + + try: + await ctx.guild.ban(user, reason=reason, delete_message_days=0) + action_result = True + except Forbidden: + action_result = False dm_result = ":incoming_envelope: " if notified else "" action = f"{dm_result}:ok_hand: permanently banned {user.mention}" @@ -194,46 +215,51 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") - if not notified: - await self.log_notify_failure(user, ctx.author, "ban") + dm_status = "Sent" if notified else "**Failed**" + log_content = None if all((notified, action_result)) else ctx.author.mention + title = "Member permanently banned" + if not action_result: + title += " (Failed)" - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_ban, colour=Colour(Colours.soft_red), - title="Member permanently banned", + title=title, thumbnail=user.avatar_url_as(static_format="png"), text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} + DM: {dm_status} Reason: {reason} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) @with_role(*MODERATION_ROLES) - @command(name="mute") + @command() async def mute(self, ctx: Context, user: Member, *, reason: str = None): """ Create a permanent mute infraction in the database for a user. - :param user: Accepts user mention, ID, etc. - :param reason: The reason for the mute. - """ - notified = await self.notify_infraction( - user=user, - infr_type="Mute", - duration="Permanent", - reason=reason - ) + **`user`:** Accepts user mention, ID, etc. + **`reason`:** The reason for the mute. + """ response_object = await post_infraction(ctx, user, type="mute", reason=reason) if response_object is None: return - # add the mute role self.mod_log.ignore(Event.member_update, user.id) await user.add_roles(self._muted_role, reason=reason) + notified = await self.notify_infraction( + user=user, + infr_type="Mute", + duration="Permanent", + reason=reason + ) + dm_result = ":incoming_envelope: " if notified else "" action = f"{dm_result}:ok_hand: permanently muted {user.mention}" @@ -242,10 +268,13 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") - if not notified: - await self.log_notify_failure(user, ctx.author, "mute") + if notified: + dm_status = "Sent" + log_content = None + else: + dm_status = "**Failed**" + log_content = ctx.author.mention - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_mute, colour=Colour(Colours.soft_red), @@ -254,42 +283,47 @@ class Moderation(Scheduler): text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} + DM: {dm_status} Reason: {reason} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) # endregion # region: Temporary infractions @with_role(*MODERATION_ROLES) - @command(name="tempmute") + @command() async def tempmute(self, ctx: Context, user: Member, duration: str, *, reason: str = None): """ Create a temporary mute infraction in the database for a user. - :param user: Accepts user mention, ID, etc. - :param duration: The duration for the temporary mute infraction - :param reason: The reason for the temporary mute. + + **`user`:** Accepts user mention, ID, etc. + **`duration`:** The duration for the temporary mute infraction + **`reason`:** The reason for the temporary mute. """ - notified = await self.notify_infraction( - user=user, - infr_type="Mute", - duration=duration, - reason=reason + response_object = await post_infraction( + ctx, user, type="mute", reason=reason, duration=duration ) - - response_object = await post_infraction(ctx, user, type="mute", reason=reason, duration=duration) if response_object is None: return self.mod_log.ignore(Event.member_update, user.id) await user.add_roles(self._muted_role, reason=reason) + notified = await self.notify_infraction( + user=user, + infr_type="Mute", + duration=duration, + reason=reason + ) + infraction_object = response_object["infraction"] infraction_expiration = infraction_object["expires_at"] - loop = asyncio.get_event_loop() - self.schedule_task(loop, infraction_object["id"], infraction_object) + self.schedule_task(ctx.bot.loop, infraction_object["id"], infraction_object) dm_result = ":incoming_envelope: " if notified else "" action = f"{dm_result}:ok_hand: muted {user.mention} until {infraction_expiration}" @@ -299,10 +333,13 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") - if not notified: - await self.log_notify_failure(user, ctx.author, "mute") + if notified: + dm_status = "Sent" + log_content = None + else: + dm_status = "**Failed**" + log_content = ctx.author.mention - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_mute, colour=Colour(Colours.soft_red), @@ -311,22 +348,34 @@ class Moderation(Scheduler): text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} + DM: {dm_status} Reason: {reason} Duration: {duration} Expires: {infraction_expiration} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) @with_role(*MODERATION_ROLES) - @command(name="tempban") - async def tempban(self, ctx: Context, user: Union[User, proxy_user], duration: str, *, reason: str = None): + @command() + async def tempban( + self, ctx: Context, user: Union[User, proxy_user], duration: str, *, reason: str = None + ): """ Create a temporary ban infraction in the database for a user. - :param user: Accepts user mention, ID, etc. - :param duration: The duration for the temporary ban infraction - :param reason: The reason for the temporary ban. + + **`user`:** Accepts user mention, ID, etc. + **`duration`:** The duration for the temporary ban infraction + **`reason`:** The reason for the temporary ban. """ + response_object = await post_infraction( + ctx, user, type="ban", reason=reason, duration=duration + ) + if response_object is None: + return + notified = await self.notify_infraction( user=user, infr_type="Ban", @@ -334,20 +383,19 @@ class Moderation(Scheduler): reason=reason ) - response_object = await post_infraction(ctx, user, type="ban", reason=reason, duration=duration) - if response_object is None: - return - self.mod_log.ignore(Event.member_ban, user.id) self.mod_log.ignore(Event.member_remove, user.id) - guild: Guild = ctx.guild - await guild.ban(user, reason=reason, delete_message_days=0) + + try: + await ctx.guild.ban(user, reason=reason, delete_message_days=0) + action_result = True + except Forbidden: + action_result = False infraction_object = response_object["infraction"] infraction_expiration = infraction_object["expires_at"] - loop = asyncio.get_event_loop() - self.schedule_task(loop, infraction_object["id"], infraction_object) + self.schedule_task(ctx.bot.loop, infraction_object["id"], infraction_object) dm_result = ":incoming_envelope: " if notified else "" action = f"{dm_result}:ok_hand: banned {user.mention} until {infraction_expiration}" @@ -357,67 +405,74 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") - if not notified: - await self.log_notify_failure(user, ctx.author, "ban") + dm_status = "Sent" if notified else "**Failed**" + log_content = None if all((notified, action_result)) else ctx.author.mention + title = "Member temporarily banned" + if not action_result: + title += " (Failed)" - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_ban, colour=Colour(Colours.soft_red), thumbnail=user.avatar_url_as(static_format="png"), - title="Member temporarily banned", + title=title, text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} + DM: {dm_status} Reason: {reason} Duration: {duration} Expires: {infraction_expiration} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) # endregion # region: Permanent shadow infractions @with_role(*MODERATION_ROLES) - @command(name="shadow_warn", hidden=True, aliases=['shadowwarn', 'swarn', 'note']) - async def shadow_warn(self, ctx: Context, user: Union[User, proxy_user], *, reason: str = None): + @command(hidden=True, aliases=['shadowwarn', 'swarn', 'shadow_warn']) + async def note(self, ctx: Context, user: Union[User, proxy_user], *, reason: str = None): """ - Create a warning infraction in the database for a user. - :param user: accepts user mention, ID, etc. - :param reason: The reason for the warning. + Create a private infraction note in the database for a user. + + **`user`:** accepts user mention, ID, etc. + **`reason`:** The reason for the warning. """ - response_object = await post_infraction(ctx, user, type="warning", reason=reason, hidden=True) + response_object = await post_infraction( + ctx, user, type="warning", reason=reason, hidden=True + ) if response_object is None: return if reason is None: - result_message = f":ok_hand: note added for {user.mention}." + await ctx.send(f":ok_hand: note added for {user.mention}.") else: - result_message = f":ok_hand: note added for {user.mention} ({reason})." - - await ctx.send(result_message) + await ctx.send(f":ok_hand: note added for {user.mention} ({reason}).") - # Send a message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_warn, colour=Colour(Colours.soft_red), - title="Member shadow warned", + title="Member note added", thumbnail=user.avatar_url_as(static_format="png"), text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} Reason: {reason} - """) + """), + footer=f"ID {response_object['infraction']['id']}" ) @with_role(*MODERATION_ROLES) - @command(name="shadow_kick", hidden=True, aliases=['shadowkick', 'skick']) + @command(hidden=True, aliases=['shadowkick', 'skick']) async def shadow_kick(self, ctx: Context, user: Member, *, reason: str = None): """ Kicks a user. - :param user: accepts user mention, ID, etc. - :param reason: The reason for the kick. + + **`user`:** accepts user mention, ID, etc. + **`reason`:** The reason for the kick. """ response_object = await post_infraction(ctx, user, type="kick", reason=reason, hidden=True) @@ -425,35 +480,47 @@ class Moderation(Scheduler): return self.mod_log.ignore(Event.member_remove, user.id) - await user.kick(reason=reason) + + try: + await user.kick(reason=reason) + action_result = True + except Forbidden: + action_result = False if reason is None: - result_message = f":ok_hand: kicked {user.mention}." + await ctx.send(f":ok_hand: kicked {user.mention}.") else: - result_message = f":ok_hand: kicked {user.mention} ({reason})." + await ctx.send(f":ok_hand: kicked {user.mention} ({reason}).") - await ctx.send(result_message) + title = "Member shadow kicked" + if action_result: + log_content = None + else: + log_content = ctx.author.mention + title += " (Failed)" - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.sign_out, colour=Colour(Colours.soft_red), - title="Member shadow kicked", + title=title, thumbnail=user.avatar_url_as(static_format="png"), text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} Reason: {reason} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) @with_role(*MODERATION_ROLES) - @command(name="shadow_ban", hidden=True, aliases=['shadowban', 'sban']) + @command(hidden=True, aliases=['shadowban', 'sban']) async def shadow_ban(self, ctx: Context, user: Union[User, proxy_user], *, reason: str = None): """ Create a permanent ban infraction in the database for a user. - :param user: Accepts user mention, ID, etc. - :param reason: The reason for the ban. + + **`user`:** Accepts user mention, ID, etc. + **`reason`:** The reason for the ban. """ response_object = await post_infraction(ctx, user, type="ban", reason=reason, hidden=True) @@ -462,53 +529,61 @@ class Moderation(Scheduler): self.mod_log.ignore(Event.member_ban, user.id) self.mod_log.ignore(Event.member_remove, user.id) - await ctx.guild.ban(user, reason=reason, delete_message_days=0) + + try: + await ctx.guild.ban(user, reason=reason, delete_message_days=0) + action_result = True + except Forbidden: + action_result = False if reason is None: - result_message = f":ok_hand: permanently banned {user.mention}." + await ctx.send(f":ok_hand: permanently banned {user.mention}.") else: - result_message = f":ok_hand: permanently banned {user.mention} ({reason})." + await ctx.send(f":ok_hand: permanently banned {user.mention} ({reason}).") - await ctx.send(result_message) + title = "Member permanently banned" + if action_result: + log_content = None + else: + log_content = ctx.author.mention + title += " (Failed)" - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_ban, colour=Colour(Colours.soft_red), - title="Member permanently banned", + title=title, thumbnail=user.avatar_url_as(static_format="png"), text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} Reason: {reason} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) @with_role(*MODERATION_ROLES) - @command(name="shadow_mute", hidden=True, aliases=['shadowmute', 'smute']) + @command(hidden=True, aliases=['shadowmute', 'smute']) async def shadow_mute(self, ctx: Context, user: Member, *, reason: str = None): """ Create a permanent mute infraction in the database for a user. - :param user: Accepts user mention, ID, etc. - :param reason: The reason for the mute. + + **`user`:** Accepts user mention, ID, etc. + **`reason`:** The reason for the mute. """ response_object = await post_infraction(ctx, user, type="mute", reason=reason, hidden=True) if response_object is None: return - # add the mute role self.mod_log.ignore(Event.member_update, user.id) await user.add_roles(self._muted_role, reason=reason) if reason is None: - result_message = f":ok_hand: permanently muted {user.mention}." + await ctx.send(f":ok_hand: permanently muted {user.mention}.") else: - result_message = f":ok_hand: permanently muted {user.mention} ({reason})." - - await ctx.send(result_message) + await ctx.send(f":ok_hand: permanently muted {user.mention} ({reason}).") - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_mute, colour=Colour(Colours.soft_red), @@ -518,23 +593,29 @@ class Moderation(Scheduler): Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} Reason: {reason} - """) + """), + footer=f"ID {response_object['infraction']['id']}" ) # endregion # region: Temporary shadow infractions @with_role(*MODERATION_ROLES) - @command(name="shadow_tempmute", hidden=True, aliases=["shadowtempmute, stempmute"]) - async def shadow_tempmute(self, ctx: Context, user: Member, duration: str, *, reason: str = None): + @command(hidden=True, aliases=["shadowtempmute, stempmute"]) + async def shadow_tempmute( + self, ctx: Context, user: Member, duration: str, *, reason: str = None + ): """ Create a temporary mute infraction in the database for a user. - :param user: Accepts user mention, ID, etc. - :param duration: The duration for the temporary mute infraction - :param reason: The reason for the temporary mute. + + **`user`:** Accepts user mention, ID, etc. + **`duration`:** The duration for the temporary mute infraction + **`reason`:** The reason for the temporary mute. """ - response_object = await post_infraction(ctx, user, type="mute", reason=reason, duration=duration, hidden=True) + response_object = await post_infraction( + ctx, user, type="mute", reason=reason, duration=duration, hidden=True + ) if response_object is None: return @@ -544,17 +625,15 @@ class Moderation(Scheduler): infraction_object = response_object["infraction"] infraction_expiration = infraction_object["expires_at"] - loop = asyncio.get_event_loop() - self.schedule_expiration(loop, infraction_object) + self.schedule_expiration(ctx.bot.loop, infraction_object) if reason is None: - result_message = f":ok_hand: muted {user.mention} until {infraction_expiration}." + await ctx.send(f":ok_hand: muted {user.mention} until {infraction_expiration}.") else: - result_message = f":ok_hand: muted {user.mention} until {infraction_expiration} ({reason})." - - await ctx.send(result_message) + await ctx.send( + f":ok_hand: muted {user.mention} until {infraction_expiration} ({reason})." + ) - # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_mute, colour=Colour(Colours.soft_red), @@ -566,67 +645,84 @@ class Moderation(Scheduler): Reason: {reason} Duration: {duration} Expires: {infraction_expiration} - """) + """), + footer=f"ID {response_object['infraction']['id']}" ) @with_role(*MODERATION_ROLES) - @command(name="shadow_tempban", hidden=True, aliases=["shadowtempban, stempban"]) + @command(hidden=True, aliases=["shadowtempban, stempban"]) async def shadow_tempban( self, ctx: Context, user: Union[User, proxy_user], duration: str, *, reason: str = None ): """ Create a temporary ban infraction in the database for a user. - :param user: Accepts user mention, ID, etc. - :param duration: The duration for the temporary ban infraction - :param reason: The reason for the temporary ban. + + **`user`:** Accepts user mention, ID, etc. + **`duration`:** The duration for the temporary ban infraction + **`reason`:** The reason for the temporary ban. """ - response_object = await post_infraction(ctx, user, type="ban", reason=reason, duration=duration, hidden=True) + response_object = await post_infraction( + ctx, user, type="ban", reason=reason, duration=duration, hidden=True + ) if response_object is None: return self.mod_log.ignore(Event.member_ban, user.id) self.mod_log.ignore(Event.member_remove, user.id) - guild: Guild = ctx.guild - await guild.ban(user, reason=reason, delete_message_days=0) + + try: + await ctx.guild.ban(user, reason=reason, delete_message_days=0) + action_result = True + except Forbidden: + action_result = False infraction_object = response_object["infraction"] infraction_expiration = infraction_object["expires_at"] - loop = asyncio.get_event_loop() - self.schedule_expiration(loop, infraction_object) + self.schedule_expiration(ctx.bot.loop, infraction_object) if reason is None: - result_message = f":ok_hand: banned {user.mention} until {infraction_expiration}." + await ctx.send(f":ok_hand: banned {user.mention} until {infraction_expiration}.") else: - result_message = f":ok_hand: banned {user.mention} until {infraction_expiration} ({reason})." + await ctx.send( + f":ok_hand: banned {user.mention} until {infraction_expiration} ({reason})." + ) - await ctx.send(result_message) + title = "Member temporarily banned" + if action_result: + log_content = None + else: + log_content = ctx.author.mention + title += " (Failed)" # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_ban, colour=Colour(Colours.soft_red), thumbnail=user.avatar_url_as(static_format="png"), - title="Member temporarily banned", + title=title, text=textwrap.dedent(f""" Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} Reason: {reason} Duration: {duration} Expires: {infraction_expiration} - """) + """), + content=log_content, + footer=f"ID {response_object['infraction']['id']}" ) # endregion # region: Remove infractions (un- commands) @with_role(*MODERATION_ROLES) - @command(name="unmute") + @command() async def unmute(self, ctx: Context, user: Member): """ Deactivates the active mute infraction for a user. - :param user: Accepts user mention, ID, etc. + + **`user`:** Accepts user mention, ID, etc. """ try: @@ -638,16 +734,20 @@ class Moderation(Scheduler): ), headers=self.headers ) + response_object = await response.json() if "error_code" in response_object: - await ctx.send(f":x: There was an error removing the infraction: {response_object['error_message']}") - return + return await ctx.send( + ":x: There was an error removing the infraction: " + f"{response_object['error_message']}" + ) infraction_object = response_object["infraction"] if infraction_object is None: # no active infraction - await ctx.send(f":x: There is no active mute infraction for user {user.mention}.") - return + return await ctx.send( + f":x: There is no active mute infraction for user {user.mention}." + ) await self._deactivate_infraction(infraction_object) if infraction_object["expires_at"] is not None: @@ -660,11 +760,16 @@ class Moderation(Scheduler): icon_url=Icons.user_unmute ) - dm_result = ":incoming_envelope: " if notified else "" - await ctx.send(f"{dm_result}:ok_hand: Un-muted {user.mention}.") + if notified: + dm_status = "Sent" + dm_emoji = ":incoming_envelope: " + log_content = None + else: + dm_status = "**Failed**" + dm_emoji = "" + log_content = ctx.author.mention - if not notified: - await self.log_notify_failure(user, ctx.author, "unmute") + await ctx.send(f"{dm_emoji}:ok_hand: Un-muted {user.mention}.") # Send a log message to the mod log await self.mod_log.send_log_message( @@ -676,19 +781,23 @@ class Moderation(Scheduler): Member: {user.mention} (`{user.id}`) Actor: {ctx.message.author} Intended expiry: {infraction_object['expires_at']} - """) + DM: {dm_status} + """), + footer=infraction_object["id"], + content=log_content ) - except Exception: - log.exception("There was an error removing an infraction.") + + except Exception as e: + log.exception("There was an error removing an infraction.", exc_info=e) await ctx.send(":x: There was an error removing the infraction.") - return @with_role(*MODERATION_ROLES) - @command(name="unban") + @command() async def unban(self, ctx: Context, user: Union[User, proxy_user]): """ Deactivates the active ban infraction for a user. - :param user: Accepts user mention, ID, etc. + + **`user`:** Accepts user mention, ID, etc. """ try: @@ -702,14 +811,17 @@ class Moderation(Scheduler): ) response_object = await response.json() if "error_code" in response_object: - await ctx.send(f":x: There was an error removing the infraction: {response_object['error_message']}") - return + return await ctx.send( + ":x: There was an error removing the infraction: " + f"{response_object['error_message']}" + ) infraction_object = response_object["infraction"] if infraction_object is None: # no active infraction - await ctx.send(f":x: There is no active ban infraction for user {user.mention}.") - return + return await ctx.send( + f":x: There is no active ban infraction for user {user.mention}." + ) await self._deactivate_infraction(infraction_object) if infraction_object["expires_at"] is not None: @@ -732,7 +844,6 @@ class Moderation(Scheduler): except Exception: log.exception("There was an error removing an infraction.") await ctx.send(":x: There was an error removing the infraction.") - return # endregion # region: Edit infraction commands @@ -755,10 +866,12 @@ class Moderation(Scheduler): @infraction_edit_group.command(name="duration") async def edit_duration(self, ctx: Context, infraction_id: str, duration: str): """ - Sets the duration of the given infraction, relative to the time of updating. - :param infraction_id: the id (UUID) of the infraction - :param duration: the new duration of the infraction, relative to the time of updating. Use "permanent" to mark - the infraction as permanent. + Sets the duration of the given infraction, relative to the time of + updating. + + **`infraction_id`:** The ID (UUID) of the infraction. + **`duration`:** The new duration of the infraction, relative to the + time of updating. Use "permanent" to the infraction as permanent. """ try: @@ -784,8 +897,10 @@ class Moderation(Scheduler): ) response_object = await response.json() if "error_code" in response_object or response_object.get("success") is False: - await ctx.send(f":x: There was an error updating the infraction: {response_object['error_message']}") - return + return await ctx.send( + ":x: There was an error updating the infraction: " + f"{response_object['error_message']}" + ) infraction_object = response_object["infraction"] # Re-schedule @@ -796,7 +911,10 @@ class Moderation(Scheduler): if duration is None: await ctx.send(f":ok_hand: Updated infraction: marked as permanent.") else: - await ctx.send(f":ok_hand: Updated infraction: set to expire on {infraction_object['expires_at']}.") + await ctx.send( + ":ok_hand: Updated infraction: set to expire on " + f"{infraction_object['expires_at']}." + ) except Exception: log.exception("There was an error updating an infraction.") @@ -839,8 +957,8 @@ class Moderation(Scheduler): async def edit_reason(self, ctx: Context, infraction_id: str, *, reason: str): """ Sets the reason of the given infraction. - :param infraction_id: the id (UUID) of the infraction - :param reason: The new reason of the infraction + **`infraction_id`:** The ID (UUID) of the infraction. + **`reason`:** The new reason of the infraction. """ try: @@ -863,14 +981,15 @@ class Moderation(Scheduler): ) response_object = await response.json() if "error_code" in response_object or response_object.get("success") is False: - await ctx.send(f":x: There was an error updating the infraction: {response_object['error_message']}") - return + return await ctx.send( + ":x: There was an error updating the infraction: " + f"{response_object['error_message']}" + ) await ctx.send(f":ok_hand: Updated infraction: set reason to \"{reason}\".") except Exception: log.exception("There was an error updating an infraction.") - await ctx.send(":x: There was an error updating the infraction.") - return + return await ctx.send(":x: There was an error updating the infraction.") new_infraction = response_object["infraction"] prev_infraction = previous_object["infraction"] @@ -1004,6 +1123,7 @@ class Moderation(Scheduler): def schedule_expiration(self, loop: asyncio.AbstractEventLoop, infraction_object: dict): """ Schedules a task to expire a temporary infraction. + :param loop: the asyncio event loop :param infraction_object: the infraction object to expire at the end of the task """ @@ -1032,9 +1152,10 @@ class Moderation(Scheduler): async def _scheduled_task(self, infraction_object: dict): """ - A co-routine which marks an infraction as expired after the delay from the time of scheduling - to the time of expiration. At the time of expiration, the infraction is marked as inactive on the website, - and the expiration task is cancelled. + A co-routine which marks an infraction as expired after the delay from the time of + scheduling to the time of expiration. At the time of expiration, the infraction is + marked as inactive on the website, and the expiration task is cancelled. + :param infraction_object: the infraction in question """ @@ -1061,8 +1182,9 @@ class Moderation(Scheduler): async def _deactivate_infraction(self, infraction_object): """ - A co-routine which marks an infraction as inactive on the website. This co-routine does not cancel or - un-schedule an expiration task. + A co-routine which marks an infraction as inactive on the website. This co-routine does + not cancel or un-schedule an expiration task. + :param infraction_object: the infraction in question """ @@ -1116,7 +1238,8 @@ class Moderation(Scheduler): return lines.strip() async def notify_infraction( - self, user: Union[User, Member], infr_type: str, duration: str = None, reason: str = None + self, user: Union[User, Member], infr_type: str, duration: str = None, + reason: str = None ): """ Notify a user of their fresh infraction :) @@ -1150,7 +1273,8 @@ class Moderation(Scheduler): return await self.send_private_embed(user, embed) async def notify_pardon( - self, user: Union[User, Member], title: str, content: str, icon_url: str = Icons.user_verified + self, user: Union[User, Member], title: str, content: str, + icon_url: str = Icons.user_verified ): """ Notify a user that an infraction has been lifted. @@ -1197,7 +1321,10 @@ class Moderation(Scheduler): content=actor.mention, colour=Colour(Colours.soft_red), title="Notification Failed", - text=f"Direct message was unable to be sent.\nUser: {target.mention}\nType: {infraction_type}" + text=( + f"Direct message was unable to be sent.\nUser: {target.mention}\n" + f"Type: {infraction_type}" + ) ) # endregion diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 905f114c1..682fd1040 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -106,7 +106,7 @@ class ModLog: async def send_log_message( self, icon_url: Optional[str], colour: Colour, title: Optional[str], text: str, thumbnail: str = None, channel_id: int = Channels.modlog, ping_everyone: bool = False, - files: List[File] = None, content: str = None + files: List[File] = None, content: str = None, footer: str = None ): embed = Embed(description=text) @@ -125,6 +125,9 @@ class ModLog: else: content = "@everyone" + if footer: + embed.set_footer(text=footer) + await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) async def on_guild_channel_create(self, channel: GUILD_CHANNEL): -- cgit v1.2.3 From 9f8e4774ae6eeac8cbcece8c65c8de390c3300fd Mon Sep 17 00:00:00 2001 From: sco1 Date: Sun, 6 Jan 2019 00:27:49 -0500 Subject: Antispam Infraction Fixes Add muted role object to cog attributes Fix unawaited coroutine in modlog Adjust modlog message ctx variable to be more explicit Fix duration being sent to API as integer instead of string Fix temporary infraction being placed into a nonexistent schedule, now placed into the moderation cog's task schedule --- bot/cogs/antispam.py | 15 +++++++++------ bot/cogs/modlog.py | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 052fd48b2..cf52d30fa 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -45,7 +45,7 @@ WHITELISTED_ROLES = (Roles.owner, Roles.admin, Roles.moderator, Roles.helpers) class AntiSpam: def __init__(self, bot: Bot): self.bot = bot - self.muted_role = None + self._muted_role = Object(Roles.muted) @property def mod_log(self) -> ModLog: @@ -135,7 +135,7 @@ class AntiSpam: mod_alert_message += f"{content}" # Return the mod log message Context that we can use to post the infraction - mod_log_message = await self.mod_log.send_log_message( + mod_log_ctx = await self.mod_log.send_log_message( icon_url=Icons.filtering, colour=Colour(Colours.soft_red), title=f"Spam detected!", @@ -146,17 +146,20 @@ class AntiSpam: ) # Post AntiSpam mute as a regular infraction so it can be reversed - ctx = await self.bot.get_context(mod_log_message) - response_object = await post_infraction(ctx, member, type="mute", reason=reason, duration=remove_role_after) + response_object = await post_infraction( + mod_log_ctx, member, type="mute", reason=reason, duration=f"{remove_role_after}S" + ) if response_object is None: return # Appropriate error(s) are already raised by post_infraction self.mod_log.ignore(Event.member_update, member.id) await member.add_roles(self._muted_role, reason=reason) - loop = asyncio.get_event_loop() + # Insert ourselves into the moderation infraction loop infraction_object = response_object["infraction"] - self.schedule_task(loop, infraction_object["id"], infraction_object) + loop = asyncio.get_event_loop() + moderation_cog = self.bot.get_cog('Moderation') + moderation_cog.schedule_task(loop, infraction_object["id"], infraction_object) description = textwrap.dedent(f""" **Channel**: {msg.channel.mention} diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index f36c431e6..9d26fa925 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -126,7 +126,7 @@ class ModLog: content = "@everyone" log_message = await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) - return self.bot.get_context(log_message) # Optionally return for use with antispam + return await self.bot.get_context(log_message) # Optionally return for use with antispam async def on_guild_channel_create(self, channel: GUILD_CHANNEL): if channel.guild.id != GuildConstant.id: -- cgit v1.2.3 From 76951b4f1b0ada06b45f38271e6eb8c93ce8e51e Mon Sep 17 00:00:00 2001 From: sco1 Date: Sun, 6 Jan 2019 00:54:27 -0500 Subject: Invoke Moderation tempmute directly --- bot/cogs/antispam.py | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index cf52d30fa..800700a50 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -1,22 +1,18 @@ -import asyncio import logging -import textwrap from datetime import datetime, timedelta from typing import List -from dateutil.relativedelta import relativedelta from discord import Colour, Member, Message, Object, TextChannel from discord.ext.commands import Bot from bot import rules +from bot.cogs.moderation import Moderation from bot.cogs.modlog import ModLog from bot.constants import ( AntiSpam as AntiSpamConfig, Channels, Colours, DEBUG_MODE, Event, Guild as GuildConfig, Icons, Roles, ) -from bot.utils.moderation import post_infraction -from bot.utils.time import humanize_delta log = logging.getLogger(__name__) @@ -111,8 +107,6 @@ class AntiSpam: # Sanity check to ensure we're not lagging behind if self.muted_role not in member.roles: remove_role_after = AntiSpamConfig.punishment['remove_after'] - duration_delta = relativedelta(seconds=remove_role_after) - human_duration = humanize_delta(duration_delta) mod_alert_message = ( f"**Triggered by:** {member.display_name}#{member.discriminator} (`{member.id}`)\n" @@ -145,32 +139,8 @@ class AntiSpam: ping_everyone=AntiSpamConfig.ping_everyone ) - # Post AntiSpam mute as a regular infraction so it can be reversed - response_object = await post_infraction( - mod_log_ctx, member, type="mute", reason=reason, duration=f"{remove_role_after}S" - ) - if response_object is None: - return # Appropriate error(s) are already raised by post_infraction - - self.mod_log.ignore(Event.member_update, member.id) - await member.add_roles(self._muted_role, reason=reason) - - # Insert ourselves into the moderation infraction loop - infraction_object = response_object["infraction"] - loop = asyncio.get_event_loop() - moderation_cog = self.bot.get_cog('Moderation') - moderation_cog.schedule_task(loop, infraction_object["id"], infraction_object) - - description = textwrap.dedent(f""" - **Channel**: {msg.channel.mention} - **User**: {msg.author.mention} (`{msg.author.id}`) - **Reason**: {reason} - Role will be removed after {human_duration}. - """) - await self.mod_log.send_log_message( - icon_url=Icons.user_mute, colour=Colour(Colours.soft_red), - title="User muted", text=description - ) + # Run a tempmute + await mod_log_ctx.invoke(Moderation.tempmute, member, f"{remove_role_after}S", reason=reason) async def maybe_delete_messages(self, channel: TextChannel, messages: List[Message]): # Is deletion of offending messages actually enabled? -- cgit v1.2.3 From 23af034e656a82d0d6fe6268a5c41d010cbfaf4c Mon Sep 17 00:00:00 2001 From: Derek Date: Sun, 6 Jan 2019 19:08:34 -0500 Subject: Add free command --- bot/__main__.py | 1 + bot/cogs/free.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 bot/cogs/free.py diff --git a/bot/__main__.py b/bot/__main__.py index 3c40a3243..581fa5c8e 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -75,6 +75,7 @@ bot.load_extension("bot.cogs.tags") bot.load_extension("bot.cogs.token_remover") bot.load_extension("bot.cogs.utils") bot.load_extension("bot.cogs.wolfram") +bot.load_extension("bot.cogs.free") if has_rmq: bot.load_extension("bot.cogs.rmq") diff --git a/bot/cogs/free.py b/bot/cogs/free.py new file mode 100644 index 000000000..e50058106 --- /dev/null +++ b/bot/cogs/free.py @@ -0,0 +1,90 @@ +import logging +from datetime import datetime + +from discord import Colour, Embed, Member, utils +from discord.ext.commands import BucketType, Context, command, cooldown + + +log = logging.getLogger(__name__) + + +class Free: + """Tries to figure out which help channels are free.""" + + PYTHON_HELP_ID = 356013061213126657 + TIME_INACTIVE = 300 + + @command(name="free", aliases=('f',)) + @cooldown(1, 60.0, BucketType.channel) + async def free(self, ctx: Context, user: Member = None, seek: int = 2): + """ + Lists free help channels by likeliness of availability. + :param user: accepts user mention, ID, etc. + :param seek: How far back to check the last active message. + + seek is used only when this command is invoked in a help channel. + + When seek is 2, we are avoiding considering the last active message + in a channel to be the one that invoked this command. + + When seek is 3 or more, a user has been mentioned on the assumption + that they asked if the channel is free or they asked their question + in an active channel, and we want the message before that happened. + """ + free_channels = [] + python_help = utils.get(ctx.guild.categories, id=self.PYTHON_HELP_ID) + + if user is not None and seek == 2: + seek = 3 + elif seek > 10: + seek = 3 + + for channel in python_help.channels: + if channel.id == ctx.channel.id: + messages = await channel.history(limit=seek).flatten() + msg = messages[seek-1] + else: + messages = await channel.history(limit=1).flatten() + msg = messages[0] + + inactive = (datetime.utcnow() - msg.created_at).seconds + if inactive > self.TIME_INACTIVE: + free_channels.append((inactive, channel.id)) + + embed = Embed() + embed.colour = Colour.gold() + embed.title = "**Looking for a free help channel?**" + + if user is not None: + embed.description = f"**Hey <@{user.id}>!**\n\n" + else: + embed.description = "" + + if free_channels: + embed.description += "**The following channel{0} look{1} free:**\n\n**".format( + 's' if len(free_channels) > 1 else '', + '' if len(free_channels) > 1 else 's') + + for i, channel in enumerate(sorted(free_channels, reverse=True), 1): + inactive, ID = channel + minutes, seconds = divmod(inactive, 60) + if minutes > 60: + hours, minutes = divmod(minutes, 60) + embed.description += f'{i}. <#{ID}> inactive for {hours}h{minutes}m{seconds}s\n\n' + else: + embed.description += f'{i}. <#{ID}> inactive for {minutes}m{seconds}s\n\n' + + embed.description += ("**\nThese channels aren't guaranteed to be free, " + "so use your best judgement and check for yourself.") + else: + embed.description = ("**Doesn't look like any channels are available to me. " + "You're welcome to check for yourself to be sure. " + "If all channels are truly busy, please be patient " + "as one will likely be available soon.**") + + return await ctx.send(embed=embed) + + +def setup(bot): + bot.add_cog(Free()) + log.info("Cog loaded: Free") -- cgit v1.2.3 From ebad04f068598ddce567c2f855172206d0bb4e21 Mon Sep 17 00:00:00 2001 From: Derek Date: Sun, 6 Jan 2019 19:16:15 -0500 Subject: Change single quotes to double quotes for the sake of consistency --- bot/cogs/free.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index e50058106..73946dec1 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -70,9 +70,9 @@ class Free: minutes, seconds = divmod(inactive, 60) if minutes > 60: hours, minutes = divmod(minutes, 60) - embed.description += f'{i}. <#{ID}> inactive for {hours}h{minutes}m{seconds}s\n\n' + embed.description += f"{i}. <#{ID}> inactive for {hours}h{minutes}m{seconds}s\n\n" else: - embed.description += f'{i}. <#{ID}> inactive for {minutes}m{seconds}s\n\n' + embed.description += f"{i}. <#{ID}> inactive for {minutes}m{seconds}s\n\n" embed.description += ("**\nThese channels aren't guaranteed to be free, " "so use your best judgement and check for yourself.") -- cgit v1.2.3 From f0f37e657c6ec3c28fcfbdcd1dfbfb7e8d0729f1 Mon Sep 17 00:00:00 2001 From: Derek Date: Sun, 6 Jan 2019 19:48:58 -0500 Subject: Update constraint on seek so it can't be less than 1 --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 73946dec1..697e15f4b 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -36,7 +36,7 @@ class Free: if user is not None and seek == 2: seek = 3 - elif seek > 10: + elif not 0 < seek < 10: seek = 3 for channel in python_help.channels: -- cgit v1.2.3 From 4a78dabeec56a07c4e48ce40bb6fdf09b32110b3 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 03:28:45 -0500 Subject: Remove unnecessary return statement --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 697e15f4b..00e5fb8bb 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -82,7 +82,7 @@ class Free: "If all channels are truly busy, please be patient " "as one will likely be available soon.**") - return await ctx.send(embed=embed) + await ctx.send(embed=embed) def setup(bot): -- cgit v1.2.3 From 7e9dfc2fae4cf086245c504c04fd0b69c1ccce13 Mon Sep 17 00:00:00 2001 From: sco1 Date: Mon, 7 Jan 2019 09:55:58 -0500 Subject: Fix bbmessage emoji pointing to incorrect ID --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index ad87e44ac..7d088b6db 100644 --- a/config-default.yml +++ b/config-default.yml @@ -25,7 +25,7 @@ style: green_chevron: "<:greenchevron:418104310329769993>" red_chevron: "<:redchevron:418112778184818698>" white_chevron: "<:whitechevron:418110396973711363>" - bb_message: "<:bbmessage:472476937504423936>" + bb_message: "<:bbmessage:476273120999636992>" status_online: "<:status_online:470326272351010816>" status_idle: "<:status_idle:470326266625785866>" -- cgit v1.2.3 From 71eac59a69b36e21efb0bcf165ad8cd41ccaa1fb Mon Sep 17 00:00:00 2001 From: sco1 Date: Mon, 7 Jan 2019 17:51:00 -0500 Subject: Add mute infraction check to role restoration --- bot/cogs/events.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index edfc6e579..c604169c0 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -25,6 +25,7 @@ class Events: def __init__(self, bot: Bot): self.bot = bot + self.headers = {"X-API-KEY": Keys.site_api} @property def mod_log(self) -> ModLog: @@ -103,6 +104,29 @@ class Events: resp = await response.json() return resp["data"] + async def has_active_mute(self, user_id: str) -> bool: + """ + Check whether a user has any active mute infractions + """ + response = await self.bot.http_session.get( + URLs.site_infractions_user.format( + user_id=user_id + ), + params={"hidden": "True"}, + headers=self.headers + ) + infraction_list = await response.json() + + # Check for active mute infractions + if len(infraction_list) == 0: + # Short circuit + return False + + muted_check = any( + [infraction["active"] for infraction in infraction_list if infraction["type"].lower() == "mute"] + ) + return muted_check + async def on_command_error(self, ctx: Context, e: CommandError): command = ctx.command parent = None @@ -236,6 +260,10 @@ class Events: for role in RESTORE_ROLES: if role in old_roles: + # Check for mute roles that were not able to be removed and skip if present + if role == str(Roles.muted) and not await self.has_active_mute(str(member.id)): + continue + new_roles.append(Object(int(role))) for role in new_roles: -- cgit v1.2.3 From d5cb479be6925a10cf0c46e3088518f56a5a91b3 Mon Sep 17 00:00:00 2001 From: sco1 Date: Mon, 7 Jan 2019 19:16:03 -0500 Subject: Add line after docstring --- bot/cogs/events.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index c604169c0..77a15733d 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -108,6 +108,7 @@ class Events: """ Check whether a user has any active mute infractions """ + response = await self.bot.http_session.get( URLs.site_infractions_user.format( user_id=user_id -- cgit v1.2.3 From 72150b77659dd130ff80e3797179e75943f5c4a2 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 20:02:35 -0500 Subject: Add category constant --- bot/cogs/free.py | 4 +++- bot/constants.py | 7 +++++++ config-default.yml | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 00e5fb8bb..4566ade5f 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -4,6 +4,8 @@ from datetime import datetime from discord import Colour, Embed, Member, utils from discord.ext.commands import BucketType, Context, command, cooldown +from bot.constants import Categories + log = logging.getLogger(__name__) @@ -11,7 +13,7 @@ log = logging.getLogger(__name__) class Free: """Tries to figure out which help channels are free.""" - PYTHON_HELP_ID = 356013061213126657 + PYTHON_HELP_ID = Categories.python_help TIME_INACTIVE = 300 @command(name="free", aliases=('f',)) diff --git a/bot/constants.py b/bot/constants.py index bbe6c1604..aae9b05e5 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -315,6 +315,13 @@ class CleanMessages(metaclass=YAMLGetter): message_limit: int +class Categories(metaclass=YAMLGetter): + section = "guild" + subsection = "categories" + + python_help: int + + class Channels(metaclass=YAMLGetter): section = "guild" subsection = "channels" diff --git a/config-default.yml b/config-default.yml index ad87e44ac..7e2a22cc9 100644 --- a/config-default.yml +++ b/config-default.yml @@ -85,6 +85,9 @@ style: guild: id: 267624335836053506 + categories: + python_help: 356013061213126657 + channels: admins: &ADMINS 365960823622991872 announcements: 354619224620138496 -- cgit v1.2.3 From 5e794a0a7ab732ff2ac2ba37473ecd87413603d4 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 20:12:16 -0500 Subject: Update method for mentioning users and channels to be more idiomatic --- bot/cogs/free.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 4566ade5f..71ee18032 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -51,14 +51,14 @@ class Free: inactive = (datetime.utcnow() - msg.created_at).seconds if inactive > self.TIME_INACTIVE: - free_channels.append((inactive, channel.id)) + free_channels.append((inactive, channel)) embed = Embed() embed.colour = Colour.gold() embed.title = "**Looking for a free help channel?**" if user is not None: - embed.description = f"**Hey <@{user.id}>!**\n\n" + embed.description = f"**Hey {user.mention}!**\n\n" else: embed.description = "" @@ -67,14 +67,13 @@ class Free: 's' if len(free_channels) > 1 else '', '' if len(free_channels) > 1 else 's') - for i, channel in enumerate(sorted(free_channels, reverse=True), 1): - inactive, ID = channel + for i, (inactive, channel) in enumerate(sorted(free_channels, reverse=True), 1): minutes, seconds = divmod(inactive, 60) if minutes > 60: hours, minutes = divmod(minutes, 60) - embed.description += f"{i}. <#{ID}> inactive for {hours}h{minutes}m{seconds}s\n\n" + embed.description += f"{i}. {channel.mention} inactive for {hours}h{minutes}m{seconds}s\n\n" else: - embed.description += f"{i}. <#{ID}> inactive for {minutes}m{seconds}s\n\n" + embed.description += f"{i}. {channel.mention} inactive for {minutes}m{seconds}s\n\n" embed.description += ("**\nThese channels aren't guaranteed to be free, " "so use your best judgement and check for yourself.") -- cgit v1.2.3 From ffbfd4b3467af2becb62f7b7639678136c1b4c1a Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 20:15:07 -0500 Subject: Fix off by one --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 71ee18032..90caad7ae 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -69,7 +69,7 @@ class Free: for i, (inactive, channel) in enumerate(sorted(free_channels, reverse=True), 1): minutes, seconds = divmod(inactive, 60) - if minutes > 60: + if minutes > 59: hours, minutes = divmod(minutes, 60) embed.description += f"{i}. {channel.mention} inactive for {hours}h{minutes}m{seconds}s\n\n" else: -- cgit v1.2.3 From 3b3f8794cca30b152a7747e3ae7e061328a38ba6 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 20:30:30 -0500 Subject: Update method of obtaining last message to be simpler --- bot/cogs/free.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 90caad7ae..cd04275e2 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -46,8 +46,7 @@ class Free: messages = await channel.history(limit=seek).flatten() msg = messages[seek-1] else: - messages = await channel.history(limit=1).flatten() - msg = messages[0] + msg = await channel.history(limit=1).next() inactive = (datetime.utcnow() - msg.created_at).seconds if inactive > self.TIME_INACTIVE: -- cgit v1.2.3 From 0b8c5f074c725d739fffabb18076519d33a033a7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Tue, 8 Jan 2019 16:18:43 -0500 Subject: Remove list comprehension since any() works on generators Co-Authored-By: sco1 --- bot/cogs/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index 77a15733d..b44e5871e 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -124,7 +124,7 @@ class Events: return False muted_check = any( - [infraction["active"] for infraction in infraction_list if infraction["type"].lower() == "mute"] + infraction["active"] for infraction in infraction_list if infraction["type"].lower() == "mute" ) return muted_check -- cgit v1.2.3 From e8a5db64dc70270488a6b9a43e21470c11936878 Mon Sep 17 00:00:00 2001 From: sco1 Date: Tue, 8 Jan 2019 16:36:54 -0500 Subject: Switch short-circuit logic, add logging --- bot/cogs/events.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index b44e5871e..78878dcb9 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -119,7 +119,7 @@ class Events: infraction_list = await response.json() # Check for active mute infractions - if len(infraction_list) == 0: + if not infraction_list: # Short circuit return False @@ -263,6 +263,10 @@ class Events: if role in old_roles: # Check for mute roles that were not able to be removed and skip if present if role == str(Roles.muted) and not await self.has_active_mute(str(member.id)): + log.debug( + f"User {member.id} has no active mute infraction, " + "their leftover muted role will not be persisted" + ) continue new_roles.append(Object(int(role))) -- cgit v1.2.3 From 01a069c26fa2f45e2ad20ab9fc4c612a503b4d73 Mon Sep 17 00:00:00 2001 From: Derek Date: Tue, 8 Jan 2019 16:45:22 -0500 Subject: Add linter exception --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index cd04275e2..076415ded 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -46,7 +46,7 @@ class Free: messages = await channel.history(limit=seek).flatten() msg = messages[seek-1] else: - msg = await channel.history(limit=1).next() + msg = await channel.history(limit=1).next() # noqa (False positive) inactive = (datetime.utcnow() - msg.created_at).seconds if inactive > self.TIME_INACTIVE: -- cgit v1.2.3 From 25d9b1ea36b03ed431d7262d373124e3f788d408 Mon Sep 17 00:00:00 2001 From: sco1 Date: Tue, 8 Jan 2019 17:34:51 -0500 Subject: From review --- bot/cogs/events.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index 78878dcb9..f0baecd4b 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -123,10 +123,9 @@ class Events: # Short circuit return False - muted_check = any( + return any( infraction["active"] for infraction in infraction_list if infraction["type"].lower() == "mute" ) - return muted_check async def on_command_error(self, ctx: Context, e: CommandError): command = ctx.command -- cgit v1.2.3 From 3a209226e76b2ef1e542cd5a35e2571b3a1cc418 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 01:45:01 -0500 Subject: Add activity timeout constant --- bot/cogs/free.py | 13 ++++++------- bot/constants.py | 6 ++++++ config-default.yml | 6 ++++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 076415ded..7447fd941 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -4,18 +4,17 @@ from datetime import datetime from discord import Colour, Embed, Member, utils from discord.ext.commands import BucketType, Context, command, cooldown -from bot.constants import Categories +from bot.constants import Categories, Free log = logging.getLogger(__name__) +PYTHON_HELP_ID = Categories.python_help +TIMEOUT = Free.activity_timeout + class Free: """Tries to figure out which help channels are free.""" - - PYTHON_HELP_ID = Categories.python_help - TIME_INACTIVE = 300 - @command(name="free", aliases=('f',)) @cooldown(1, 60.0, BucketType.channel) async def free(self, ctx: Context, user: Member = None, seek: int = 2): @@ -34,7 +33,7 @@ class Free: in an active channel, and we want the message before that happened. """ free_channels = [] - python_help = utils.get(ctx.guild.categories, id=self.PYTHON_HELP_ID) + python_help = utils.get(ctx.guild.categories, id=PYTHON_HELP_ID) if user is not None and seek == 2: seek = 3 @@ -49,7 +48,7 @@ class Free: msg = await channel.history(limit=1).next() # noqa (False positive) inactive = (datetime.utcnow() - msg.created_at).seconds - if inactive > self.TIME_INACTIVE: + if inactive > TIMEOUT: free_channels.append((inactive, channel)) embed = Embed() diff --git a/bot/constants.py b/bot/constants.py index aae9b05e5..689590c0c 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -471,6 +471,12 @@ class BigBrother(metaclass=YAMLGetter): header_message_limit: int +class Free(metaclass=YAMLGetter): + section = 'free' + + activity_timeout: int + + # Debug mode DEBUG_MODE = True if 'local' in os.environ.get("SITE_URL", "local") else False diff --git a/config-default.yml b/config-default.yml index 1b4e6f412..504d40ed7 100644 --- a/config-default.yml +++ b/config-default.yml @@ -337,5 +337,11 @@ big_brother: header_message_limit: 15 +free: + # Seconds to elapse for a channel + # to be considered inactive. + activity_timeout: 300 + + config: required_keys: ['bot.token'] -- cgit v1.2.3 From 2340509a482d2ed3fce2e3ede456ac49b2a97c43 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 17:55:40 -0500 Subject: Add exemption from cooldown for helpers --- bot/cogs/free.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 7447fd941..6b06eaa4c 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -2,9 +2,10 @@ import logging from datetime import datetime from discord import Colour, Embed, Member, utils +from discord.ext import commands from discord.ext.commands import BucketType, Context, command, cooldown -from bot.constants import Categories, Free +from bot.constants import Categories, Free, Roles log = logging.getLogger(__name__) @@ -83,6 +84,26 @@ class Free: await ctx.send(embed=embed) + @free.error + async def free_error(self, ctx: Context, error): + """ + Runs if any error is raised during invocation + of !free command. Any error aside from + CommandOnCooldown is ignored. + + If error raised is CommandOnCooldown, and the + user who invoked has the helper role, reset + the cooldown and reinvoke the command. + """ + helpers = ctx.guild.get_role(Roles.helpers) + + if isinstance(error, commands.CommandOnCooldown): + if helpers in ctx.author.roles: + # reset cooldown so second invocation + # doesn't bring us back here. + ctx.command.reset_cooldown(ctx) + await ctx.invoke(ctx.command) + def setup(bot): bot.add_cog(Free()) -- cgit v1.2.3 From 0f85110040e70376838b380579cac2ccad007914 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:10:03 -0500 Subject: Put channel category id constant back in class --- bot/cogs/free.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 6b06eaa4c..a66a37f1b 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -10,12 +10,14 @@ from bot.constants import Categories, Free, Roles log = logging.getLogger(__name__) -PYTHON_HELP_ID = Categories.python_help TIMEOUT = Free.activity_timeout class Free: """Tries to figure out which help channels are free.""" + + PYTHON_HELP_ID = Categories.python_help + @command(name="free", aliases=('f',)) @cooldown(1, 60.0, BucketType.channel) async def free(self, ctx: Context, user: Member = None, seek: int = 2): @@ -34,7 +36,7 @@ class Free: in an active channel, and we want the message before that happened. """ free_channels = [] - python_help = utils.get(ctx.guild.categories, id=PYTHON_HELP_ID) + python_help = utils.get(ctx.guild.categories, id=self.PYTHON_HELP_ID) if user is not None and seek == 2: seek = 3 -- cgit v1.2.3 From ee0a38a5671597d200c4927d9e900481727cecf1 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:11:39 -0500 Subject: Change awkward wording --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index a66a37f1b..e9c0386fd 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -79,7 +79,7 @@ class Free: embed.description += ("**\nThese channels aren't guaranteed to be free, " "so use your best judgement and check for yourself.") else: - embed.description = ("**Doesn't look like any channels are available to me. " + embed.description = ("**Doesn't look like any channels are available right now. " "You're welcome to check for yourself to be sure. " "If all channels are truly busy, please be patient " "as one will likely be available soon.**") -- cgit v1.2.3 From 61c7b27b3453120771764fbdd8b43fbc504a8882 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:13:19 -0500 Subject: Change embed color to blurple --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index e9c0386fd..4b5f3b3bb 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -55,7 +55,7 @@ class Free: free_channels.append((inactive, channel)) embed = Embed() - embed.colour = Colour.gold() + embed.colour = Colour.blurple() embed.title = "**Looking for a free help channel?**" if user is not None: -- cgit v1.2.3 From 5868516da942e6df4f5caa1fa24b5af13e1f3555 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:19:14 -0500 Subject: Change line to fit prevailing style --- bot/cogs/free.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 4b5f3b3bb..6f0e86330 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -66,7 +66,8 @@ class Free: if free_channels: embed.description += "**The following channel{0} look{1} free:**\n\n**".format( 's' if len(free_channels) > 1 else '', - '' if len(free_channels) > 1 else 's') + '' if len(free_channels) > 1 else 's' + ) for i, (inactive, channel) in enumerate(sorted(free_channels, reverse=True), 1): minutes, seconds = divmod(inactive, 60) -- cgit v1.2.3 From 4313a9c5ed6b052d46f00350b10a8c3d62136c48 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:36:57 -0500 Subject: Add all the block comments! --- bot/cogs/free.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 6f0e86330..0880cdd25 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -43,10 +43,15 @@ class Free: elif not 0 < seek < 10: seek = 3 + # Iterate through all the help channels + # to check latest activity for channel in python_help.channels: + # Seek further back in the help channel + # the command was invoked in if channel.id == ctx.channel.id: messages = await channel.history(limit=seek).flatten() msg = messages[seek-1] + # Otherwise get last message else: msg = await channel.history(limit=1).next() # noqa (False positive) @@ -63,12 +68,17 @@ class Free: else: embed.description = "" + # Display all potentially inactive channels + # in descending order of inactivity if free_channels: embed.description += "**The following channel{0} look{1} free:**\n\n**".format( 's' if len(free_channels) > 1 else '', '' if len(free_channels) > 1 else 's' ) + # Sort channels in descending order by seconds + # Get position in list, inactivity, and channel object + # For each channel, add to embed.description for i, (inactive, channel) in enumerate(sorted(free_channels, reverse=True), 1): minutes, seconds = divmod(inactive, 60) if minutes > 59: -- cgit v1.2.3 From 40d9ba8a08f6ba6586aea331f8574e2aa3dd03b9 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:38:41 -0500 Subject: Change activity timeout to 10 minutes --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index 504d40ed7..7b0468643 100644 --- a/config-default.yml +++ b/config-default.yml @@ -340,7 +340,7 @@ big_brother: free: # Seconds to elapse for a channel # to be considered inactive. - activity_timeout: 300 + activity_timeout: 600 config: -- cgit v1.2.3 From b2929ef2e72d42950169db4a46c064eedc228c4e Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 10 Jan 2019 16:55:55 +0100 Subject: Adding watch reason to the big-brother header embeds --- bot/cogs/bigbrother.py | 65 +++++++++++++++++++++++++++++++++++++++++++------- config-default.yml | 1 + 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/bot/cogs/bigbrother.py b/bot/cogs/bigbrother.py index 29b13f038..26d35a97c 100644 --- a/bot/cogs/bigbrother.py +++ b/bot/cogs/bigbrother.py @@ -2,8 +2,10 @@ import asyncio import logging import re from collections import defaultdict, deque +from time import strptime from typing import List, Union +from aiohttp import ClientError from discord import Color, Embed, Guild, Member, Message, TextChannel, User from discord.ext.commands import Bot, Context, group @@ -26,13 +28,15 @@ class BigBrother: def __init__(self, bot: Bot): self.bot = bot self.watched_users = {} # { user_id: log_channel_id } + self.watch_reasons = {} # { user_id: watch_reason } self.channel_queues = defaultdict(lambda: defaultdict(deque)) # { user_id: { channel_id: queue(messages) } self.last_log = [None, None, 0] # [user_id, channel_id, message_count] self.consuming = False + self.infraction_watch_prefix = "bb watch: " # Please do not change or we won't be able to find old reasons self.bot.loop.create_task(self.get_watched_users()) - def update_cache(self, api_response: List[dict]): + async def update_cache(self, api_response: List[dict]): """ Updates the internal cache of watched users from the given `api_response`. This function will only add (or update) existing keys, it will not delete @@ -54,13 +58,52 @@ class BigBrother: "but the given channel could not be found. Ignoring." ) + watch_reason = await self.get_watch_reason(user_id) + self.watch_reasons[user_id] = watch_reason + async def get_watched_users(self): """Retrieves watched users from the API.""" await self.bot.wait_until_ready() async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: data = await response.json() - self.update_cache(data) + await self.update_cache(data) + + async def get_watch_reason(self, user_id: int) -> str: + """ Fetches and returns the latest watch reason for a user using the infraction API """ + + re_bb_watch = rf"^{self.infraction_watch_prefix}" + user_id = str(user_id) + + try: + response = await self.bot.http_session.get( + URLs.site_infractions_user_type.format( + user_id=user_id, + infraction_type="note", + ), + params={"search": re_bb_watch, "hidden": "True", "active": "False"}, + headers=self.HEADERS + ) + infraction_list = await response.json() + except ClientError: + log.exception(f"Failed to retrieve bb watch reason for {user_id}.") + return "(error retrieving bb reason)" + + if infraction_list: + latest_reason_infraction = max(infraction_list, key=self._parse_time) + latest_reason = latest_reason_infraction['reason'][10:] + log.trace(f"The latest bb watch reason for {user_id}: {latest_reason}") + return latest_reason + + log.trace(f"No bb watch reason found for {user_id}; returning default string") + return "(no reason specified)" + + @staticmethod + def _parse_time(infraction): + """Takes RFC1123 date_time string and returns time object for sorting purposes""" + + date_string = infraction["inserted_at"] + return strptime(date_string, "%a, %d %b %Y %H:%M:%S %Z") async def on_member_ban(self, guild: Guild, user: Union[User, Member]): if guild.id == GuildConfig.id and user.id in self.watched_users: @@ -70,6 +113,7 @@ class BigBrother: async with self.bot.http_session.delete(url, headers=self.HEADERS) as response: del self.watched_users[user.id] del self.channel_queues[user.id] + del self.watch_reasons[user.id] if response.status == 204: await channel.send( f"{Emojis.bb_message}:hammer: {user} got banned, so " @@ -143,6 +187,7 @@ class BigBrother: embed = Embed(description=f"{message.author.mention} in [#{message.channel.name}]({message.jump_url})") embed.set_author(name=message.author.nick or message.author.name, icon_url=message.author.avatar_url) + embed.set_footer(text=f"Watch reason: {self.watch_reasons[message.author.id]}") await destination.send(embed=embed) @staticmethod @@ -201,7 +246,7 @@ class BigBrother: async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: if response.status == 200: data = await response.json() - self.update_cache(data) + await self.update_cache(data) lines = tuple(f"• <@{entry['user_id']}> in <#{entry['channel_id']}>" for entry in data) await LinePaginator.paginate( @@ -246,15 +291,15 @@ class BigBrother: ) else: self.watched_users[user.id] = channel + self.watch_reasons[user.id] = reason + # Add a note (shadow warning) with the reason for watching + reason = f"{self.infraction_watch_prefix}{reason}" + await post_infraction(ctx, user, type="warning", reason=reason, hidden=True) else: data = await response.json() - reason = data.get('error_message', "no message provided") - await ctx.send(f":x: the API returned an error: {reason}") - - # Add a note (shadow warning) with the reason for watching - reason = "bb watch: " + reason # Prepend for situational awareness - await post_infraction(ctx, user, type="warning", reason=reason, hidden=True) + error_reason = data.get('error_message', "no message provided") + await ctx.send(f":x: the API returned an error: {error_reason}") @bigbrother_group.command(name='unwatch', aliases=('uw',)) @with_role(Roles.owner, Roles.admin, Roles.moderator) @@ -270,6 +315,8 @@ class BigBrother: del self.watched_users[user.id] if user.id in self.channel_queues: del self.channel_queues[user.id] + if user.id in self.watch_reasons: + del self.watch_reasons[user.id] else: log.warning(f"user {user.id} was unwatched but was not found in the cache") diff --git a/config-default.yml b/config-default.yml index 21d7f20b9..1a5a63c6a 100644 --- a/config-default.yml +++ b/config-default.yml @@ -240,6 +240,7 @@ urls: site_infractions_type: !JOIN [*SCHEMA, *API, "/bot/infractions/type/{infraction_type}"] site_infractions_by_id: !JOIN [*SCHEMA, *API, "/bot/infractions/id/{infraction_id}"] site_infractions_user_type_current: !JOIN [*SCHEMA, *API, "/bot/infractions/user/{user_id}/{infraction_type}/current"] + site_infractions_user_type: !JOIN [*SCHEMA, *API, "/bot/infractions/user/{user_id}/{infraction_type}"] site_logs_api: !JOIN [*SCHEMA, *API, "/bot/logs"] site_logs_view: !JOIN [*SCHEMA, *DOMAIN, "/bot/logs"] site_names_api: !JOIN [*SCHEMA, *API, "/bot/snake_names"] -- cgit v1.2.3 From f113c2bed932f52439b1185b96a03c6e3ae03c8e Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 10 Jan 2019 17:03:45 +0100 Subject: Adding type annotations --- bot/cogs/bigbrother.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/bigbrother.py b/bot/cogs/bigbrother.py index 26d35a97c..b325fa5fe 100644 --- a/bot/cogs/bigbrother.py +++ b/bot/cogs/bigbrother.py @@ -2,7 +2,7 @@ import asyncio import logging import re from collections import defaultdict, deque -from time import strptime +from time import strptime, struct_time from typing import List, Union from aiohttp import ClientError @@ -99,7 +99,7 @@ class BigBrother: return "(no reason specified)" @staticmethod - def _parse_time(infraction): + def _parse_time(infraction: str) -> struct_time: """Takes RFC1123 date_time string and returns time object for sorting purposes""" date_string = infraction["inserted_at"] -- cgit v1.2.3 From 66e476f9ca024c5e6e2917679aaa4da03ef69acd Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 10 Jan 2019 17:37:20 +0100 Subject: Removing default argument for the alias watch to require a reason via the alias as well --- bot/cogs/alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index 2ce4a51e3..0b848c773 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -71,7 +71,7 @@ class Alias: @command(name="watch", hidden=True) async def bigbrother_watch_alias( - self, ctx, user: User, *, reason: str = None + self, ctx, user: User, *, reason: str ): """ Alias for invoking bigbrother watch user [text_channel]. -- cgit v1.2.3 From 86440dd8ad355d157a0ebf8250d7abe7d56c5982 Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 17:21:43 -0500 Subject: Update error handler to log errors instead of ignore them --- bot/cogs/free.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 0880cdd25..b8e9f65d0 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -115,7 +115,10 @@ class Free: # reset cooldown so second invocation # doesn't bring us back here. ctx.command.reset_cooldown(ctx) - await ctx.invoke(ctx.command) + # return to avoid needlessly logging the error + return await ctx.invoke(ctx.command) + + log.error(error) # Don't ignore other errors def setup(bot): -- cgit v1.2.3 From d46fed40b335060f7d1652cff6eb58912b824e0b Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 17:27:57 -0500 Subject: Add 2nd space to inline comment to obey allmighty linter --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index b8e9f65d0..acd1dc108 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -118,7 +118,7 @@ class Free: # return to avoid needlessly logging the error return await ctx.invoke(ctx.command) - log.error(error) # Don't ignore other errors + log.error(error) # Don't ignore other errors def setup(bot): -- cgit v1.2.3 From c55fa67e21a6a10feb0738b3bacd6b0dba1d4de9 Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 17:58:19 -0500 Subject: Add cooldown constants to config --- bot/cogs/free.py | 4 +++- bot/constants.py | 2 ++ config-default.yml | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index acd1dc108..8d413a69b 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -11,6 +11,8 @@ from bot.constants import Categories, Free, Roles log = logging.getLogger(__name__) TIMEOUT = Free.activity_timeout +RATE = Free.cooldown_rate +PER = Free.cooldown_per class Free: @@ -19,7 +21,7 @@ class Free: PYTHON_HELP_ID = Categories.python_help @command(name="free", aliases=('f',)) - @cooldown(1, 60.0, BucketType.channel) + @cooldown(RATE, PER, BucketType.channel) async def free(self, ctx: Context, user: Member = None, seek: int = 2): """ Lists free help channels by likeliness of availability. diff --git a/bot/constants.py b/bot/constants.py index 1bb602eb2..be713cef2 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -477,6 +477,8 @@ class Free(metaclass=YAMLGetter): section = 'free' activity_timeout: int + cooldown_rate: int + cooldown_per: float # Debug mode diff --git a/config-default.yml b/config-default.yml index 3db7b2025..f462b8199 100644 --- a/config-default.yml +++ b/config-default.yml @@ -343,6 +343,8 @@ free: # Seconds to elapse for a channel # to be considered inactive. activity_timeout: 600 + cooldown_rate: 1 + cooldown_per: 60.0 config: -- cgit v1.2.3 From e1bd2c37693e863738fb5eddcd0f46ae9e484f1f Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 18:04:06 -0500 Subject: Put extra sentence in docstring clarifying arg usage --- bot/cogs/free.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 8d413a69b..0010b4a90 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -29,6 +29,7 @@ class Free: :param seek: How far back to check the last active message. seek is used only when this command is invoked in a help channel. + You cannot override seek without mentioning a user first. When seek is 2, we are avoiding considering the last active message in a channel to be the one that invoked this command. -- cgit v1.2.3 From d8db79581a84c1d8adf0a297d5390c4c3f519aba Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 18:09:14 -0500 Subject: Update error handler docstring to be more accurate --- bot/cogs/free.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 0010b4a90..4be804881 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -103,13 +103,11 @@ class Free: @free.error async def free_error(self, ctx: Context, error): """ - Runs if any error is raised during invocation - of !free command. Any error aside from - CommandOnCooldown is ignored. - If error raised is CommandOnCooldown, and the user who invoked has the helper role, reset the cooldown and reinvoke the command. + + Otherwise log the error. """ helpers = ctx.guild.get_role(Roles.helpers) -- cgit v1.2.3 From a2f8b21adf7880a116ae9f80e95cd7e696e7e135 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 11 Jan 2019 09:24:57 +0100 Subject: Only retrieve/cache watch reason when user becomes active; restore update_cache to synchronous as it was before --- bot/cogs/bigbrother.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/bot/cogs/bigbrother.py b/bot/cogs/bigbrother.py index b325fa5fe..70916cd7b 100644 --- a/bot/cogs/bigbrother.py +++ b/bot/cogs/bigbrother.py @@ -36,7 +36,7 @@ class BigBrother: self.bot.loop.create_task(self.get_watched_users()) - async def update_cache(self, api_response: List[dict]): + def update_cache(self, api_response: List[dict]): """ Updates the internal cache of watched users from the given `api_response`. This function will only add (or update) existing keys, it will not delete @@ -58,16 +58,13 @@ class BigBrother: "but the given channel could not be found. Ignoring." ) - watch_reason = await self.get_watch_reason(user_id) - self.watch_reasons[user_id] = watch_reason - async def get_watched_users(self): """Retrieves watched users from the API.""" await self.bot.wait_until_ready() async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: data = await response.json() - await self.update_cache(data) + self.update_cache(data) async def get_watch_reason(self, user_id: int) -> str: """ Fetches and returns the latest watch reason for a user using the infraction API """ @@ -90,8 +87,8 @@ class BigBrother: return "(error retrieving bb reason)" if infraction_list: - latest_reason_infraction = max(infraction_list, key=self._parse_time) - latest_reason = latest_reason_infraction['reason'][10:] + latest_reason_infraction = max(infraction_list, key=self._parse_infraction_time) + latest_reason = latest_reason_infraction['reason'][len(self.infraction_watch_prefix):] log.trace(f"The latest bb watch reason for {user_id}: {latest_reason}") return latest_reason @@ -99,7 +96,7 @@ class BigBrother: return "(no reason specified)" @staticmethod - def _parse_time(infraction: str) -> struct_time: + def _parse_infraction_time(infraction: str) -> struct_time: """Takes RFC1123 date_time string and returns time object for sorting purposes""" date_string = infraction["inserted_at"] @@ -183,6 +180,12 @@ class BigBrother: # Send header if user/channel are different or if message limit exceeded. if message.author.id != last_user or message.channel.id != last_channel or msg_count > limit: + # Retrieve watch reason from API if it's not already in the cache + if message.author.id not in self.watch_reasons: + log.trace(f"No watch reason for {message.author.id} found in cache; retrieving from API") + user_watch_reason = await self.get_watch_reason(message.author.id) + self.watch_reasons[message.author.id] = user_watch_reason + self.last_log = [message.author.id, message.channel.id, 0] embed = Embed(description=f"{message.author.mention} in [#{message.channel.name}]({message.jump_url})") @@ -246,7 +249,7 @@ class BigBrother: async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: if response.status == 200: data = await response.json() - await self.update_cache(data) + self.update_cache(data) lines = tuple(f"• <@{entry['user_id']}> in <#{entry['channel_id']}>" for entry in data) await LinePaginator.paginate( -- cgit v1.2.3 From 5dd611793c598508c5be8868725ca5400ad0c304 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 11 Jan 2019 11:01:53 +0100 Subject: Using stronger language in the message and emphazising the 'strongly recommend' with bold --- bot/cogs/token_remover.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bot/cogs/token_remover.py b/bot/cogs/token_remover.py index 8277513a7..c1a0e18ba 100644 --- a/bot/cogs/token_remover.py +++ b/bot/cogs/token_remover.py @@ -16,8 +16,9 @@ log = logging.getLogger(__name__) DELETION_MESSAGE_TEMPLATE = ( "Hey {mention}! I noticed you posted a seemingly valid Discord API " - "token in your message and have removed your message to prevent abuse. " - "We recommend regenerating your token regardless, which you can do here: " + "token in your message and have removed your message. " + "We **strongly recommend** regenerating your token as it's probably " + "been compromised. You can do that here: " "\n" "Feel free to re-post it with the token removed. " "If you believe this was a mistake, please let us know!" -- cgit v1.2.3 From 8f30b52fd378bb3546c84d0fdb945a4b492236b8 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 11 Jan 2019 15:06:12 +0100 Subject: Catching the superclass CheckFailure instead of the subclass --- bot/cogs/help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index c82a25417..ded068123 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -6,10 +6,10 @@ from contextlib import suppress from discord import Colour, Embed, HTTPException from discord.ext import commands +from discord.ext.commands import CheckFailure from fuzzywuzzy import fuzz, process from bot import constants -from bot.decorators import InChannelCheckFailure from bot.pagination import ( DELETE_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, @@ -435,7 +435,7 @@ class HelpSession: # the mean time. try: can_run = await command.can_run(self._ctx) - except InChannelCheckFailure: + except CheckFailure: can_run = False if not can_run: -- cgit v1.2.3 From 0651779383423ad08b860cde79d29c3e00dca677 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 11 Jan 2019 15:14:43 +0100 Subject: Revert "Catching the superclass CheckFailure instead of the subclass" This reverts commit 8f30b52fd378bb3546c84d0fdb945a4b492236b8. Accidentally pushed to master when I thought I was at a branch --- bot/cogs/help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index ded068123..c82a25417 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -6,10 +6,10 @@ from contextlib import suppress from discord import Colour, Embed, HTTPException from discord.ext import commands -from discord.ext.commands import CheckFailure from fuzzywuzzy import fuzz, process from bot import constants +from bot.decorators import InChannelCheckFailure from bot.pagination import ( DELETE_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, @@ -435,7 +435,7 @@ class HelpSession: # the mean time. try: can_run = await command.can_run(self._ctx) - except CheckFailure: + except InChannelCheckFailure: can_run = False if not can_run: -- cgit v1.2.3 From 3905e8ff52ae652c94a1ff0372aa044709060774 Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 11 Jan 2019 09:48:50 -0500 Subject: Disable rich embed filter Discord adding the embeds is causing it to trigger. --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index 21d7f20b9..866a5b5ab 100644 --- a/config-default.yml +++ b/config-default.yml @@ -137,7 +137,7 @@ filter: filter_zalgo: false filter_invites: true filter_domains: true - filter_rich_embeds: true + filter_rich_embeds: false watch_words: true watch_tokens: true -- cgit v1.2.3 From 4bc92be7c4831fa3b714d7091700d587f8f62373 Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 11 Jan 2019 22:39:15 -0500 Subject: Add edit delta to modlog for multi-edit messages To help with self-bot detection, if a message has been previously edited, generate a human-readable delta between the last edit and the new one Use message timestamp for modlog embeds generated during on_message event Visually separate send_log_message kwargs to make them easier to read --- bot/cogs/modlog.py | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 06f81cb36..bded3baa0 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -104,9 +104,19 @@ class ModLog: self._ignored[event].append(item) async def send_log_message( - self, icon_url: Optional[str], colour: Colour, title: Optional[str], text: str, - thumbnail: str = None, channel_id: int = Channels.modlog, ping_everyone: bool = False, - files: List[File] = None, content: str = None, additional_embeds: List[Embed] = None, + self, + icon_url: Optional[str], + colour: Colour, + title: Optional[str], + text: str, + thumbnail: str = None, + channel_id: int = Channels.modlog, + ping_everyone: bool = False, + files: List[File] = None, + content: str = None, + additional_embeds: List[Embed] = None, + timestamp_override: datetime.datetime = None, + footer_override: str = None, ): embed = Embed(description=text) @@ -114,7 +124,14 @@ class ModLog: embed.set_author(name=title, icon_url=icon_url) embed.colour = colour - embed.timestamp = datetime.datetime.utcnow() + + if timestamp_override: + embed.timestamp = timestamp_override + else: + embed.timestamp = datetime.datetime.utcnow() + + if footer_override: + embed.set_footer(text=footer_override) if thumbnail is not None: embed.set_thumbnail(url=thumbnail) @@ -676,14 +693,28 @@ class ModLog: f"{after.clean_content}" ) + if before.edited_at: + # Message was previously edited, to assist with self-bot detection, use the edited_at + # datetime as the baseline and create a human-readable delta between this edit event + # and the last time the message was edited + timestamp = before.edited_at + delta = humanize_delta(relativedelta(after.edited_at, before.edited_at)) + footer = f"Last edited {delta} ago" + else: + # Message was not previously edited, use the created_at datetime as the baseline, no + # delta calculation needed + timestamp = before.created_at + footer = None + + print(timestamp, footer) await self.send_log_message( - Icons.message_edit, Colour.blurple(), "Message edited (Before)", - before_response, channel_id=Channels.message_log + Icons.message_edit, Colour.blurple(), "Message edited (Before)", before_response, + channel_id=Channels.message_log, timestamp_override=timestamp, footer_override=footer ) await self.send_log_message( - Icons.message_edit, Colour.blurple(), "Message edited (After)", - after_response, channel_id=Channels.message_log + Icons.message_edit, Colour.blurple(), "Message edited (After)", after_response, + channel_id=Channels.message_log, timestamp_override=after.edited_at ) async def on_raw_message_edit(self, event: RawMessageUpdateEvent): -- cgit v1.2.3 From ddfb1f4689c54a8f7adccd90bd9038e69ff67168 Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 11 Jan 2019 23:12:07 -0500 Subject: Remove debug print statement --- bot/cogs/modlog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index bded3baa0..76a5eff58 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -706,7 +706,6 @@ class ModLog: timestamp = before.created_at footer = None - print(timestamp, footer) await self.send_log_message( Icons.message_edit, Colour.blurple(), "Message edited (Before)", before_response, channel_id=Channels.message_log, timestamp_override=timestamp, footer_override=footer -- cgit v1.2.3 From 4dc7ba5b54ab8de7f86f5239b956d94d80146c85 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Sat, 12 Jan 2019 15:04:17 +0100 Subject: Changing check-specific exception to superclass exception so help doesn't break when new check exceptions are added --- bot/cogs/help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index c82a25417..ded068123 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -6,10 +6,10 @@ from contextlib import suppress from discord import Colour, Embed, HTTPException from discord.ext import commands +from discord.ext.commands import CheckFailure from fuzzywuzzy import fuzz, process from bot import constants -from bot.decorators import InChannelCheckFailure from bot.pagination import ( DELETE_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, @@ -435,7 +435,7 @@ class HelpSession: # the mean time. try: can_run = await command.can_run(self._ctx) - except InChannelCheckFailure: + except CheckFailure: can_run = False if not can_run: -- cgit v1.2.3 From e4a707157fe9cadef5debef67779a5443b48d11e Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 12 Jan 2019 13:16:58 -0500 Subject: Add Optional type hints where appropriate --- bot/cogs/modlog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 76a5eff58..589052b1e 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -109,14 +109,14 @@ class ModLog: colour: Colour, title: Optional[str], text: str, - thumbnail: str = None, + thumbnail: Optional[str] = None, channel_id: int = Channels.modlog, ping_everyone: bool = False, - files: List[File] = None, - content: str = None, - additional_embeds: List[Embed] = None, - timestamp_override: datetime.datetime = None, - footer_override: str = None, + files: Optional[List[File]] = None, + content: Optional[str] = None, + additional_embeds: Optional[List[Embed]] = None, + timestamp_override: Optional[datetime.datetime] = None, + footer_override: Optional[str] = None, ): embed = Embed(description=text) -- cgit v1.2.3 From 3fc94a97dc88504c29985fff735b346e2122c4a2 Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 12 Jan 2019 13:22:11 -0500 Subject: Condense logic --- bot/cogs/modlog.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 589052b1e..55611c5e4 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -125,15 +125,12 @@ class ModLog: embed.colour = colour - if timestamp_override: - embed.timestamp = timestamp_override - else: - embed.timestamp = datetime.datetime.utcnow() + embed.timestamp = timestamp_override or datetime.datetime.utcnow() if footer_override: embed.set_footer(text=footer_override) - if thumbnail is not None: + if thumbnail: embed.set_thumbnail(url=thumbnail) if ping_everyone: -- cgit v1.2.3 From c94189cbfae4b1788773af3bb7ec32ba32caeb12 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Sat, 12 Jan 2019 16:36:04 -0500 Subject: Change ctx.invoke to ctx.reinvoke to conserve passed arguments Co-Authored-By: fiskenslakt --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 4be804881..e310d9cc7 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -117,7 +117,7 @@ class Free: # doesn't bring us back here. ctx.command.reset_cooldown(ctx) # return to avoid needlessly logging the error - return await ctx.invoke(ctx.command) + return await ctx.reinvoke() log.error(error) # Don't ignore other errors -- cgit v1.2.3 From 240b33719d48716762461ee8eddbeb28ff460a03 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Sat, 12 Jan 2019 16:46:14 -0500 Subject: Change log.error to log.exception to avoid hiding traceback Co-Authored-By: fiskenslakt --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index e310d9cc7..620449f7e 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -119,7 +119,7 @@ class Free: # return to avoid needlessly logging the error return await ctx.reinvoke() - log.error(error) # Don't ignore other errors + log.exception(error) # Don't ignore other errors def setup(bot): -- cgit v1.2.3 From 3d1d5e692655a50d0f5e88f440ddb056d4426b3b Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 13 Jan 2019 00:03:31 +0100 Subject: Adressing jchrists review comments --- Pipfile.lock | 228 +++++++++++++++++++++++++----------------------- bot/cogs/information.py | 7 +- 2 files changed, 122 insertions(+), 113 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 02b9d0359..8dea91451 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -90,11 +90,11 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:194ec62a25438adcb3fdb06378b26559eda1ea8a747367d34c33cef9c7f48d57", - "sha256:90f8e61121d6ae58362ce3bed8cd997efb00c914eae0ff3d363c32f9a9822d10", - "sha256:f0abd31228055d698bb392a826528ea08ebb9959e6bea17c606fd9c9009db938" + "sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858", + "sha256:945065979fb8529dd2f37dbb58f00b661bdbcbebf954f93b32fdf5263ef35348", + "sha256:ba6d5c59906a85ac23dadfe5c88deaf3e179ef565f4898671253e50a78680718" ], - "version": "==4.6.3" + "version": "==4.7.1" }, "certifi": { "hashes": [ @@ -232,39 +232,35 @@ }, "lxml": { "hashes": [ - "sha256:02bc220d61f46e9b9d5a53c361ef95e9f5e1d27171cd461dddb17677ae2289a5", - "sha256:22f253b542a342755f6cfc047fe4d3a296515cf9b542bc6e261af45a80b8caf6", - "sha256:2f31145c7ff665b330919bfa44aacd3a0211a76ca7e7b441039d2a0b0451e415", - "sha256:36720698c29e7a9626a0dc802ef8885f8f0239bfd1689628ecd459a061f2807f", - "sha256:438a1b0203545521f6616132bfe0f4bca86f8a401364008b30e2b26ec408ce85", - "sha256:4815892904c336bbaf73dafd54f45f69f4021c22b5bad7332176bbf4fb830568", - "sha256:5be031b0f15ad63910d8e5038b489d95a79929513b3634ad4babf77100602588", - "sha256:5c93ae37c3c588e829b037fdfbd64a6e40c901d3f93f7beed6d724c44829a3ad", - "sha256:60842230678674cdac4a1cf0f707ef12d75b9a4fc4a565add4f710b5fcf185d5", - "sha256:62939a8bb6758d1bf923aa1c13f0bcfa9bf5b2fc0f5fa917a6e25db5fe0cfa4e", - "sha256:75830c06a62fe7b8fe3bbb5f269f0b308f19f3949ac81cfd40062f47c1455faf", - "sha256:81992565b74332c7c1aff6a913a3e906771aa81c9d0c68c68113cffcae45bc53", - "sha256:8c892fb0ee52c594d9a7751c7d7356056a9682674b92cc1c4dc968ff0f30c52f", - "sha256:9d862e3cf4fc1f2837dedce9c42269c8c76d027e49820a548ac89fdcee1e361f", - "sha256:a623965c086a6e91bb703d4da62dabe59fe88888e82c4117d544e11fd74835d6", - "sha256:a7783ab7f6a508b0510490cef9f857b763d796ba7476d9703f89722928d1e113", - "sha256:aab09fbe8abfa3b9ce62aaf45aca2d28726b1b9ee44871dbe644050a2fff4940", - "sha256:abf181934ac3ef193832fb973fd7f6149b5c531903c2ec0f1220941d73eee601", - "sha256:ae07fa0c115733fce1e9da96a3ac3fa24801742ca17e917e0c79d63a01eeb843", - "sha256:b9c78242219f674ab645ec571c9a95d70f381319a23911941cd2358a8e0521cf", - "sha256:bccb267678b870d9782c3b44d0cefe3ba0e329f9af8c946d32bf3778e7a4f271", - "sha256:c4df4d27f4c93b2cef74579f00b1d3a31a929c7d8023f870c4b476f03a274db4", - "sha256:caf0e50b546bb60dfa99bb18dfa6748458a83131ecdceaf5c071d74907e7e78a", - "sha256:d3266bd3ac59ac4edcd5fa75165dee80b94a3e5c91049df5f7c057ccf097551c", - "sha256:db0d213987bcd4e6d41710fb4532b22315b0d8fb439ff901782234456556aed1", - "sha256:dbbd5cf7690a40a9f0a9325ab480d0fccf46d16b378eefc08e195d84299bfae1", - "sha256:e16e07a0ec3a75b5ee61f2b1003c35696738f937dc8148fbda9fe2147ccb6e61", - "sha256:e175a006725c7faadbe69e791877d09936c0ef2cf49d01b60a6c1efcb0e8be6f", - "sha256:edd9c13a97f6550f9da2236126bb51c092b3b1ce6187f2bd966533ad794bbb5e", - "sha256:fa39ea60d527fbdd94215b5e5552f1c6a912624521093f1384a491a8ad89ad8b" + "sha256:0dd6589fa75d369ba06d2b5f38dae107f76ea127f212f6a7bee134f6df2d1d21", + "sha256:1afbac344aa68c29e81ab56c1a9411c3663157b5aee5065b7fa030b398d4f7e0", + "sha256:1baad9d073692421ad5dbbd81430aba6c7f5fdc347f03537ae046ddf2c9b2297", + "sha256:1d8736421a2358becd3edf20260e41a06a0bf08a560480d3a5734a6bcbacf591", + "sha256:1e1d9bddc5afaddf0de76246d3f2152f961697ad7439c559f179002682c45801", + "sha256:1f179dc8b2643715f020f4d119d5529b02cd794c1c8f305868b73b8674d2a03f", + "sha256:241fb7bdf97cb1df1edfa8f0bcdfd80525d4023dac4523a241907c8b2f44e541", + "sha256:2f9765ee5acd3dbdcdc0d0c79309e01f7c16bc8d39b49250bf88de7b46daaf58", + "sha256:312e1e1b1c3ce0c67e0b8105317323e12807955e8186872affb667dbd67971f6", + "sha256:3273db1a8055ca70257fd3691c6d2c216544e1a70b673543e15cc077d8e9c730", + "sha256:34dfaa8c02891f9a246b17a732ca3e99c5e42802416628e740a5d1cb2f50ff49", + "sha256:3aa3f5288af349a0f3a96448ebf2e57e17332d99f4f30b02093b7948bd9f94cc", + "sha256:51102e160b9d83c1cc435162d90b8e3c8c93b28d18d87b60c56522d332d26879", + "sha256:56115fc2e2a4140e8994eb9585119a1ae9223b506826089a3ba753a62bd194a6", + "sha256:69d83de14dbe8fe51dccfd36f88bf0b40f5debeac763edf9f8325180190eba6e", + "sha256:99fdce94aeaa3ccbdfcb1e23b34273605c5853aa92ec23d84c84765178662c6c", + "sha256:a7c0cd5b8a20f3093ee4a67374ccb3b8a126743b15a4d759e2a1bf098faac2b2", + "sha256:abe12886554634ed95416a46701a917784cb2b4c77bfacac6916681d49bbf83d", + "sha256:b4f67b5183bd5f9bafaeb76ad119e977ba570d2b0e61202f534ac9b5c33b4485", + "sha256:bdd7c1658475cc1b867b36d5c4ed4bc316be8d3368abe03d348ba906a1f83b0e", + "sha256:c6f24149a19f611a415a51b9bc5f17b6c2f698e0d6b41ffb3fa9f24d35d05d73", + "sha256:d1e111b3ab98613115a208c1017f266478b0ab224a67bc8eac670fa0bad7d488", + "sha256:d6520aa965773bbab6cb7a791d5895b00d02cf9adc93ac2bf4edb9ac1a6addc5", + "sha256:dd185cde2ccad7b649593b0cda72021bc8a91667417001dbaf24cd746ecb7c11", + "sha256:de2e5b0828a9d285f909b5d2e9d43f1cf6cf21fe65bc7660bdaa1780c7b58298", + "sha256:f726444b8e909c4f41b4fde416e1071cf28fa84634bfb4befdf400933b6463af" ], "index": "pypi", - "version": "==4.2.5" + "version": "==4.3.0" }, "markdownify": { "hashes": [ @@ -349,85 +345,88 @@ }, "pillow": { "hashes": [ - "sha256:00203f406818c3f45d47bb8fe7e67d3feddb8dcbbd45a289a1de7dd789226360", - "sha256:0616f800f348664e694dddb0b0c88d26761dd5e9f34e1ed7b7a7d2da14b40cb7", - "sha256:091136f2a37e9ed6bd8ce96fbf5269199ba6edee490d64de7ac934316f31ecca", - "sha256:0d67ae9a5937b1348fa1d97c7dcb6b56aaef828ca6655298e96f2f3114ad829d", - "sha256:0e1aaddd00ee9014fe7a61b9da61427233fcd7c7f193b5efd6689e0ec36bc42f", - "sha256:1f7908aab90c92ad85af9d2fec5fc79456a89b3adcc26314d2cde0e238bd789e", - "sha256:2ea3517cd5779843de8a759c2349a3cd8d3893e03ab47053b66d5ec6f8bc4f93", - "sha256:39b662f65a067709a62943003c1e807d140e7fcf631fcfc66ebe905f8149b9f4", - "sha256:3ddc19447cf42ef3ec564ab7ebbd4f67838ba9816d739befe29dd70149c775bd", - "sha256:48a9f0538c91fc136b3a576bee0e7cd174773dc9920b310c21dcb5519722e82c", - "sha256:5280ebc42641a1283b7b1f2c20e5b936692198b9dd9995527c18b794850be1a8", - "sha256:576a8a7a57065dab968d9d18befa2594a7673dcdab78c9b1f34248410cc6118f", - "sha256:5e334a23c8f7cb6079987a2ed9978821a42b4323a3a3bdbc132945348737f9a9", - "sha256:5e34e4b5764af65551647f5cc67cf5198c1d05621781d5173b342e5e55bf023b", - "sha256:63b120421ab85cad909792583f83b6ca3584610c2fe70751e23f606a3c2e87f0", - "sha256:696b5e0109fe368d0057f484e2e91717b49a03f1e310f857f133a4acec9f91dd", - "sha256:6cb528de694f503ea164541c151da6c18267727a7558e0c9716cc0383d89658a", - "sha256:7306d851d5a0cfac9ea07f1177783836f4b37292e5f224a534a52111cb6a6451", - "sha256:7e3e32346d991f1788026917d0a9c182d6d32dc757163eee7ca990f1f831499e", - "sha256:870ed021a42b1b02b5fe4a739ea735f671a84128c0a666c705db2cb9abd528eb", - "sha256:916da1c19e4012d06a372127d7140dae894806fad67ef44330e5600d77833581", - "sha256:9303a289fa0811e1c6abd9ddebfc770556d7c3311cb2b32eff72164ddc49bc64", - "sha256:9577888ecc0ad7d06c3746afaba339c94d62b59da16f7a5d1cff9e491f23dace", - "sha256:987e1c94a33c93d9b209315bfda9faa54b8edfce6438a1e93ae866ba20de5956", - "sha256:99a3bbdbb844f4fb5d6dd59fac836a40749781c1fa63c563bc216c27aef63f60", - "sha256:99db8dc3097ceafbcff9cb2bff384b974795edeb11d167d391a02c7bfeeb6e16", - "sha256:a379526415f54f9462bc65a4da76fb0acc05e3b2a21717dde79621cf4377e0e6", - "sha256:a5a96cf49eb580756a44ecf12949e52f211e20bffbf5a95760ac14b1e499cd37", - "sha256:a844b5d8120f99fb7cd276ff544ac5bd562b0c053760d59694e6bf747c6ca7f5", - "sha256:a9284368e81a67a7f47d5ef1ef7e4f11a4f688485879f44cf5f9090bba1f9d94", - "sha256:aa6ca3eb56704cdc0d876fc6047ffd5ee960caad52452fbee0f99908a141a0ae", - "sha256:aade5e66795c94e4a2b2624affeea8979648d1b0ae3fcee17e74e2c647fc4a8a", - "sha256:b78905860336c1d292409e3df6ad39cc1f1c7f0964e66844bbc2ebfca434d073", - "sha256:b92f521cdc4e4a3041cc343625b699f20b0b5f976793fb45681aac1efda565f8", - "sha256:bb2baf44e97811687893873eab8cf9f18b40321cc15d15ff9f91dc031e30631f", - "sha256:bfde84bbd6ae5f782206d454b67b7ee8f7f818c29b99fd02bf022fd33bab14cb", - "sha256:c2b62d3df80e694c0e4a0ed47754c9480521e25642251b3ab1dff050a4e60409", - "sha256:c55d348c1c65896c1bd804527de4880d251ae832acf90d74ad525bb79e77d55c", - "sha256:c5e2be6c263b64f6f7656e23e18a4a9980cffc671442795682e8c4e4f815dd9f", - "sha256:c99aa3c63104e0818ec566f8ff3942fb7c7a8f35f9912cb63fd8e12318b214b2", - "sha256:dae06620d3978da346375ebf88b9e2dd7d151335ba668c995aea9ed07af7add4", - "sha256:db5499d0710823fa4fb88206050d46544e8f0e0136a9a5f5570b026584c8fd74", - "sha256:dcd3cd17d291e01e47636101c4a6638ffb44c842d009973e3b5c1b67ff718c58", - "sha256:f12df6b45abc18f27f6e21ce26f7cbf7aa19820911462e46536e22085658ca1e", - "sha256:f36baafd82119c4a114b9518202f2a983819101dcc14b26e43fc12cbefdce00e", - "sha256:f52b79c8796d81391ab295b04e520bda6feed54d54931708872e8f9ae9db0ea1", - "sha256:fa2a50f762d06d84125db0b95d0121e9c640afa7edc23fc0848896760a390f8e", - "sha256:fa49bb60792b542b95ca93a39041e7113843093ce3cfd216870118eb3798fcc9", - "sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888", - "sha256:ffbccfe1c077b5f41738bd719518213c217be7a7a12a7e74113d05a0d6617390" + "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", + "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", + "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", + "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", + "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", + "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", + "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", + "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", + "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", + "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", + "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", + "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", + "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", + "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", + "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", + "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", + "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", + "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", + "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", + "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", + "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", + "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", + "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", + "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", + "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", + "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", + "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", + "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", + "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", + "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", + "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", + "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", + "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", + "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", + "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", + "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", + "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", + "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", + "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", + "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", + "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", + "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", + "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", + "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", + "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", + "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", + "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", + "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", + "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", + "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", + "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" ], "index": "pypi", - "version": "==5.3.0" + "version": "==5.4.1" }, "pycares": { "hashes": [ - "sha256:0e81c971236bb0767354f1456e67ab6ae305f248565ce77cd413a311f9572bf5", - "sha256:11c0ff3ccdb5a838cbd59a4e59df35d31355a80a61393bca786ca3b44569ba10", - "sha256:170d62bd300999227e64da4fa85459728cc96e62e44780bbc86a915fdae01f78", - "sha256:36f4c03df57c41a87eb3d642201684eb5a8bc194f4bafaa9f60ee6dc0aef8e40", - "sha256:371ce688776da984c4105c8ca760cc60944b9b49ccf8335c71dc7669335e6173", - "sha256:3a2234516f7db495083d8bba0ccdaabae587e62cfcd1b8154d5d0b09d3a48dfc", - "sha256:3f288586592c697109b2b06e3988b7e17d9765887b5fc367010ee8500cbddc86", - "sha256:40134cee03c8bbfbc644d4c0bc81796e12dd012a5257fb146c5a5417812ee5f7", - "sha256:722f5d2c5f78d47b13b0112f6daff43ce4e08e8152319524d14f1f917cc5125e", - "sha256:7b18fab0ed534a898552df91bc804bd62bb3a2646c11e054baca14d23663e1d6", - "sha256:8a39d03bd99ea191f86b990ef67ecce878d6bf6518c5cde9173fb34fb36beb5e", - "sha256:8ea263de8bf1a30b0d87150b4aa0e3203cf93bc1723ea3e7408a7d25e1299217", - "sha256:943e2dc67ff45ab4c81d628c959837d01561d7e185080ab7a276b8ca67573fb5", - "sha256:9d56a54c93e64b30c0d31f394d9890f175edec029cd846221728f99263cdee82", - "sha256:b95b339c11d824f0bb789d31b91c8534916fcbdce248cccce216fa2630bb8a90", - "sha256:bbfd9aba1e172cd2ab7b7142d49b28cf44d6451c4a66a870aff1dc3cb84849c7", - "sha256:d8637bcc2f901aa61ec1d754abc862f9f145cb0346a0249360df4c159377018e", - "sha256:e2446577eeea79d2179c9469d9d4ce3ab8a07d7985465c3cb91e7d74abc329b6", - "sha256:e72fa163f37ae3b09f143cc6690a36f012d13e905d142e1beed4ec0e593ff657", - "sha256:f32b7c63094749fbc0c1106c9a785666ec8afd49ecfe7002a30bb7c42e62b47c", - "sha256:f50be4dd53f009cfb4b98c3c6b240e18ff9b17e3f1c320bd594bb83eddabfcb2" + "sha256:080ae0f1b1b754be60b6ef31b9ab2915364c210eb1cb4d8e089357c89d7b9819", + "sha256:0eccb76dff0155ddf793a589c6270e1bdbf6975b2824d18d1d23db2075d7fc96", + "sha256:223a03d69e864a18d7bb2e0108bca5ba069ef91e5b048b953ed90ea9f50eb77f", + "sha256:289e49f98adfd7a2ae3656df26e1d62cf49a06bbc03ced63f243c22cd8919adf", + "sha256:292ac442a1d4ff27d41be748ec19f0c4ff47efebfb715064ba336564ea0f2071", + "sha256:34771095123da0e54597fe3c5585a28d3799945257e51b378a20778bf33573b6", + "sha256:34c8865f2d047be4c301ce90a916c7748be597e271c5c7932e8b9a6de85840f4", + "sha256:36af260b215f86ebfe4a5e4aea82fd6036168a5710cbf8aad77019ab52156dda", + "sha256:5e8e2a461717da40482b5fecf1119116234922d29660b3c3e01cbc5ba2cbf4bd", + "sha256:61e77bd75542c56dff49434fedbafb25604997bc57dc0ebf791a5732503cb1bb", + "sha256:691740c332f38a9035b4c6d1f0e6c8af239466ef2373a894d4393f0ea65c815d", + "sha256:6bc0e0fdcb4cdc4ca06aa0b07e6e3560d62b2af79ef0ea4589835fcd2059012b", + "sha256:96db5c93e2fe2e39f519efb7bb9d86aef56f5813fa0b032e47aba329fa925d57", + "sha256:af701b22c91b3e36f65ee9f4b1bc2fe4800c8ed486eb6ef203624acbe53d026d", + "sha256:b25bd21bba9c43d44320b719118c2ce35e4a78031f61d906caeb01316d49dafb", + "sha256:c42f68319f8ea2322ed81c31a86c4e60547e6e90f3ebef479a7a7540bddbf268", + "sha256:cc9a8d35af12bc5f484f3496f9cb3ab5bedfa4dcf3dfff953099453d88b659a7", + "sha256:dfee9d198ba6d6f29aa5bf510bfb2c28a60c3f308116f114c9fd311980d3e870", + "sha256:e1dd02e110a7a97582097ebba6713d9da28583b538c08e8a14bc82169c5d3e10", + "sha256:e48c586c80a139c6c7fb0298b944d1c40752cf839bc8584cc793e42a8971ba6c", + "sha256:f509762dec1a70eac32b86c098f37ac9c5d3d4a8a9098983328377c9e71543b2", + "sha256:f8e0d61733843844f9019c911d5676818d99c4cd2c54b91de58384c7d962862b", + "sha256:fe20280fed496deba60e0f6437b7672bdc83bf45e243bb546af47c60c85bcfbc" ], - "version": "==2.3.0" + "version": "==2.4.0" }, "pycparser": { "hashes": [ @@ -502,10 +501,10 @@ }, "pytz": { "hashes": [ - "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", - "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" + "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", + "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" ], - "version": "==2018.7" + "version": "==2018.9" }, "pyyaml": { "hashes": [ @@ -552,6 +551,13 @@ ], "version": "==1.2.1" }, + "soupsieve": { + "hashes": [ + "sha256:1d6ca207e67765d5297a59d1b5a18344a84587674d8c002cea72081c01a7f638", + "sha256:dff67354bff219f169ee634173c0148fcb0f7b23304ffddcfa2bb2f07accf30a" + ], + "version": "==1.7" + }, "sphinx": { "hashes": [ "sha256:429e3172466df289f0f742471d7e30ba3ee11f3b5aecd9a840480d03f14bcfe5", diff --git a/bot/cogs/information.py b/bot/cogs/information.py index c0401fe19..12970bd0e 100644 --- a/bot/cogs/information.py +++ b/bot/cogs/information.py @@ -137,7 +137,7 @@ class Information: # Non-moderators may only do this in #bot-commands if not with_role_check(ctx, *MODERATION_ROLES): - if not ctx.channel == Channels.bot: + if not ctx.channel.id == Channels.bot: raise MissingPermissions("You can't do that here!") # Validates hidden input @@ -205,7 +205,7 @@ class Information: await ctx.send(embed=embed) @user_info.error - async def eval_command_error(self, ctx: Context, error: CommandError): + async def user_info_command_error(self, ctx: Context, error: CommandError): embed = Embed(colour=Colour.red()) if isinstance(error, BadArgument): @@ -218,6 +218,9 @@ class Information: embed.description = f"Sorry, but you may only use this command within <#{Channels.bot}>." await ctx.send(embed=embed) + else: + log.exception(f"Unhandled error: {error}") + def setup(bot): bot.add_cog(Information(bot)) -- cgit v1.2.3 From da798da7ea4117204bd7eb1ac0d8f9c8ae7891d3 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 13 Jan 2019 00:05:59 +0100 Subject: reverting the lockfile, no changes needed --- Pipfile.lock | 362 +++++++++++++++++++++++++++-------------------------------- 1 file changed, 166 insertions(+), 196 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 8dea91451..506b17065 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "aio-pika": { "hashes": [ - "sha256:c3eb639f7fc5c96355e7a227380989c9e0f342bb6612e6671ea76d188813ba45", - "sha256:ea26efd262d7c4cd4ac00fb968ede89e82c00ad331b47415e3c2353a4b91cbe0" + "sha256:6438e72963e459552f196a07a081a5f6dc54d42a474292b8497bd4a59554fc85", + "sha256:dc15b451dca6d2b1c504ab353e3f2fe7e7e252fdb1c219261b5412e1cafbc72d" ], "index": "pypi", - "version": "==4.9.1" + "version": "==4.6.3" }, "aiodns": { "hashes": [ @@ -57,7 +57,6 @@ "sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0", "sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07" ], - "index": "pypi", "version": "==3.4.4" }, "alabaster": { @@ -90,18 +89,18 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858", - "sha256:945065979fb8529dd2f37dbb58f00b661bdbcbebf954f93b32fdf5263ef35348", - "sha256:ba6d5c59906a85ac23dadfe5c88deaf3e179ef565f4898671253e50a78680718" + "sha256:194ec62a25438adcb3fdb06378b26559eda1ea8a747367d34c33cef9c7f48d57", + "sha256:90f8e61121d6ae58362ce3bed8cd997efb00c914eae0ff3d363c32f9a9822d10", + "sha256:f0abd31228055d698bb392a826528ea08ebb9959e6bea17c606fd9c9009db938" ], - "version": "==4.7.1" + "version": "==4.6.3" }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" ], - "version": "==2018.11.29" + "version": "==2018.10.15" }, "cffi": { "hashes": [ @@ -189,10 +188,10 @@ }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" ], - "version": "==2.8" + "version": "==2.7" }, "idna-ssl": { "hashes": [ @@ -232,35 +231,39 @@ }, "lxml": { "hashes": [ - "sha256:0dd6589fa75d369ba06d2b5f38dae107f76ea127f212f6a7bee134f6df2d1d21", - "sha256:1afbac344aa68c29e81ab56c1a9411c3663157b5aee5065b7fa030b398d4f7e0", - "sha256:1baad9d073692421ad5dbbd81430aba6c7f5fdc347f03537ae046ddf2c9b2297", - "sha256:1d8736421a2358becd3edf20260e41a06a0bf08a560480d3a5734a6bcbacf591", - "sha256:1e1d9bddc5afaddf0de76246d3f2152f961697ad7439c559f179002682c45801", - "sha256:1f179dc8b2643715f020f4d119d5529b02cd794c1c8f305868b73b8674d2a03f", - "sha256:241fb7bdf97cb1df1edfa8f0bcdfd80525d4023dac4523a241907c8b2f44e541", - "sha256:2f9765ee5acd3dbdcdc0d0c79309e01f7c16bc8d39b49250bf88de7b46daaf58", - "sha256:312e1e1b1c3ce0c67e0b8105317323e12807955e8186872affb667dbd67971f6", - "sha256:3273db1a8055ca70257fd3691c6d2c216544e1a70b673543e15cc077d8e9c730", - "sha256:34dfaa8c02891f9a246b17a732ca3e99c5e42802416628e740a5d1cb2f50ff49", - "sha256:3aa3f5288af349a0f3a96448ebf2e57e17332d99f4f30b02093b7948bd9f94cc", - "sha256:51102e160b9d83c1cc435162d90b8e3c8c93b28d18d87b60c56522d332d26879", - "sha256:56115fc2e2a4140e8994eb9585119a1ae9223b506826089a3ba753a62bd194a6", - "sha256:69d83de14dbe8fe51dccfd36f88bf0b40f5debeac763edf9f8325180190eba6e", - "sha256:99fdce94aeaa3ccbdfcb1e23b34273605c5853aa92ec23d84c84765178662c6c", - "sha256:a7c0cd5b8a20f3093ee4a67374ccb3b8a126743b15a4d759e2a1bf098faac2b2", - "sha256:abe12886554634ed95416a46701a917784cb2b4c77bfacac6916681d49bbf83d", - "sha256:b4f67b5183bd5f9bafaeb76ad119e977ba570d2b0e61202f534ac9b5c33b4485", - "sha256:bdd7c1658475cc1b867b36d5c4ed4bc316be8d3368abe03d348ba906a1f83b0e", - "sha256:c6f24149a19f611a415a51b9bc5f17b6c2f698e0d6b41ffb3fa9f24d35d05d73", - "sha256:d1e111b3ab98613115a208c1017f266478b0ab224a67bc8eac670fa0bad7d488", - "sha256:d6520aa965773bbab6cb7a791d5895b00d02cf9adc93ac2bf4edb9ac1a6addc5", - "sha256:dd185cde2ccad7b649593b0cda72021bc8a91667417001dbaf24cd746ecb7c11", - "sha256:de2e5b0828a9d285f909b5d2e9d43f1cf6cf21fe65bc7660bdaa1780c7b58298", - "sha256:f726444b8e909c4f41b4fde416e1071cf28fa84634bfb4befdf400933b6463af" + "sha256:02bc220d61f46e9b9d5a53c361ef95e9f5e1d27171cd461dddb17677ae2289a5", + "sha256:22f253b542a342755f6cfc047fe4d3a296515cf9b542bc6e261af45a80b8caf6", + "sha256:2f31145c7ff665b330919bfa44aacd3a0211a76ca7e7b441039d2a0b0451e415", + "sha256:36720698c29e7a9626a0dc802ef8885f8f0239bfd1689628ecd459a061f2807f", + "sha256:438a1b0203545521f6616132bfe0f4bca86f8a401364008b30e2b26ec408ce85", + "sha256:4815892904c336bbaf73dafd54f45f69f4021c22b5bad7332176bbf4fb830568", + "sha256:5be031b0f15ad63910d8e5038b489d95a79929513b3634ad4babf77100602588", + "sha256:5c93ae37c3c588e829b037fdfbd64a6e40c901d3f93f7beed6d724c44829a3ad", + "sha256:60842230678674cdac4a1cf0f707ef12d75b9a4fc4a565add4f710b5fcf185d5", + "sha256:62939a8bb6758d1bf923aa1c13f0bcfa9bf5b2fc0f5fa917a6e25db5fe0cfa4e", + "sha256:75830c06a62fe7b8fe3bbb5f269f0b308f19f3949ac81cfd40062f47c1455faf", + "sha256:81992565b74332c7c1aff6a913a3e906771aa81c9d0c68c68113cffcae45bc53", + "sha256:8c892fb0ee52c594d9a7751c7d7356056a9682674b92cc1c4dc968ff0f30c52f", + "sha256:9d862e3cf4fc1f2837dedce9c42269c8c76d027e49820a548ac89fdcee1e361f", + "sha256:a623965c086a6e91bb703d4da62dabe59fe88888e82c4117d544e11fd74835d6", + "sha256:a7783ab7f6a508b0510490cef9f857b763d796ba7476d9703f89722928d1e113", + "sha256:aab09fbe8abfa3b9ce62aaf45aca2d28726b1b9ee44871dbe644050a2fff4940", + "sha256:abf181934ac3ef193832fb973fd7f6149b5c531903c2ec0f1220941d73eee601", + "sha256:ae07fa0c115733fce1e9da96a3ac3fa24801742ca17e917e0c79d63a01eeb843", + "sha256:b9c78242219f674ab645ec571c9a95d70f381319a23911941cd2358a8e0521cf", + "sha256:bccb267678b870d9782c3b44d0cefe3ba0e329f9af8c946d32bf3778e7a4f271", + "sha256:c4df4d27f4c93b2cef74579f00b1d3a31a929c7d8023f870c4b476f03a274db4", + "sha256:caf0e50b546bb60dfa99bb18dfa6748458a83131ecdceaf5c071d74907e7e78a", + "sha256:d3266bd3ac59ac4edcd5fa75165dee80b94a3e5c91049df5f7c057ccf097551c", + "sha256:db0d213987bcd4e6d41710fb4532b22315b0d8fb439ff901782234456556aed1", + "sha256:dbbd5cf7690a40a9f0a9325ab480d0fccf46d16b378eefc08e195d84299bfae1", + "sha256:e16e07a0ec3a75b5ee61f2b1003c35696738f937dc8148fbda9fe2147ccb6e61", + "sha256:e175a006725c7faadbe69e791877d09936c0ef2cf49d01b60a6c1efcb0e8be6f", + "sha256:edd9c13a97f6550f9da2236126bb51c092b3b1ce6187f2bd966533ad794bbb5e", + "sha256:fa39ea60d527fbdd94215b5e5552f1c6a912624521093f1384a491a8ad89ad8b" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.2.5" }, "markdownify": { "hashes": [ @@ -304,37 +307,37 @@ }, "multidict": { "hashes": [ - "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", - "sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3", - "sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef", - "sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b", - "sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73", - "sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc", - "sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3", - "sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd", - "sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351", - "sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941", - "sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d", - "sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1", - "sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b", - "sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a", - "sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3", - "sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7", - "sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0", - "sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0", - "sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014", - "sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5", - "sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036", - "sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d", - "sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a", - "sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce", - "sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1", - "sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a", - "sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9", - "sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7", - "sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b" - ], - "version": "==4.5.2" + "sha256:013eb6591ab95173fd3deb7667d80951abac80100335b3e97b5fa778c1bb4b91", + "sha256:0bffbbbb48db35f57dfb4733e943ac8178efb31aab5601cb7b303ee228ce96af", + "sha256:1a34aab1dfba492407c757532f665ba3282ec4a40b0d2f678bda828ef422ebb7", + "sha256:1b4b46a33f459a2951b0fd26c2d80639810631eb99b3d846d298b02d28a3e31d", + "sha256:1d616d80c37a388891bf760d64bc50cac7c61dbb7d7013f2373aa4b44936e9f0", + "sha256:225aefa7befbe05bd0116ef87e8cd76cbf4ac39457a66faf7fb5f3c2d7bea19a", + "sha256:2c9b28985ef7c830d5c7ea344d068bcdee22f8b6c251369dea98c3a814713d44", + "sha256:39e0600f8dd72acb011d09960da560ba3451b1eca8de5557c15705afc9d35f0e", + "sha256:3c642c40ea1ca074397698446893a45cd6059d5d071fc3ba3915c430c125320f", + "sha256:42357c90b488fac38852bcd7b31dcd36b1e2325413960304c28b8d98e6ff5fd4", + "sha256:6ac668f27dbdf8a69c31252f501e128a69a60b43a44e43d712fb58ce3e5dfcca", + "sha256:713683da2e3f1dd81a920c995df5dda51f1fff2b3995f5864c3ee782fcdcb96c", + "sha256:73b6e7853b6d3bc0eac795044e700467631dff37a5a33d3230122b03076ac2f9", + "sha256:77534c1b9f4a5d0962392cad3f668d1a04036b807618e3357eb2c50d8b05f7f7", + "sha256:77b579ef57e27457064bb6bb4c8e5ede866af071af60fe3576226136048c6dfa", + "sha256:82cf28f18c935d66c15a6f82fda766a4138d21e78532a1946b8ec603019ba0b8", + "sha256:937e8f12f9edc0d2e351c09fc3e7335a65eefb75406339d488ee46ef241f75d8", + "sha256:985dbf59e92f475573a04598f9a00f92b4fdb64fc41f1df2ea6f33b689319537", + "sha256:9c4fab7599ba8c0dbf829272c48c519625c2b7f5630b49925802f1af3a77f1f4", + "sha256:9e8772be8455b49a85ad6dbf6ce433da7856ba481d6db36f53507ae540823b15", + "sha256:a06d6d88ce3be4b54deabd078810e3c077a8b2e20f0ce541c979b5dd49337031", + "sha256:a1da0cdc3bc45315d313af976dab900888dbb477d812997ee0e6e4ea43d325e5", + "sha256:a6652466a4800e9fde04bf0252e914fff5f05e2a40ee1453db898149624dfe04", + "sha256:a7f23523ea6a01f77e0c6da8aae37ab7943e35630a8d2eda7e49502f36b51b46", + "sha256:a87429da49f4c9fb37a6a171fa38b59a99efdeabffb34b4255a7a849ffd74a20", + "sha256:c26bb81d0d19619367a96593a097baec2d5a7b3a0cfd1e3a9470277505a465c2", + "sha256:d4f4545edb4987f00fde44241cef436bf6471aaac7d21c6bbd497cca6049f613", + "sha256:daabc2766a2b76b3bec2086954c48d5f215f75a335eaee1e89c8357922a3c4d5", + "sha256:f08c1dcac70b558183b3b755b92f1135a76fd1caa04009b89ddea57a815599aa" + ], + "version": "==4.5.1" }, "packaging": { "hashes": [ @@ -345,88 +348,65 @@ }, "pillow": { "hashes": [ - "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", - "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", - "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", - "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", - "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", - "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", - "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", - "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", - "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", - "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", - "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", - "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", - "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", - "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", - "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", - "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", - "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", - "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", - "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", - "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", - "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", - "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", - "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", - "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", - "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", - "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", - "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", - "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", - "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", - "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", - "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", - "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", - "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", - "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", - "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", - "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", - "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", - "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", - "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", - "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", - "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", - "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", - "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", - "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", - "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", - "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", - "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", - "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", - "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", - "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", - "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" + "sha256:00203f406818c3f45d47bb8fe7e67d3feddb8dcbbd45a289a1de7dd789226360", + "sha256:0616f800f348664e694dddb0b0c88d26761dd5e9f34e1ed7b7a7d2da14b40cb7", + "sha256:1f7908aab90c92ad85af9d2fec5fc79456a89b3adcc26314d2cde0e238bd789e", + "sha256:2ea3517cd5779843de8a759c2349a3cd8d3893e03ab47053b66d5ec6f8bc4f93", + "sha256:48a9f0538c91fc136b3a576bee0e7cd174773dc9920b310c21dcb5519722e82c", + "sha256:5280ebc42641a1283b7b1f2c20e5b936692198b9dd9995527c18b794850be1a8", + "sha256:5e34e4b5764af65551647f5cc67cf5198c1d05621781d5173b342e5e55bf023b", + "sha256:63b120421ab85cad909792583f83b6ca3584610c2fe70751e23f606a3c2e87f0", + "sha256:696b5e0109fe368d0057f484e2e91717b49a03f1e310f857f133a4acec9f91dd", + "sha256:870ed021a42b1b02b5fe4a739ea735f671a84128c0a666c705db2cb9abd528eb", + "sha256:916da1c19e4012d06a372127d7140dae894806fad67ef44330e5600d77833581", + "sha256:9303a289fa0811e1c6abd9ddebfc770556d7c3311cb2b32eff72164ddc49bc64", + "sha256:9577888ecc0ad7d06c3746afaba339c94d62b59da16f7a5d1cff9e491f23dace", + "sha256:987e1c94a33c93d9b209315bfda9faa54b8edfce6438a1e93ae866ba20de5956", + "sha256:99a3bbdbb844f4fb5d6dd59fac836a40749781c1fa63c563bc216c27aef63f60", + "sha256:99db8dc3097ceafbcff9cb2bff384b974795edeb11d167d391a02c7bfeeb6e16", + "sha256:a5a96cf49eb580756a44ecf12949e52f211e20bffbf5a95760ac14b1e499cd37", + "sha256:aa6ca3eb56704cdc0d876fc6047ffd5ee960caad52452fbee0f99908a141a0ae", + "sha256:aade5e66795c94e4a2b2624affeea8979648d1b0ae3fcee17e74e2c647fc4a8a", + "sha256:b78905860336c1d292409e3df6ad39cc1f1c7f0964e66844bbc2ebfca434d073", + "sha256:b92f521cdc4e4a3041cc343625b699f20b0b5f976793fb45681aac1efda565f8", + "sha256:bfde84bbd6ae5f782206d454b67b7ee8f7f818c29b99fd02bf022fd33bab14cb", + "sha256:c2b62d3df80e694c0e4a0ed47754c9480521e25642251b3ab1dff050a4e60409", + "sha256:c5e2be6c263b64f6f7656e23e18a4a9980cffc671442795682e8c4e4f815dd9f", + "sha256:c99aa3c63104e0818ec566f8ff3942fb7c7a8f35f9912cb63fd8e12318b214b2", + "sha256:dae06620d3978da346375ebf88b9e2dd7d151335ba668c995aea9ed07af7add4", + "sha256:db5499d0710823fa4fb88206050d46544e8f0e0136a9a5f5570b026584c8fd74", + "sha256:f36baafd82119c4a114b9518202f2a983819101dcc14b26e43fc12cbefdce00e", + "sha256:f52b79c8796d81391ab295b04e520bda6feed54d54931708872e8f9ae9db0ea1", + "sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888" ], "index": "pypi", - "version": "==5.4.1" + "version": "==5.3.0" }, "pycares": { "hashes": [ - "sha256:080ae0f1b1b754be60b6ef31b9ab2915364c210eb1cb4d8e089357c89d7b9819", - "sha256:0eccb76dff0155ddf793a589c6270e1bdbf6975b2824d18d1d23db2075d7fc96", - "sha256:223a03d69e864a18d7bb2e0108bca5ba069ef91e5b048b953ed90ea9f50eb77f", - "sha256:289e49f98adfd7a2ae3656df26e1d62cf49a06bbc03ced63f243c22cd8919adf", - "sha256:292ac442a1d4ff27d41be748ec19f0c4ff47efebfb715064ba336564ea0f2071", - "sha256:34771095123da0e54597fe3c5585a28d3799945257e51b378a20778bf33573b6", - "sha256:34c8865f2d047be4c301ce90a916c7748be597e271c5c7932e8b9a6de85840f4", - "sha256:36af260b215f86ebfe4a5e4aea82fd6036168a5710cbf8aad77019ab52156dda", - "sha256:5e8e2a461717da40482b5fecf1119116234922d29660b3c3e01cbc5ba2cbf4bd", - "sha256:61e77bd75542c56dff49434fedbafb25604997bc57dc0ebf791a5732503cb1bb", - "sha256:691740c332f38a9035b4c6d1f0e6c8af239466ef2373a894d4393f0ea65c815d", - "sha256:6bc0e0fdcb4cdc4ca06aa0b07e6e3560d62b2af79ef0ea4589835fcd2059012b", - "sha256:96db5c93e2fe2e39f519efb7bb9d86aef56f5813fa0b032e47aba329fa925d57", - "sha256:af701b22c91b3e36f65ee9f4b1bc2fe4800c8ed486eb6ef203624acbe53d026d", - "sha256:b25bd21bba9c43d44320b719118c2ce35e4a78031f61d906caeb01316d49dafb", - "sha256:c42f68319f8ea2322ed81c31a86c4e60547e6e90f3ebef479a7a7540bddbf268", - "sha256:cc9a8d35af12bc5f484f3496f9cb3ab5bedfa4dcf3dfff953099453d88b659a7", - "sha256:dfee9d198ba6d6f29aa5bf510bfb2c28a60c3f308116f114c9fd311980d3e870", - "sha256:e1dd02e110a7a97582097ebba6713d9da28583b538c08e8a14bc82169c5d3e10", - "sha256:e48c586c80a139c6c7fb0298b944d1c40752cf839bc8584cc793e42a8971ba6c", - "sha256:f509762dec1a70eac32b86c098f37ac9c5d3d4a8a9098983328377c9e71543b2", - "sha256:f8e0d61733843844f9019c911d5676818d99c4cd2c54b91de58384c7d962862b", - "sha256:fe20280fed496deba60e0f6437b7672bdc83bf45e243bb546af47c60c85bcfbc" + "sha256:0e81c971236bb0767354f1456e67ab6ae305f248565ce77cd413a311f9572bf5", + "sha256:11c0ff3ccdb5a838cbd59a4e59df35d31355a80a61393bca786ca3b44569ba10", + "sha256:170d62bd300999227e64da4fa85459728cc96e62e44780bbc86a915fdae01f78", + "sha256:36f4c03df57c41a87eb3d642201684eb5a8bc194f4bafaa9f60ee6dc0aef8e40", + "sha256:371ce688776da984c4105c8ca760cc60944b9b49ccf8335c71dc7669335e6173", + "sha256:3a2234516f7db495083d8bba0ccdaabae587e62cfcd1b8154d5d0b09d3a48dfc", + "sha256:3f288586592c697109b2b06e3988b7e17d9765887b5fc367010ee8500cbddc86", + "sha256:40134cee03c8bbfbc644d4c0bc81796e12dd012a5257fb146c5a5417812ee5f7", + "sha256:722f5d2c5f78d47b13b0112f6daff43ce4e08e8152319524d14f1f917cc5125e", + "sha256:7b18fab0ed534a898552df91bc804bd62bb3a2646c11e054baca14d23663e1d6", + "sha256:8a39d03bd99ea191f86b990ef67ecce878d6bf6518c5cde9173fb34fb36beb5e", + "sha256:8ea263de8bf1a30b0d87150b4aa0e3203cf93bc1723ea3e7408a7d25e1299217", + "sha256:943e2dc67ff45ab4c81d628c959837d01561d7e185080ab7a276b8ca67573fb5", + "sha256:9d56a54c93e64b30c0d31f394d9890f175edec029cd846221728f99263cdee82", + "sha256:b95b339c11d824f0bb789d31b91c8534916fcbdce248cccce216fa2630bb8a90", + "sha256:bbfd9aba1e172cd2ab7b7142d49b28cf44d6451c4a66a870aff1dc3cb84849c7", + "sha256:d8637bcc2f901aa61ec1d754abc862f9f145cb0346a0249360df4c159377018e", + "sha256:e2446577eeea79d2179c9469d9d4ce3ab8a07d7985465c3cb91e7d74abc329b6", + "sha256:e72fa163f37ae3b09f143cc6690a36f012d13e905d142e1beed4ec0e593ff657", + "sha256:f32b7c63094749fbc0c1106c9a785666ec8afd49ecfe7002a30bb7c42e62b47c", + "sha256:f50be4dd53f009cfb4b98c3c6b240e18ff9b17e3f1c320bd594bb83eddabfcb2" ], - "version": "==2.4.0" + "version": "==2.3.0" }, "pycparser": { "hashes": [ @@ -436,10 +416,10 @@ }, "pygments": { "hashes": [ - "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", - "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" ], - "version": "==2.3.1" + "version": "==2.2.0" }, "pynacl": { "hashes": [ @@ -501,10 +481,10 @@ }, "pytz": { "hashes": [ - "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", - "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" + "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", + "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" ], - "version": "==2018.9" + "version": "==2018.7" }, "pyyaml": { "hashes": [ @@ -525,11 +505,11 @@ }, "requests": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" ], "index": "pypi", - "version": "==2.21.0" + "version": "==2.20.1" }, "shortuuid": { "hashes": [ @@ -539,10 +519,10 @@ }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" ], - "version": "==1.12.0" + "version": "==1.11.0" }, "snowballstemmer": { "hashes": [ @@ -551,20 +531,13 @@ ], "version": "==1.2.1" }, - "soupsieve": { - "hashes": [ - "sha256:1d6ca207e67765d5297a59d1b5a18344a84587674d8c002cea72081c01a7f638", - "sha256:dff67354bff219f169ee634173c0148fcb0f7b23304ffddcfa2bb2f07accf30a" - ], - "version": "==1.7" - }, "sphinx": { "hashes": [ - "sha256:429e3172466df289f0f742471d7e30ba3ee11f3b5aecd9a840480d03f14bcfe5", - "sha256:c4cb17ba44acffae3d3209646b6baec1e215cad3065e852c68cc569d4df1b9f8" + "sha256:120732cbddb1b2364471c3d9f8bfd4b0c5b550862f99a65736c77f970b142aea", + "sha256:b348790776490894e0424101af9c8413f2a86831524bd55c5f379d3e3e12ca64" ], "index": "pypi", - "version": "==1.8.3" + "version": "==1.8.2" }, "sphinxcontrib-websupport": { "hashes": [ @@ -608,19 +581,17 @@ }, "yarl": { "hashes": [ - "sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9", - "sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f", - "sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb", - "sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320", - "sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842", - "sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0", - "sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829", - "sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310", - "sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4", - "sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8", - "sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1" - ], - "version": "==1.3.0" + "sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9", + "sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee", + "sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308", + "sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357", + "sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78", + "sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8", + "sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1", + "sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4", + "sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7" + ], + "version": "==1.2.6" } }, "develop": { @@ -633,10 +604,10 @@ }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" ], - "version": "==2018.11.29" + "version": "==2018.10.15" }, "chardet": { "hashes": [ @@ -715,10 +686,10 @@ }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" ], - "version": "==2.8" + "version": "==2.7" }, "mccabe": { "hashes": [ @@ -736,7 +707,6 @@ }, "pycodestyle": { "hashes": [ - "sha256:74abc4e221d393ea5ce1f129ea6903209940c1ecd29e002e8c6933c2b21026e0", "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" ], @@ -775,11 +745,11 @@ }, "requests": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", + "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" ], "index": "pypi", - "version": "==2.21.0" + "version": "==2.20.1" }, "safety": { "hashes": [ @@ -791,10 +761,10 @@ }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" ], - "version": "==1.12.0" + "version": "==1.11.0" }, "urllib3": { "hashes": [ -- cgit v1.2.3 From ef622b6505f01b6305a766c6a1903799c4e95ba7 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 13 Jan 2019 01:00:32 +0100 Subject: fixing a bad conflict resolution --- bot/decorators.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/bot/decorators.py b/bot/decorators.py index 80311243d..5b4a0cbac 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -69,17 +69,6 @@ def without_role(*role_ids: int): return commands.check(predicate) -def in_channel(channel_id): - """ - Checks if the command was executed - inside of the specified channel. - """ - - async def predicate(ctx: Context): - return in_channel_check(ctx, channel_id) - return commands.check(predicate) - - def locked(): """ Allows the user to only run one instance of the decorated command at a time. -- cgit v1.2.3 From 57c65d4dde7d6d3385b9f5270a4fce2805def910 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 13 Jan 2019 01:08:27 +0100 Subject: fixing a bad conflict resolution pt2 --- bot/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/decorators.py b/bot/decorators.py index 5b4a0cbac..710045c10 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -10,7 +10,7 @@ from discord.ext import commands from discord.ext.commands import CheckFailure, Context from bot.constants import ERROR_REPLIES -from bot.utils.checks import in_channel_check, with_role_check, without_role_check +from bot.utils.checks import with_role_check, without_role_check log = logging.getLogger(__name__) -- cgit v1.2.3 From 0b3d189ca7344ba371e5212ba1a1a0070fa15091 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 13 Jan 2019 13:24:08 +0100 Subject: Using the exception message as the description in BadArgument error embeds. --- bot/cogs/information.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/information.py b/bot/cogs/information.py index 12970bd0e..129166d2f 100644 --- a/bot/cogs/information.py +++ b/bot/cogs/information.py @@ -133,7 +133,7 @@ class Information: # someone other than the caller if user and user != ctx.author: if not with_role_check(ctx, *MODERATION_ROLES): - raise BadArgument("Only mods can target other users.") + raise BadArgument("You do not have permission to use this command on users other than yourself.") # Non-moderators may only do this in #bot-commands if not with_role_check(ctx, *MODERATION_ROLES): @@ -210,7 +210,7 @@ class Information: if isinstance(error, BadArgument): embed.title = random.choice(NEGATIVE_REPLIES) - embed.description = "You do not have permission to use this command on users other than yourself." + embed.description = str(error) await ctx.send(embed=embed) elif isinstance(error, MissingPermissions): -- cgit v1.2.3 From e2b71351f9068c1f60646ea8bc65bcb50cb0e5fe Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Sun, 13 Jan 2019 21:32:08 +0100 Subject: Changing the rich embed filter to a watchlist that ignores twitter embeds; adding time delta check to avoid the trigger-twice problem --- bot/cogs/filtering.py | 42 +++++++++++++++++++++++++----------------- bot/constants.py | 3 +-- config-default.yml | 3 +-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 570d6549f..6b4469ceb 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -1,7 +1,9 @@ import logging import re +from typing import Optional import discord.errors +from dateutil.relativedelta import relativedelta from discord import Colour, DMChannel, Member, Message, TextChannel from discord.ext.commands import Bot @@ -73,18 +75,11 @@ class Filtering: f"Your URL has been removed because it matched a blacklisted domain. {_staff_mistake_str}" ) }, - "filter_rich_embeds": { - "enabled": Filter.filter_rich_embeds, + "watch_rich_embeds": { + "enabled": Filter.watch_rich_embeds, "function": self._has_rich_embed, - "type": "filter", + "type": "watchlist", "content_only": False, - "user_notification": Filter.notify_user_rich_embeds, - "notification_msg": ( - "Your post has been removed because it contained a rich embed. " - "This indicates that you're either using an unofficial discord client or are using a self-bot, " - f"both of which violate Discord's Terms of Service. {_staff_mistake_str}\n\n" - "Please don't use a self-bot or an unofficial Discord client on our server." - ) }, "watch_words": { "enabled": Filter.watch_words, @@ -107,10 +102,14 @@ class Filtering: async def on_message(self, msg: Message): await self._filter_message(msg) - async def on_message_edit(self, _: Message, after: Message): - await self._filter_message(after) + async def on_message_edit(self, before: Message, after: Message): + if not before.edited_at: + delta = relativedelta(after.edited_at, before.created_at).microseconds + else: + delta = None + await self._filter_message(after, delta) - async def _filter_message(self, msg: Message): + async def _filter_message(self, msg: Message, delta: Optional[int] = None): """ Whenever a message is sent or edited, run it through our filters to see if it @@ -141,6 +140,13 @@ class Filtering: for filter_name, _filter in self.filters.items(): # Is this specific filter enabled in the config? if _filter["enabled"]: + # Double trigger check for the embeds filter + if filter_name == "watch_rich_embeds": + # If the edit delta is less than 0.001 seconds, then we're probably dealing + # with a double filter trigger. + if delta is not None and delta < 100: + return + # Does the filter only need the message content or the full message? if _filter["content_only"]: triggered = await _filter["function"](msg.content) @@ -183,7 +189,7 @@ class Filtering: log.debug(message) - additional_embeds = msg.embeds if filter_name == "filter_rich_embeds" else None + additional_embeds = msg.embeds if filter_name == "watch_rich_embeds" else None # Send pretty mod log embed to mod-alerts await self.mod_log.send_log_message( @@ -311,11 +317,13 @@ class Filtering: @staticmethod async def _has_rich_embed(msg: Message): """ - Returns True if any of the embeds in the message - are of type 'rich', returns False otherwise + Returns True if any of the embeds in the message are of type 'rich', but are not twitter + embeds. Returns False otherwise. """ if msg.embeds: - return any(embed.type == "rich" for embed in msg.embeds) + for embed in msg.embeds: + if embed.type == "rich" and (not embed.url or "twitter.com" not in embed.url): + return True return False async def notify_member(self, filtered_member: Member, reason: str, channel: TextChannel): diff --git a/bot/constants.py b/bot/constants.py index be713cef2..0e49840eb 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -201,7 +201,7 @@ class Filter(metaclass=YAMLGetter): filter_zalgo: bool filter_invites: bool filter_domains: bool - filter_rich_embeds: bool + watch_rich_embeds: bool watch_words: bool watch_tokens: bool @@ -209,7 +209,6 @@ class Filter(metaclass=YAMLGetter): notify_user_zalgo: bool notify_user_invites: bool notify_user_domains: bool - notify_user_rich_embeds: bool ping_everyone: bool guild_invite_whitelist: List[int] diff --git a/config-default.yml b/config-default.yml index bb49a46e1..3c18069f9 100644 --- a/config-default.yml +++ b/config-default.yml @@ -140,7 +140,7 @@ filter: filter_zalgo: false filter_invites: true filter_domains: true - filter_rich_embeds: false + watch_rich_embeds: true watch_words: true watch_tokens: true @@ -149,7 +149,6 @@ filter: notify_user_zalgo: false notify_user_invites: true notify_user_domains: false - notify_user_rich_embeds: true # Filter configuration ping_everyone: true # Ping @everyone when we send a mod-alert? -- cgit v1.2.3 From d52046cab42a80b2e3c56f4fabf620de62f16fa8 Mon Sep 17 00:00:00 2001 From: Scragly <29337040+scragly@users.noreply.github.com> Date: Wed, 16 Jan 2019 23:45:13 +1000 Subject: Correct altered log_msg kwarg used. --- bot/cogs/modlog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 06b9ecfe6..d60ccf895 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -705,7 +705,7 @@ class ModLog: await self.send_log_message( Icons.message_edit, Colour.blurple(), "Message edited (Before)", before_response, - channel_id=Channels.message_log, timestamp_override=timestamp, footer_override=footer + channel_id=Channels.message_log, timestamp_override=timestamp, footer=footer ) await self.send_log_message( -- cgit v1.2.3 From 8ec98acab482a83aea391a905f1b347b714e6359 Mon Sep 17 00:00:00 2001 From: Scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 17 Jan 2019 00:58:18 +1000 Subject: Add new userlog channel support. --- bot/cogs/events.py | 11 ++++++----- bot/cogs/modlog.py | 15 ++++++++++----- bot/constants.py | 1 + config-default.yml | 1 + 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index f0baecd4b..8dac83d9b 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -1,4 +1,5 @@ import logging +from functools import partial from discord import Colour, Embed, Member, Object from discord.ext.commands import ( @@ -7,7 +8,6 @@ from discord.ext.commands import ( Context, NoPrivateMessage, UserInputError ) -from bot.cogs.modlog import ModLog from bot.constants import ( Channels, Colours, DEBUG_MODE, Guild, Icons, Keys, @@ -28,8 +28,9 @@ class Events: self.headers = {"X-API-KEY": Keys.site_api} @property - def mod_log(self) -> ModLog: - return self.bot.get_cog("ModLog") + def send_log(self) -> partial: + cog = self.bot.get_cog("ModLog") + return partial(cog.send_log_message, channel_id=Channels.userlog) async def send_updated_users(self, *users, replace_all=False): users = list(filter(lambda user: str(Roles.verified) in user["roles"], users)) @@ -249,7 +250,7 @@ class Events: except Exception as e: log.exception("Failed to persist roles") - await self.mod_log.send_log_message( + await self.send_log( Icons.crown_red, Colour(Colours.soft_red), "Failed to persist roles", f"```py\n{e}\n```", member.avatar_url_as(static_format="png") @@ -290,7 +291,7 @@ class Events: reason="Roles restored" ) - await self.mod_log.send_log_message( + await self.send_log( Icons.crown_blurple, Colour.blurple(), "Roles restored", f"Restored {len(new_roles)} roles", member.avatar_url_as(static_format="png") diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 55611c5e4..843b0cf83 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -381,7 +381,8 @@ class ModLog: await self.send_log_message( Icons.user_ban, Colour(Colours.soft_red), "User banned", f"{member.name}#{member.discriminator} (`{member.id}`)", - thumbnail=member.avatar_url_as(static_format="png") + thumbnail=member.avatar_url_as(static_format="png"), + channel_id=Channels.userlog ) async def on_member_join(self, member: Member): @@ -400,7 +401,8 @@ class ModLog: await self.send_log_message( Icons.sign_in, Colour(Colours.soft_green), "User joined", message, - thumbnail=member.avatar_url_as(static_format="png") + thumbnail=member.avatar_url_as(static_format="png"), + channel_id=Channels.userlog ) async def on_member_remove(self, member: Member): @@ -414,7 +416,8 @@ class ModLog: await self.send_log_message( Icons.sign_out, Colour(Colours.soft_red), "User left", f"{member.name}#{member.discriminator} (`{member.id}`)", - thumbnail=member.avatar_url_as(static_format="png") + thumbnail=member.avatar_url_as(static_format="png"), + channel_id=Channels.userlog ) async def on_member_unban(self, guild: Guild, member: User): @@ -428,7 +431,8 @@ class ModLog: await self.send_log_message( Icons.user_unban, Colour.blurple(), "User unbanned", f"{member.name}#{member.discriminator} (`{member.id}`)", - thumbnail=member.avatar_url_as(static_format="png") + thumbnail=member.avatar_url_as(static_format="png"), + channel_id=Channels.userlog ) async def on_member_update(self, before: Member, after: Member): @@ -516,7 +520,8 @@ class ModLog: await self.send_log_message( Icons.user_update, Colour.blurple(), "Member updated", message, - thumbnail=after.avatar_url_as(static_format="png") + thumbnail=after.avatar_url_as(static_format="png"), + channel_id=Channels.userlog ) async def on_raw_bulk_message_delete(self, event: RawBulkMessageDeleteEvent): diff --git a/bot/constants.py b/bot/constants.py index be713cef2..73e21b173 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -352,6 +352,7 @@ class Channels(metaclass=YAMLGetter): off_topic_3: int python: int reddit: int + userlog: int verification: int diff --git a/config-default.yml b/config-default.yml index b6427b489..a4e4c45f7 100644 --- a/config-default.yml +++ b/config-default.yml @@ -114,6 +114,7 @@ guild: python: 267624335836053506 reddit: 458224812528238616 staff_lounge: &STAFF_LOUNGE 464905259261755392 + userlog: 528976905546760203 verification: 352442727016693763 ignored: [*ADMINS, *MESSAGE_LOG, *MODLOG] -- cgit v1.2.3 From c0f71a9d6707a8b3ca5e177cb6bec09b69d04804 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Thu, 17 Jan 2019 02:52:31 +1000 Subject: Revert modlog channel for banned notification Co-Authored-By: scragly <29337040+scragly@users.noreply.github.com> --- bot/cogs/modlog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 843b0cf83..51c52828e 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -382,7 +382,7 @@ class ModLog: Icons.user_ban, Colour(Colours.soft_red), "User banned", f"{member.name}#{member.discriminator} (`{member.id}`)", thumbnail=member.avatar_url_as(static_format="png"), - channel_id=Channels.userlog + channel_id=Channels.modlog ) async def on_member_join(self, member: Member): -- cgit v1.2.3 From dfd4e93d250585bcb5815e4f31f32a18346ebe11 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Thu, 17 Jan 2019 02:55:29 +1000 Subject: Revert modlog channel for unbanned notification Co-Authored-By: scragly <29337040+scragly@users.noreply.github.com> --- bot/cogs/modlog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 51c52828e..e58ca3ae7 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -432,7 +432,7 @@ class ModLog: Icons.user_unban, Colour.blurple(), "User unbanned", f"{member.name}#{member.discriminator} (`{member.id}`)", thumbnail=member.avatar_url_as(static_format="png"), - channel_id=Channels.userlog + channel_id=Channels.modlog ) async def on_member_update(self, before: Member, after: Member): -- cgit v1.2.3