aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/__init__.py8
-rw-r--r--bot/__main__.py3
-rw-r--r--bot/cogs/hiphopify.py192
-rw-r--r--bot/cogs/tags.py19
-rw-r--r--bot/constants.py68
-rw-r--r--bot/formatter.py3
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__}.", "")