diff options
author | 2018-04-20 22:14:22 +0200 | |
---|---|---|
committer | 2018-04-20 22:14:22 +0200 | |
commit | 57b1f9f88cb53b3c682e70dbf5b3dbc601e81d14 (patch) | |
tree | ea36f74fd111b58339e55de6c39b698704ec7a98 | |
parent | Use discord.py's case insensitivity instead of patching `bot.cogs` ourselves ... (diff) |
[#1eeu1] Hiphopify (#54)
* Boilerplate for the new hiphopify cog, and a new feature for the python parser - we can now parse role mentions, channel mentions, and user mentions without wrapping them in quotes.
* Got the bot.unhiphopify feature working. Still needs polish. Also added required constants.
* Moved some constants out of tags and hiphopify and into the constants file. Fixed a bug with the python parsing monkeypatch where regular non-python style parsing would no longer allow mentions. Wrote the on_member_change event handler and the bot.hiphopify feature. Almost ready for testing.
* cleaning up constants
* Now logging the hiphopify events into #mod-log
* Adding a little extra logging
* Added an explicit catch and warning log if the user has disabled DMs or blocked the bot, as this would cause discord to raise an error. Also addressed the feedback from Joseph and Apertures reviews.
-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__}.", "") |