diff options
-rw-r--r-- | bot/__init__.py | 8 | ||||
-rw-r--r-- | bot/__main__.py | 3 | ||||
-rw-r--r-- | bot/cogs/hiphopify.py | 192 | ||||
-rw-r--r-- | bot/cogs/tags.py | 19 | ||||
-rw-r--r-- | bot/constants.py | 68 | ||||
-rw-r--r-- | bot/formatter.py | 3 |
6 files changed, 270 insertions, 23 deletions
diff --git a/bot/__init__.py b/bot/__init__.py index d005c854d..070bff585 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -1,6 +1,7 @@ # coding=utf-8 import ast import logging +import re import sys from logging import Logger, StreamHandler from logging.handlers import SysLogHandler @@ -113,7 +114,12 @@ def _get_word(self) -> str: # Is it possible to parse this without syntax error? syntax_valid = True try: - ast.literal_eval(self.buffer[self.index:]) + # Catch raw channel, member or role mentions and wrap them in quotes. + tempbuffer = re.sub(r"(<(?:@|@!|[#&])\d+>)", + r'"\1"', + self.buffer) + ast.literal_eval(tempbuffer[self.index:]) + self.buffer = tempbuffer except SyntaxError: log.warning("The command cannot be parsed by ast.literal_eval because it raises a SyntaxError.") # TODO: It would be nice if this actually made the bot return a SyntaxError. ClickUp #1b12z # noqa: T000 diff --git a/bot/__main__.py b/bot/__main__.py index 72e639851..100350d48 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -37,8 +37,9 @@ bot.load_extension("bot.cogs.cogs") bot.load_extension("bot.cogs.clickup") bot.load_extension("bot.cogs.deployment") bot.load_extension("bot.cogs.eval") -# bot.load_extension("bot.cogs.math") bot.load_extension("bot.cogs.fun") +bot.load_extension("bot.cogs.hiphopify") +# bot.load_extension("bot.cogs.math") bot.load_extension("bot.cogs.tags") bot.load_extension("bot.cogs.verification") diff --git a/bot/cogs/hiphopify.py b/bot/cogs/hiphopify.py new file mode 100644 index 000000000..9c494651c --- /dev/null +++ b/bot/cogs/hiphopify.py @@ -0,0 +1,192 @@ +import logging +import random + +from discord import Colour, Embed, Member +from discord.errors import Forbidden +from discord.ext.commands import AutoShardedBot, Context, command + +from bot.constants import ( + ADMIN_ROLE, MODERATOR_ROLE, MOD_LOG_CHANNEL, + NEGATIVE_REPLIES, OWNER_ROLE, POSITIVE_REPLIES, + SITE_API_HIPHOPIFY_URL, SITE_API_KEY +) +from bot.decorators import with_role + +log = logging.getLogger(__name__) + + +class Hiphopify: + """ + A set of commands to moderate terrible nicknames. + """ + + def __init__(self, bot: AutoShardedBot): + self.bot = bot + self.headers = {"X-API-KEY": SITE_API_KEY} + + async def on_member_update(self, before, after): + """ + This event will trigger when someone changes their name. + At this point we will look up the user in our database and check + whether they are allowed o change their names, or if they are in + hiphop-prison. If they are not allowed, we will change it back. + :return: + """ + + if before.display_name == after.display_name: + return # User didn't change their nickname. Abort! + + log.debug( + f"{before.display_name} is trying to change their nickname to {after.display_name}. " + "Checking if the user is in hiphop-prison..." + ) + + response = await self.bot.http_session.get( + SITE_API_HIPHOPIFY_URL, + headers=self.headers, + params={"user_id": str(before.id)} + ) + + response = await response.json() + + if response: + if after.display_name == response.get("forced_nick"): + return # Nick change was triggered by this event. Ignore. + + log.debug( + f"{after.display_name} is currently in hiphop-prison. " + f"Changing the nick back to {before.display_name}." + ) + await after.edit(nick=response.get("forced_nick")) + try: + await after.send( + "You have tried to change your nickname on the **Python Discord** server " + f"from **{before.display_name}** to **{after.display_name}**, but as you " + "are currently in hiphop-prison, you do not have permission to do so. " + "You will be allowed to change your nickname again at the following time:\n\n" + f"**{response.get('end_timestamp')}**." + ) + except Forbidden: + log.warning( + "The user tried to change their nickname while in hiphop-prison. " + "This led to the bot trying to DM the user to let them know they cannot do that, " + "but the user had either blocked the bot or disabled DMs, so it was not possible " + "to DM them, and a discord.errors.Forbidden error was incurred." + ) + + @with_role(ADMIN_ROLE, OWNER_ROLE, MODERATOR_ROLE) + @command(name="hiphopify()", aliases=["hiphopify", "force_nick()", "force_nick"]) + async def hiphopify(self, ctx: Context, member: Member, duration: str, forced_nick: str = None): + """ + This command will force a random rapper name (like Lil' Wayne) to be the users + nickname for a specified duration. If a forced_nick is provided, it will use that instead. + + :param ctx: Discord message context + :param ta: + If provided, this function shows data for that specific tag. + If not provided, this function shows the caller a list of all tags. + """ + + log.debug( + f"Attempting to hiphopify {member.display_name} for {duration}. " + f"forced_nick is set to {forced_nick}." + ) + + embed = Embed() + embed.colour = Colour.blurple() + + params = { + "user_id": str(member.id), + "duration": duration + } + + if forced_nick: + params["forced_nick"] = forced_nick + + response = await self.bot.http_session.post( + SITE_API_HIPHOPIFY_URL, + headers=self.headers, + json=params + ) + + response = await response.json() + + if "error_message" in response: + log.warning( + "Encountered the following error when trying to hiphopify the user:\n" + f"{response.get('error_message')}" + ) + embed.colour = Colour.red() + embed.title = random.choice(NEGATIVE_REPLIES) + embed.description = response.get("error_message") + return await ctx.send(embed=embed) + + else: + forced_nick = response.get('forced_nick') + end_time = response.get("end_timestamp") + image_url = response.get("image_url") + + embed.title = "Congratulations!" + embed.description = ( + "Your previous nickname was so bad that we have decided to change it. " + f"Your new nickname will be **{forced_nick}**.\n\n" + f"You will be unable to change your nickname back until \n**{end_time}**." + ) + embed.set_image(url=image_url) + + # Log to the mod_log channel + log.trace("Logging to the #mod-log channel. This could fail because of channel permissions.") + mod_log = self.bot.get_channel(MOD_LOG_CHANNEL) + await mod_log.send( + f":middle_finger: {member.name}#{member.discriminator} (`{member.id}`) " + f"has been hiphopified by **{ctx.author.name}**. Their new nickname is `{forced_nick}`. " + f"They will not be able to change their nickname again until **{end_time}**" + ) + + # Change the nick and return the embed + log.debug("Changing the users nickname and sending the embed.") + await member.edit(nick=forced_nick) + await ctx.send(member.mention, embed=embed) + + @with_role(ADMIN_ROLE, OWNER_ROLE, MODERATOR_ROLE) + @command(name="unhiphopify()", aliases=["unhiphopify", "release_nick()", "release_nick"]) + async def unhiphopify(self, ctx: Context, member: Member): + """ + This command will remove the entry from our database, allowing the user + to once again change their nickname. + + :param ctx: Discord message context + :param member: The member to unhiphopify + """ + + log.debug(f"Attempting to unhiphopify the following user: {member.display_name}") + + embed = Embed() + embed.colour = Colour.blurple() + + response = await self.bot.http_session.delete( + SITE_API_HIPHOPIFY_URL, + headers=self.headers, + json={"user_id": str(member.id)} + ) + + response = await response.json() + embed.description = "User has been released from hiphop-prison." + embed.title = random.choice(POSITIVE_REPLIES) + + if "error_message" in response: + embed.colour = Colour.red() + embed.title = random.choice(NEGATIVE_REPLIES) + embed.description = response.get("error_message") + log.warning( + f"Error encountered when trying to unhiphopify {member.display_name}:\n" + f"{response}" + ) + + log.debug(f"{member.display_name} was successfully released from hiphop-prison.") + await ctx.send(embed=embed) + + +def setup(bot): + bot.add_cog(Hiphopify(bot)) + log.info("Cog loaded: Hiphopify") diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index b3569b782..ca48f09cb 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -6,7 +6,10 @@ from typing import Optional from discord import Colour, Embed, User from discord.ext.commands import AutoShardedBot, Context, command -from bot.constants import ADMIN_ROLE, MODERATOR_ROLE, OWNER_ROLE, SITE_API_KEY, SITE_API_TAGS_URL, TAG_COOLDOWN +from bot.constants import ( + ADMIN_ROLE, ERROR_REPLIES, MODERATOR_ROLE, OWNER_ROLE, + SITE_API_KEY, SITE_API_TAGS_URL, TAG_COOLDOWN, +) from bot.decorators import with_role from bot.pagination import LinePaginator @@ -18,18 +21,6 @@ class Tags: Save new tags and fetch existing tags. """ - FAIL_TITLES = [ - "Please don't do that.", - "You have to stop.", - "Do you mind?", - "In the future, don't do that.", - "That was a mistake.", - "You blew it.", - "You're bad at computers.", - "Are you trying to kill me?", - "Noooooo!!" - ] - def __init__(self, bot: AutoShardedBot): self.bot = bot self.tag_cooldowns = {} @@ -148,7 +139,7 @@ class Tags: else: return None - embed.title = random.choice(Tags.FAIL_TITLES) + embed.title = random.choice(ERROR_REPLIES) return embed @command(name="tags()", aliases=["tags"], hidden=True) diff --git a/bot/constants.py b/bot/constants.py index 05466220d..6641a89bb 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -1,19 +1,22 @@ # coding=utf-8 import os -# Channels, servers and roles +# Server PYTHON_GUILD = 267624335836053506 +# Channels BOT_CHANNEL = 267659945086812160 +CHECKPOINT_TEST_CHANNEL = 422077681434099723 +DEVLOG_CHANNEL = 409308876241108992 +DEVTEST_CHANNEL = 414574275865870337 HELP1_CHANNEL = 303906576991780866 HELP2_CHANNEL = 303906556754395136 HELP3_CHANNEL = 303906514266226689 +MOD_LOG_CHANNEL = 282638479504965634 PYTHON_CHANNEL = 267624335836053506 -DEVLOG_CHANNEL = 409308876241108992 -DEVTEST_CHANNEL = 414574275865870337 VERIFICATION_CHANNEL = 352442727016693763 -CHECKPOINT_TEST_CHANNEL = 422077681434099723 +# Roles ADMIN_ROLE = 267628507062992896 MODERATOR_ROLE = 267629731250176001 VERIFIED_ROLE = 352427296948486144 @@ -31,8 +34,10 @@ DEPLOY_URL = os.environ.get("DEPLOY_URL") STATUS_URL = os.environ.get("STATUS_URL") SITE_URL = os.environ.get("SITE_URL", "pythondiscord.local:8080") SITE_PROTOCOL = 'http' if 'local' in SITE_URL else 'https' -SITE_API_USER_URL = f"{SITE_PROTOCOL}://api.{SITE_URL}/user" -SITE_API_TAGS_URL = f"{SITE_PROTOCOL}://api.{SITE_URL}/tags" +SITE_API_URL = f"{SITE_PROTOCOL}://api.{SITE_URL}" +SITE_API_USER_URL = f"{SITE_API_URL}/user" +SITE_API_TAGS_URL = f"{SITE_API_URL}/tags" +SITE_API_HIPHOPIFY_URL = f"{SITE_API_URL}/hiphopify" GITHUB_URL_BOT = "https://github.com/discord-python/bot" BOT_AVATAR_URL = "https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle/logo_circle.png" @@ -54,3 +59,54 @@ WHITE_CHEVRON = "<:whitechevron:418110396973711363>" # PaperTrail logging PAPERTRAIL_ADDRESS = os.environ.get("PAPERTRAIL_ADDRESS") or None PAPERTRAIL_PORT = int(os.environ.get("PAPERTRAIL_PORT") or 0) + +# Bot replies +NEGATIVE_REPLIES = [ + "Noooooo!!", + "Nope.", + "I'm sorry Dave, I'm afraid I can't do that.", + "I don't think so.", + "Not gonna happen.", + "Out of the question.", + "Huh? No.", + "Nah.", + "Naw.", + "Not likely.", + "No way, José.", + "Not in a million years.", + "Fat chance.", + "Certainly not.", + "NEGATORY." +] + +POSITIVE_REPLIES = [ + "Yep.", + "Absolutely!", + "Can do!", + "Affirmative!", + "Yeah okay.", + "Sure.", + "Sure thing!", + "You're the boss!", + "Okay.", + "No problem.", + "I got you.", + "Alright.", + "You got it!", + "ROGER THAT", + "Of course!", + "Aye aye, cap'n!", + "I'll allow it." +] + +ERROR_REPLIES = [ + "Please don't do that.", + "You have to stop.", + "Do you mind?", + "In the future, don't do that.", + "That was a mistake.", + "You blew it.", + "You're bad at computers.", + "Are you trying to kill me?", + "Noooooo!!" +] diff --git a/bot/formatter.py b/bot/formatter.py index dd351e52b..420aa66c2 100644 --- a/bot/formatter.py +++ b/bot/formatter.py @@ -71,7 +71,8 @@ class Formatter(HelpFormatter): # get the args using the handy inspect module argspec = getfullargspec(self.command.callback) arguments = formatargspec(*argspec) - for _arg, annotation in argspec.annotations.items(): + + for annotation in argspec.annotations.values(): # remove module name to only show class name # discord.ext.commands.context.Context -> Context arguments = arguments.replace(f"{annotation.__module__}.", "") |