diff options
author | 2021-08-24 18:27:45 +0100 | |
---|---|---|
committer | 2021-08-24 18:27:45 +0100 | |
commit | 8d01d11742d10d7c89f2d078629f997903fc080b (patch) | |
tree | 079a5b8a28aca2273350e4071db06d829bd3888a | |
parent | Remove TagContentConverter (diff) | |
parent | Merge pull request #1776 from python-discord/community-partners-access (diff) |
Merge branch 'main' into converter-typehints
-rw-r--r-- | bot/constants.py | 3 | ||||
-rw-r--r-- | bot/exts/info/help.py | 4 | ||||
-rw-r--r-- | bot/exts/info/information.py | 10 | ||||
-rw-r--r-- | bot/exts/moderation/stream.py | 13 | ||||
-rw-r--r-- | bot/exts/recruitment/talentpool/_cog.py | 6 | ||||
-rw-r--r-- | bot/exts/utils/internal.py | 7 | ||||
-rw-r--r-- | bot/exts/utils/ping.py | 4 | ||||
-rw-r--r-- | bot/exts/utils/reminders.py | 14 | ||||
-rw-r--r-- | bot/exts/utils/utils.py | 12 | ||||
-rw-r--r-- | bot/rules/mentions.py | 6 | ||||
-rw-r--r-- | config-default.yml | 3 | ||||
-rw-r--r-- | tests/bot/exts/info/test_information.py | 4 | ||||
-rw-r--r-- | tests/bot/rules/test_mentions.py | 26 |
13 files changed, 75 insertions, 37 deletions
diff --git a/bot/constants.py b/bot/constants.py index 407646b28..80e01b174 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -689,7 +689,7 @@ class VideoPermission(metaclass=YAMLGetter): # Debug mode -DEBUG_MODE = 'local' in os.environ.get("SITE_URL", "local") +DEBUG_MODE: bool = _CONFIG_YAML["debug"] == "true" # Paths BOT_DIR = os.path.dirname(__file__) @@ -698,6 +698,7 @@ PROJECT_ROOT = os.path.abspath(os.path.join(BOT_DIR, os.pardir)) # Default role combinations MODERATION_ROLES = Guild.moderation_roles STAFF_ROLES = Guild.staff_roles +STAFF_PARTNERS_COMMUNITY_ROLES = STAFF_ROLES + [Roles.partners, Roles.python_community] # Channel combinations MODERATION_CHANNELS = Guild.moderation_channels diff --git a/bot/exts/info/help.py b/bot/exts/info/help.py index 0235bbaf3..21a6cf752 100644 --- a/bot/exts/info/help.py +++ b/bot/exts/info/help.py @@ -10,7 +10,7 @@ from rapidfuzz import fuzz, process from rapidfuzz.utils import default_process from bot import constants -from bot.constants import Channels, STAFF_ROLES +from bot.constants import Channels, STAFF_PARTNERS_COMMUNITY_ROLES from bot.decorators import redirect_output from bot.pagination import LinePaginator from bot.utils.messages import wait_for_deletion @@ -54,7 +54,7 @@ class CustomHelpCommand(HelpCommand): def __init__(self): super().__init__(command_attrs={"help": "Shows help for bot commands"}) - @redirect_output(destination_channel=Channels.bot_commands, bypass_roles=STAFF_ROLES) + @redirect_output(destination_channel=Channels.bot_commands, bypass_roles=STAFF_PARTNERS_COMMUNITY_ROLES) async def command_callback(self, ctx: Context, *, command: str = None) -> None: """Attempts to match the provided query with a valid command or cog.""" # the only reason we need to tamper with this is because d.py does not support "categories", diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 8bef6a8cd..38b436b7d 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -95,7 +95,7 @@ class Information(Cog): {python_general.mention} cooldown: {python_general.slowmode_delay}s """) - @has_any_role(*constants.STAFF_ROLES) + @has_any_role(*constants.STAFF_PARTNERS_COMMUNITY_ROLES) @command(name="roles") async def roles_info(self, ctx: Context) -> None: """Returns a list of all roles and their corresponding IDs.""" @@ -115,7 +115,7 @@ class Information(Cog): await LinePaginator.paginate(role_list, ctx, embed, empty=False) - @has_any_role(*constants.STAFF_ROLES) + @has_any_role(*constants.STAFF_PARTNERS_COMMUNITY_ROLES) @command(name="role") async def role_info(self, ctx: Context, *roles: Union[Role, str]) -> None: """ @@ -232,7 +232,7 @@ class Information(Cog): return # Will redirect to #bot-commands if it fails. - if in_whitelist_check(ctx, roles=constants.STAFF_ROLES): + if in_whitelist_check(ctx, roles=constants.STAFF_PARTNERS_COMMUNITY_ROLES): embed = await self.create_user_embed(ctx, user) await ctx.send(embed=embed) @@ -455,9 +455,9 @@ class Information(Cog): # remove trailing whitespace return out.rstrip() - @cooldown_with_role_bypass(2, 60 * 3, BucketType.member, bypass_roles=constants.STAFF_ROLES) + @cooldown_with_role_bypass(2, 60 * 3, BucketType.member, bypass_roles=constants.STAFF_PARTNERS_COMMUNITY_ROLES) @group(invoke_without_command=True) - @in_whitelist(channels=(constants.Channels.bot_commands,), roles=constants.STAFF_ROLES) + @in_whitelist(channels=(constants.Channels.bot_commands,), roles=constants.STAFF_PARTNERS_COMMUNITY_ROLES) async def raw(self, ctx: Context, *, message: Message, json: bool = False) -> None: """Shows information about the raw API response.""" if ctx.author not in message.channel.members: diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py index 07ee4099e..01d2614b0 100644 --- a/bot/exts/moderation/stream.py +++ b/bot/exts/moderation/stream.py @@ -9,7 +9,10 @@ from async_rediscache import RedisCache from discord.ext import commands from bot.bot import Bot -from bot.constants import Colours, Emojis, Guild, MODERATION_ROLES, Roles, STAFF_ROLES, VideoPermission +from bot.constants import ( + Colours, Emojis, Guild, MODERATION_ROLES, Roles, + STAFF_PARTNERS_COMMUNITY_ROLES, VideoPermission +) from bot.converters import Expiry from bot.pagination import LinePaginator from bot.utils.scheduling import Scheduler @@ -193,17 +196,17 @@ class Stream(commands.Cog): @commands.command(aliases=('lstream',)) @commands.has_any_role(*MODERATION_ROLES) async def liststream(self, ctx: commands.Context) -> None: - """Lists all non-staff users who have permission to stream.""" - non_staff_members_with_stream = [ + """Lists all users who aren't staff, partners or members of the python community and have stream permissions.""" + non_staff_partners_community_members_with_stream = [ member for member in ctx.guild.get_role(Roles.video).members - if not any(role.id in STAFF_ROLES for role in member.roles) + if not any(role.id in STAFF_PARTNERS_COMMUNITY_ROLES for role in member.roles) ] # List of tuples (UtcPosixTimestamp, str) # So that the list can be sorted on the UtcPosixTimestamp before the message is passed to the paginator. streamer_info = [] - for member in non_staff_members_with_stream: + for member in non_staff_partners_community_members_with_stream: if revoke_time := await self.task_cache.get(member.id): # Member only has temporary streaming perms revoke_delta = Arrow.utcfromtimestamp(revoke_time).humanize() diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py index 5c1a1cd3f..c297f70c2 100644 --- a/bot/exts/recruitment/talentpool/_cog.py +++ b/bot/exts/recruitment/talentpool/_cog.py @@ -263,7 +263,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): } ) - msg = f"✅ The nomination for {user} has been added to the talent pool" + msg = f"✅ The nomination for {user.mention} has been added to the talent pool" if history: msg += f"\n\n({len(history)} previous nominations in total)" @@ -311,7 +311,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): return if await self.unwatch(user.id, reason): - await ctx.send(f":white_check_mark: Messages sent by {user} will no longer be relayed") + await ctx.send(f":white_check_mark: Messages sent by {user.mention} will no longer be relayed") else: await ctx.send(":x: The specified user does not have an active nomination") @@ -344,7 +344,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): return if not any(entry["actor"] == actor.id for entry in nomination["entries"]): - await ctx.send(f":x: {actor} doesn't have an entry in this nomination.") + await ctx.send(f":x: {actor.mention} doesn't have an entry in this nomination.") return self.log.trace(f"Changing reason for nomination with id {nomination_id} of actor {actor} to {repr(reason)}") diff --git a/bot/exts/utils/internal.py b/bot/exts/utils/internal.py index 6f2da3131..5d2cd7611 100644 --- a/bot/exts/utils/internal.py +++ b/bot/exts/utils/internal.py @@ -11,10 +11,10 @@ from io import StringIO from typing import Any, Optional, Tuple import discord -from discord.ext.commands import Cog, Context, group, has_any_role +from discord.ext.commands import Cog, Context, group, has_any_role, is_owner from bot.bot import Bot -from bot.constants import Roles +from bot.constants import DEBUG_MODE, Roles from bot.utils import find_nth_occurrence, send_to_paste_service log = logging.getLogger(__name__) @@ -33,6 +33,9 @@ class Internal(Cog): self.socket_event_total = 0 self.socket_events = Counter() + if DEBUG_MODE: + self.eval.add_check(is_owner().predicate) + @Cog.listener() async def on_socket_response(self, msg: dict) -> None: """When a websocket event is received, increase our counters.""" diff --git a/bot/exts/utils/ping.py b/bot/exts/utils/ping.py index c6d7bd900..cf0e3265e 100644 --- a/bot/exts/utils/ping.py +++ b/bot/exts/utils/ping.py @@ -5,7 +5,7 @@ from discord import Embed from discord.ext import commands from bot.bot import Bot -from bot.constants import Channels, STAFF_ROLES, URLs +from bot.constants import Channels, STAFF_PARTNERS_COMMUNITY_ROLES, URLs from bot.decorators import in_whitelist DESCRIPTIONS = ( @@ -23,7 +23,7 @@ class Latency(commands.Cog): self.bot = bot @commands.command() - @in_whitelist(channels=(Channels.bot_commands,), roles=STAFF_ROLES) + @in_whitelist(channels=(Channels.bot_commands,), roles=STAFF_PARTNERS_COMMUNITY_ROLES) async def ping(self, ctx: commands.Context) -> None: """ Gets different measures of latency within the bot. diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 144f7b537..90ad7ef2e 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -11,7 +11,10 @@ from dateutil.parser import isoparse from discord.ext.commands import Cog, Context, Greedy, group from bot.bot import Bot -from bot.constants import Guild, Icons, MODERATION_ROLES, POSITIVE_REPLIES, Roles, STAFF_ROLES +from bot.constants import ( + Guild, Icons, MODERATION_ROLES, POSITIVE_REPLIES, + Roles, STAFF_PARTNERS_COMMUNITY_ROLES +) from bot.converters import Duration, UserMentionOrID from bot.pagination import LinePaginator from bot.utils.checks import has_any_role_check, has_no_roles_check @@ -111,7 +114,7 @@ class Reminders(Cog): If mentions aren't allowed, also return the type of mention(s) disallowed. """ - if await has_no_roles_check(ctx, *STAFF_ROLES): + if await has_no_roles_check(ctx, *STAFF_PARTNERS_COMMUNITY_ROLES): return False, "members/roles" elif await has_no_roles_check(ctx, *MODERATION_ROLES): return all(isinstance(mention, discord.Member) for mention in mentions), "roles" @@ -137,7 +140,7 @@ class Reminders(Cog): """Converts Role and Member ids to their corresponding objects if possible.""" guild = self.bot.get_guild(Guild.id) for mention_id in mention_ids: - if (mentionable := (guild.get_member(mention_id) or guild.get_role(mention_id))): + if mentionable := (guild.get_member(mention_id) or guild.get_role(mention_id)): yield mentionable def schedule_reminder(self, reminder: dict) -> None: @@ -226,8 +229,9 @@ class Reminders(Cog): Expiration is parsed per: http://strftime.org/ """ - # If the user is not staff, we need to verify whether or not to make a reminder at all. - if await has_no_roles_check(ctx, *STAFF_ROLES): + # If the user is not staff, partner or part of the python community, + # we need to verify whether or not to make a reminder at all. + if await has_no_roles_check(ctx, *STAFF_PARTNERS_COMMUNITY_ROLES): # If they don't have permission to set a reminder in this channel if ctx.channel.id not in WHITELISTED_CHANNELS: diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index 28c7ec27b..0139a6ad3 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -9,7 +9,7 @@ from discord.ext.commands import BadArgument, Cog, Context, clean_content, comma from discord.utils import snowflake_time from bot.bot import Bot -from bot.constants import Channels, MODERATION_ROLES, Roles, STAFF_ROLES +from bot.constants import Channels, MODERATION_ROLES, Roles, STAFF_PARTNERS_COMMUNITY_ROLES from bot.converters import Snowflake from bot.decorators import in_whitelist from bot.pagination import LinePaginator @@ -49,20 +49,22 @@ class Utils(Cog): self.bot = bot @command() - @in_whitelist(channels=(Channels.bot_commands, Channels.discord_py), roles=STAFF_ROLES) + @in_whitelist(channels=(Channels.bot_commands, Channels.discord_py), roles=STAFF_PARTNERS_COMMUNITY_ROLES) async def charinfo(self, ctx: Context, *, characters: str) -> None: """Shows you information on up to 50 unicode characters.""" match = re.match(r"<(a?):(\w+):(\d+)>", characters) if match: - return await messages.send_denial( + await messages.send_denial( ctx, "**Non-Character Detected**\n" "Only unicode characters can be processed, but a custom Discord emoji " "was found. Please remove it and try again." ) + return if len(characters) > 50: - return await messages.send_denial(ctx, f"Too many characters ({len(characters)}/50)") + await messages.send_denial(ctx, f"Too many characters ({len(characters)}/50)") + return def get_info(char: str) -> Tuple[str, str]: digit = f"{ord(char):x}" @@ -156,7 +158,7 @@ class Utils(Cog): await ctx.send(embed=embed) @command(aliases=("snf", "snfl", "sf")) - @in_whitelist(channels=(Channels.bot_commands,), roles=STAFF_ROLES) + @in_whitelist(channels=(Channels.bot_commands,), roles=STAFF_PARTNERS_COMMUNITY_ROLES) async def snowflake(self, ctx: Context, *snowflakes: Snowflake) -> None: """Get Discord snowflake creation time.""" if not snowflakes: diff --git a/bot/rules/mentions.py b/bot/rules/mentions.py index 79725a4b1..6f5addad1 100644 --- a/bot/rules/mentions.py +++ b/bot/rules/mentions.py @@ -13,7 +13,11 @@ async def apply( if msg.author == last_message.author ) - total_recent_mentions = sum(len(msg.mentions) for msg in relevant_messages) + total_recent_mentions = sum( + not user.bot + for msg in relevant_messages + for user in msg.mentions + ) if total_recent_mentions > config['max']: return ( diff --git a/config-default.yml b/config-default.yml index eaf8e0ad7..8e0b97a51 100644 --- a/config-default.yml +++ b/config-default.yml @@ -1,3 +1,6 @@ +debug: !ENV ["BOT_DEBUG", "true"] + + bot: prefix: "!" sentry_dsn: !ENV "BOT_SENTRY_DSN" diff --git a/tests/bot/exts/info/test_information.py b/tests/bot/exts/info/test_information.py index 0aa41d889..d8250befb 100644 --- a/tests/bot/exts/info/test_information.py +++ b/tests/bot/exts/info/test_information.py @@ -507,7 +507,7 @@ class UserCommandTests(unittest.IsolatedAsyncioTestCase): @unittest.mock.patch("bot.exts.info.information.Information.create_user_embed") async def test_staff_members_can_bypass_channel_restriction(self, create_embed, constants): """Staff members should be able to bypass the bot-commands channel restriction.""" - constants.STAFF_ROLES = [self.moderator_role.id] + constants.STAFF_PARTNERS_COMMUNITY_ROLES = [self.moderator_role.id] ctx = helpers.MockContext(author=self.moderator, channel=helpers.MockTextChannel(id=200)) await self.cog.user_info(self.cog, ctx) @@ -519,7 +519,7 @@ class UserCommandTests(unittest.IsolatedAsyncioTestCase): async def test_moderators_can_target_another_member(self, create_embed, constants): """A moderator should be able to use `!user` targeting another user.""" constants.MODERATION_ROLES = [self.moderator_role.id] - constants.STAFF_ROLES = [self.moderator_role.id] + constants.STAFF_PARTNERS_COMMUNITY_ROLES = [self.moderator_role.id] ctx = helpers.MockContext(author=self.moderator, channel=helpers.MockTextChannel(id=50)) await self.cog.user_info(self.cog, ctx, self.target) diff --git a/tests/bot/rules/test_mentions.py b/tests/bot/rules/test_mentions.py index 6444532f2..f8805ac48 100644 --- a/tests/bot/rules/test_mentions.py +++ b/tests/bot/rules/test_mentions.py @@ -2,12 +2,14 @@ from typing import Iterable from bot.rules import mentions from tests.bot.rules import DisallowedCase, RuleTest -from tests.helpers import MockMessage +from tests.helpers import MockMember, MockMessage -def make_msg(author: str, total_mentions: int) -> MockMessage: +def make_msg(author: str, total_user_mentions: int, total_bot_mentions: int = 0) -> MockMessage: """Makes a message with `total_mentions` mentions.""" - return MockMessage(author=author, mentions=list(range(total_mentions))) + user_mentions = [MockMember() for _ in range(total_user_mentions)] + bot_mentions = [MockMember(bot=True) for _ in range(total_bot_mentions)] + return MockMessage(author=author, mentions=user_mentions+bot_mentions) class TestMentions(RuleTest): @@ -48,11 +50,27 @@ class TestMentions(RuleTest): [make_msg("bob", 2), make_msg("alice", 3), make_msg("bob", 2)], ("bob",), 4, - ) + ), + DisallowedCase( + [make_msg("bob", 3, 1)], + ("bob",), + 3, + ), ) await self.run_disallowed(cases) + async def test_ignore_bot_mentions(self): + """Messages with an allowed amount of mentions, also containing bot mentions.""" + cases = ( + [make_msg("bob", 0, 3)], + [make_msg("bob", 2, 1)], + [make_msg("bob", 1, 2), make_msg("bob", 1, 2)], + [make_msg("bob", 1, 5), make_msg("alice", 2, 5)] + ) + + await self.run_allowed(cases) + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: last_message = case.recent_messages[0] return tuple( |