diff options
| author | 2019-02-17 13:00:41 +0100 | |
|---|---|---|
| committer | 2019-02-17 13:00:41 +0100 | |
| commit | e499e5b07641ff3dad211b98a67080e593d94c79 (patch) | |
| tree | 09367ed3b369efb2d290a6584e65cb2d3d60fc52 | |
| parent | Changing negative bot response in nominate command to be more explicit about ... (diff) | |
| parent | Merge pull request #311 from python-discord/add-team-creator (diff) | |
Merge branch 'master' into bb-improvements
| -rw-r--r-- | bot/__main__.py | 12 | ||||
| -rw-r--r-- | bot/cogs/alias.py | 16 | ||||
| -rw-r--r-- | bot/cogs/filtering.py | 52 | ||||
| -rw-r--r-- | bot/cogs/jams.py | 83 | ||||
| -rw-r--r-- | bot/cogs/modlog.py | 4 | ||||
| -rw-r--r-- | bot/cogs/rules.py | 102 | ||||
| -rw-r--r-- | bot/cogs/site.py | 16 | ||||
| -rw-r--r-- | bot/constants.py | 1 | ||||
| -rw-r--r-- | config-default.yml | 4 |
9 files changed, 252 insertions, 38 deletions
diff --git a/bot/__main__.py b/bot/__main__.py index c9d6fd61e..6928a3960 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -38,11 +38,11 @@ else: log.warning("Timed out while waiting for RabbitMQ") # Internal/debug -bot.load_extension("bot.cogs.logging") -bot.load_extension("bot.cogs.security") bot.load_extension("bot.cogs.events") bot.load_extension("bot.cogs.filtering") +bot.load_extension("bot.cogs.logging") bot.load_extension("bot.cogs.modlog") +bot.load_extension("bot.cogs.security") # Commands, etc bot.load_extension("bot.cogs.antispam") @@ -51,6 +51,7 @@ bot.load_extension("bot.cogs.bot") bot.load_extension("bot.cogs.clean") bot.load_extension("bot.cogs.cogs") bot.load_extension("bot.cogs.help") +bot.load_extension("bot.cogs.rules") # Only load this in production if not DEBUG_MODE: @@ -59,12 +60,13 @@ if not DEBUG_MODE: # Feature cogs bot.load_extension("bot.cogs.alias") -bot.load_extension("bot.cogs.deployment") bot.load_extension("bot.cogs.defcon") +bot.load_extension("bot.cogs.deployment") bot.load_extension("bot.cogs.eval") +bot.load_extension("bot.cogs.free") bot.load_extension("bot.cogs.fun") -bot.load_extension("bot.cogs.superstarify") bot.load_extension("bot.cogs.information") +bot.load_extension("bot.cogs.jams") bot.load_extension("bot.cogs.moderation") bot.load_extension("bot.cogs.off_topic_names") bot.load_extension("bot.cogs.reddit") @@ -72,11 +74,11 @@ bot.load_extension("bot.cogs.reminders") bot.load_extension("bot.cogs.site") bot.load_extension("bot.cogs.snakes") bot.load_extension("bot.cogs.snekbox") +bot.load_extension("bot.cogs.superstarify") 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/alias.py b/bot/cogs/alias.py index 23562ad25..bf59b6d15 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -103,14 +103,6 @@ class Alias: await self.invoke(ctx, "site faq") - @command(name="rules", hidden=True) - async def site_rules_alias(self, ctx): - """ - Alias for invoking <prefix>site rules. - """ - - await self.invoke(ctx, "site rules") - @command(name="reload", hidden=True) async def cogs_reload_alias(self, ctx, *, cog_name: str): """ @@ -137,6 +129,14 @@ class Alias: await self.invoke(ctx, "defcon disable") + @command(name="exception", hidden=True) + async def tags_get_traceback_alias(self, ctx): + """ + Alias for invoking <prefix>tags get traceback. + """ + + await self.invoke(ctx, "tags get traceback") + @group(name="get", aliases=("show", "g"), hidden=True, diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 6b4469ceb..25aaf8420 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -1,6 +1,6 @@ import logging import re -from typing import Optional +from typing import Optional, Union import discord.errors from dateutil.relativedelta import relativedelta @@ -189,7 +189,25 @@ class Filtering: log.debug(message) - additional_embeds = msg.embeds if filter_name == "watch_rich_embeds" else None + additional_embeds = None + additional_embeds_msg = None + + if filter_name == "filter_invites": + additional_embeds = [] + for invite, data in triggered.items(): + embed = discord.Embed(description=( + f"**Members:**\n{data['members']}\n" + f"**Active:**\n{data['active']}" + )) + embed.set_author(name=data["name"]) + embed.set_thumbnail(url=data["icon"]) + embed.set_footer(text=f"Guild Invite Code: {invite}") + additional_embeds.append(embed) + additional_embeds_msg = "For the following guild(s):" + + elif filter_name == "watch_rich_embeds": + additional_embeds = msg.embeds + additional_embeds_msg = "With the following embed(s):" # Send pretty mod log embed to mod-alerts await self.mod_log.send_log_message( @@ -201,6 +219,7 @@ class Filtering: channel_id=Channels.mod_alerts, ping_everyone=Filter.ping_everyone, additional_embeds=additional_embeds, + additional_embeds_msg=additional_embeds_msg ) break # We don't want multiple filters to trigger @@ -282,10 +301,12 @@ class Filtering: return bool(re.search(ZALGO_RE, text)) - async def _has_invites(self, text: str) -> bool: + async def _has_invites(self, text: str) -> Union[dict, bool]: """ - Returns True if the text contains an invite which is not on the guild_invite_whitelist in - config.yml + Checks if there's any invites in the text content that aren't in the guild whitelist. + + If any are detected, a dictionary of invite data is returned, with a key per invite. + If none are detected, False is returned. Attempts to catch some of common ways to try to cheat the system. """ @@ -295,10 +316,13 @@ class Filtering: text = text.replace("\\", "") invites = re.findall(INVITE_RE, text, re.IGNORECASE) + invite_data = dict() for invite in invites: + if invite in invite_data: + continue response = await self.bot.http_session.get( - f"{URLs.discord_invite_api}/{invite}" + f"{URLs.discord_invite_api}/{invite}", params={"with_counts": "true"} ) response = await response.json() guild = response.get("guild") @@ -311,8 +335,20 @@ class Filtering: guild_id = int(guild.get("id")) if guild_id not in Filter.guild_invite_whitelist: - return True - return False + guild_icon_hash = guild["icon"] + guild_icon = ( + "https://cdn.discordapp.com/icons/" + f"{guild_id}/{guild_icon_hash}.png?size=512" + ) + + invite_data[invite] = { + "name": guild["name"], + "icon": guild_icon, + "members": response["approximate_member_count"], + "active": response["approximate_presence_count"] + } + + return invite_data if invite_data else False @staticmethod async def _has_rich_embed(msg: Message): diff --git a/bot/cogs/jams.py b/bot/cogs/jams.py new file mode 100644 index 000000000..e68aa3d2f --- /dev/null +++ b/bot/cogs/jams.py @@ -0,0 +1,83 @@ +import logging + +from discord import Member, PermissionOverwrite, utils +from discord.ext import commands + +from bot.constants import Roles +from bot.decorators import with_role + +log = logging.getLogger(__name__) + + +class CodeJams: + """ + Manages the code-jam related parts of our server + """ + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command() + @with_role(Roles.admin) + async def createteam( + self, ctx: commands.Context, + team_name: str, members: commands.Greedy[Member] + ): + """ + Create a team channel in the Code Jams category, assign roles and then add + overwrites for the team. + + The first user passed will always be the team leader. + """ + code_jam_category = utils.get(ctx.guild.categories, name="Code Jam") + + if code_jam_category is None: + log.info("Code Jam category not found, creating it.") + + category_overwrites = { + ctx.guild.default_role: PermissionOverwrite(read_messages=False), + ctx.guild.me: PermissionOverwrite(read_messages=True) + } + + code_jam_category = await ctx.guild.create_category_channel( + "Code Jam", + overwrites=category_overwrites, + reason="It's code jam time!" + ) + + # First member is always the team leader + team_channel_overwrites = { + members[0]: PermissionOverwrite( + manage_messages=True, + read_messages=True, + manage_webhooks=True + ), + ctx.guild.default_role: PermissionOverwrite(read_messages=False), + ctx.guild.get_role(Roles.developer): PermissionOverwrite(read_messages=False) + } + + # Rest of members should just have read_messages + for member in members[1:]: + team_channel_overwrites[member] = PermissionOverwrite(read_messages=True) + + # Create a channel for the team + team_channel = await ctx.guild.create_text_channel( + team_name, + overwrites=team_channel_overwrites, + category=code_jam_category + ) + + # Assign team leader role + await members[0].add_roles(ctx.guild.get_role(Roles.team_leader)) + + # Assign rest of roles + jammer_role = ctx.guild.get_role(Roles.jammer) + for member in members: + await member.add_roles(jammer_role) + + await ctx.send(f":ok_hand: Team created: {team_channel.mention}") + + +def setup(bot): + bot.add_cog(CodeJams(bot)) + log.info("Cog loaded: CodeJams") diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 495795b6d..65efda5ed 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -115,6 +115,7 @@ class ModLog: files: Optional[List[File]] = None, content: Optional[str] = None, additional_embeds: Optional[List[Embed]] = None, + additional_embeds_msg: Optional[str] = None, timestamp_override: Optional[datetime.datetime] = None, footer: Optional[str] = None, ): @@ -143,7 +144,8 @@ class ModLog: log_message = await channel.send(content=content, embed=embed, files=files) if additional_embeds: - await channel.send("With the following embed(s):") + if additional_embeds_msg: + await channel.send(additional_embeds_msg) for additional_embed in additional_embeds: await channel.send(embed=additional_embed) diff --git a/bot/cogs/rules.py b/bot/cogs/rules.py new file mode 100644 index 000000000..eee506810 --- /dev/null +++ b/bot/cogs/rules.py @@ -0,0 +1,102 @@ +import re +from typing import Optional + +from discord import Colour, Embed +from discord.ext.commands import Bot, Context, command + +from bot.constants import Channels, Roles +from bot.decorators import redirect_output +from bot.pagination import LinePaginator + +STAFF = Roles.admin, Roles.moderator, Roles.owner + + +class Rules: + + def __init__(self, bot: Bot): + self.bot = bot + + # We'll get the rules from the API when the + # site has been updated to the Django Framework. + # Hard-code the rules for now until the new RulesView is released. + + self.rules = ( + "Be polite, and do not spam.", + + "Follow the [Discord Community Guidelines](https://discordapp.com/guidelines).", + + "Don't intentionally make other people uncomfortable - if someone asks you to stop " + "discussing something, you should stop.", + + "Be patient both with users asking questions, and the users answering them.", + + "We will not help you with anything that might break a law or the terms of service " + "of any other community, site, service, or otherwise - No piracy, brute-forcing, " + "captcha circumvention, sneaker bots, or anything else of that nature.", + + "Listen to and respect the staff members - we're here to help, but we're all human " + "beings.", + + "All discussion should be kept within the relevant channels for the subject - See the " + "[channels page](https://pythondiscord.com/about/channels) for more information.", + + "This is an English-speaking server, so please speak English to the best of your " + "ability - [Google Translate](https://translate.google.com/) should be fine if you're " + "not sure.", + + "Keep all discussions safe for work - No gore, nudity, sexual soliciting, references " + "to suicide, or anything else of that nature", + + "We do not allow advertisements for communities (including other Discord servers) or " + "commercial projects - Contact us directly if you want to discuss a partnership!" + ) + self.default_desc = ("The rules and guidelines that apply to this community can be found on" + " our [rules page](https://pythondiscord.com/about/rules). We expect" + " all members of the community to have read and understood these." + ) + + @command(aliases=['r', 'rule'], name='rules') + @redirect_output(destination_channel=Channels.bot, bypass_roles=STAFF) + async def rules_command(self, ctx: Context, *, rules: Optional[str] = None): + """ + Provides a link to the `rules` endpoint of the website, or displays + specific rules, if they are requested. + + **`ctx`:** The Discord message context + **`rules`:** The rules a user wants to get. + """ + rules_embed = Embed(title='Rules', color=Colour.blurple()) + rules_embed.set_footer(text='https://pythondiscord.com/about/rules') + + if not rules: + # Rules were not submitted. Return the default description. + rules_embed.description = self.default_desc + return await ctx.send(embed=rules_embed) + + # Split the rules input by slash, comma or space + # Returns a list of ints if they're in range of rules index + rules_to_get = [] + split_rules = re.split(r'[/, ]', rules) + for item in split_rules: + if not item.isdigit(): + if not item: + continue + rule_match = re.search(r'\d?\d[:|-]1?\d', item) + if rule_match: + a, b = sorted([int(x)-1 for x in re.split(r'[:-]', rule_match.group())]) + rules_to_get.extend(range(a, b+1)) + else: + rules_to_get.append(int(item)-1) + final_rules = [ + f'**{i+1}.** {self.rules[i]}' for i in sorted(rules_to_get) if i < len(self.rules) + ] + + if not final_rules: + # No valid rules in rules input. Return the default description. + rules_embed.description = self.default_desc + return await ctx.send(embed=rules_embed) + await LinePaginator.paginate(final_rules, ctx, rules_embed, max_lines=3) + + +def setup(bot): + bot.add_cog(Rules(bot)) diff --git a/bot/cogs/site.py b/bot/cogs/site.py index 442e80cd2..e5fd645fb 100644 --- a/bot/cogs/site.py +++ b/bot/cogs/site.py @@ -92,22 +92,6 @@ class Site: await ctx.send(embed=embed) - @site_group.command(name="rules") - async def site_rules(self, ctx: Context): - """Info about the server's rules.""" - - url = f"{URLs.site_schema}{URLs.site}/about/rules" - - embed = Embed(title="Rules") - embed.set_footer(text=url) - embed.colour = Colour.blurple() - embed.description = ( - f"The rules and guidelines that apply to this community can be found on our [rules page]({url}). " - "We expect all members of the community to have read and understood these." - ) - - await ctx.send(embed=embed) - def setup(bot): bot.add_cog(Site(bot)) diff --git a/bot/constants.py b/bot/constants.py index ab62cd79d..5b9b45c1c 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -372,6 +372,7 @@ class Roles(metaclass=YAMLGetter): owner: int verified: int helpers: int + team_leader: int class Guild(metaclass=YAMLGetter): diff --git a/config-default.yml b/config-default.yml index 3ce01916e..110dd12dd 100644 --- a/config-default.yml +++ b/config-default.yml @@ -134,6 +134,7 @@ guild: verified: 352427296948486144 helpers: 267630620367257601 rockstars: &ROCKSTARS_ROLE 458226413825294336 + team_leader: 501324292341104650 filter: @@ -162,6 +163,9 @@ filter: - 273944235143593984 # STEM - 348658686962696195 # RLBot - 531221516914917387 # Pallets + - 249111029668249601 # Gentoo + - 327254708534116352 # Adafruit + - 544525886180032552 # kennethreitz.org domain_blacklist: - pornhub.com |