aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/__main__.py11
-rw-r--r--bot/cogs/defcon.py166
-rw-r--r--bot/constants.py1
-rw-r--r--config-default.yml1
4 files changed, 174 insertions, 5 deletions
diff --git a/bot/__main__.py b/bot/__main__.py
index 8eb40757d..f297c8f1a 100644
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -5,7 +5,7 @@ from aiohttp import AsyncResolver, ClientSession, TCPConnector
from discord import Game
from discord.ext.commands import Bot, when_mentioned_or
-from bot.constants import Bot as BotConfig, ClickUp
+from bot.constants import Bot as BotConfig # , ClickUp
from bot.formatter import Formatter
from bot.utils.service_discovery import wait_for_rmq
@@ -56,12 +56,13 @@ bot.load_extension("bot.cogs.cogs")
# Local setups usually don't have the clickup key set,
# and loading the cog would simply spam errors in the console.
-if ClickUp.key is not None:
- bot.load_extension("bot.cogs.clickup")
-else:
- log.info("`CLICKUP_KEY` not set in the environment, not loading the ClickUp cog.")
+# if ClickUp.key is not None:
+# bot.load_extension("bot.cogs.clickup")
+# else:
+# log.info("`CLICKUP_KEY` not set in the environment, not loading the ClickUp cog.")
bot.load_extension("bot.cogs.deployment")
+bot.load_extension("bot.cogs.defcon")
bot.load_extension("bot.cogs.doc")
bot.load_extension("bot.cogs.eval")
bot.load_extension("bot.cogs.fun")
diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py
new file mode 100644
index 000000000..ea50bdf63
--- /dev/null
+++ b/bot/cogs/defcon.py
@@ -0,0 +1,166 @@
+import logging
+from datetime import datetime, timedelta
+
+from discord import Colour, Embed, Member
+from discord.ext.commands import Bot, Context, command
+
+from bot.constants import Channels, Keys, Roles, URLs
+from bot.decorators import with_role
+
+log = logging.getLogger(__name__)
+
+REJECTION_MESSAGE = """
+Hi, {user} - Thanks for your interest in our server!
+
+Due to a current (or detected) cyberattack on our community, we've limited access to the server for new accounts. Since
+your account is relatively new, we're unable to provide access to the server at this time.
+
+Even so, thanks for joining! We're very excited at the possibility of having you here, and we hope that this situation
+will be resolved soon. In the meantime, please feel free to peruse the resources on our site at
+<https://pythondiscord.com/>, and have a nice day!
+"""
+
+
+class Defcon:
+ """Time-sensitive server defense mechanisms"""
+ days = None # type: timedelta
+ enabled = False # type: bool
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+ self.days = timedelta(days=0)
+ self.headers = {"X-API-KEY": Keys.site_api}
+
+ async def on_ready(self):
+ try:
+ response = await self.bot.http_session.get(
+ URLs.site_settings_api,
+ headers=self.headers,
+ params={"keys": "defcon_enabled,defcon_days"}
+ )
+
+ data = await response.json()
+
+ except Exception: # Yikes!
+ log.exception("Unable to get DEFCON settings!")
+ await self.bot.get_channel(Channels.devlog).send(
+ f"<@&{Roles.admin}> **WARNING**: Unable to get DEFCON settings!"
+ )
+
+ else:
+ if data["defcon_enabled"]:
+ self.enabled = True
+ self.days = timedelta(days=data["defcon_days"])
+ log.warning(f"DEFCON enabled: {self.days.days} days")
+
+ else:
+ self.enabled = False
+ self.days = timedelta(days=0)
+ log.warning(f"DEFCON disabled")
+
+ async def on_member_join(self, member: Member):
+ if self.enabled and self.days.days > 0:
+ now = datetime.utcnow()
+
+ if now - member.created_at < self.days:
+ log.info(f"Rejecting user {member}: Account is too new and DEFCON is enabled")
+
+ try:
+ await member.send(REJECTION_MESSAGE.format(user=member.mention))
+ except Exception:
+ log.exception(f"Unable to send rejection message to user: {member}")
+
+ await member.kick(reason="DEFCON active, user is too new")
+
+ @with_role(Roles.admin, Roles.owner)
+ @command(name="defcon.enable", aliases=["defcon.enable()", "defcon_enable", "defcon_enable()"])
+ async def enable(self, ctx: Context):
+ """
+ Enable DEFCON mode. Useful in a pinch, but be sure you know what you're doing!
+
+ Currently, this just adds an account age requirement. Use bot.defcon.days(int) to set how old an account must
+ be, in days.
+ """
+
+ self.enabled = True
+
+ try:
+ response = await self.bot.http_session.put(
+ URLs.site_settings_api,
+ headers=self.headers,
+ json={"defcon_enabled": True}
+ )
+
+ await response.json()
+ except Exception:
+ log.exception("Unable to update DEFCON settings.")
+ await ctx.send("DEFCON enabled locally, but there was a problem updating the site.")
+ else:
+ await ctx.send("DEFCON enabled.")
+
+ @with_role(Roles.admin, Roles.owner)
+ @command(name="defcon.disable", aliases=["defcon.disable()", "defcon_disable", "defcon_disable()"])
+ async def disable(self, ctx: Context):
+ """
+ Disable DEFCON mode. Useful in a pinch, but be sure you know what you're doing!
+ """
+
+ self.enabled = False
+
+ try:
+ response = await self.bot.http_session.put(
+ URLs.site_settings_api,
+ headers=self.headers,
+ json={"defcon_enabled": False}
+ )
+
+ await response.json()
+ except Exception:
+ log.exception("Unable to update DEFCON settings.")
+ await ctx.send("DEFCON disabled locally, but there was a problem updating the site.")
+ else:
+ await ctx.send("DEFCON disabled.")
+
+ @with_role(Roles.admin, Roles.owner)
+ @command(name="defcon", aliases=["defcon()", "defcon.status", "defcon.status()"])
+ async def defcon(self, ctx: Context):
+ """
+ Check the current status of DEFCON mode.
+ """
+
+ embed = Embed(colour=Colour.blurple(), title="DEFCON Status")
+ embed.add_field(name="Enabled", value=str(self.enabled), inline=True)
+ embed.add_field(name="Days", value=str(self.days.days), inline=True)
+
+ await ctx.send(embed=embed)
+
+ @with_role(Roles.admin, Roles.owner)
+ @command(name="defcon.days", aliases=["defcon.days()", "defcon_days", "defcon_days()"])
+ async def days_command(self, ctx: Context, days: int):
+ """
+ Set how old an account must be to join the server, in days, with DEFCON mode enabled.
+ """
+
+ self.days = timedelta(days=days)
+
+ try:
+ response = await self.bot.http_session.put(
+ URLs.site_settings_api,
+ headers=self.headers,
+ json={"defcon_days": days}
+ )
+
+ await response.json()
+ except Exception:
+ log.exception("Unable to update DEFCON settings.")
+ await ctx.send(
+ f"DEFCON days updated; accounts must be {days} days old to join to the server "
+ f"- but there was a problem updating the site."
+ )
+ else:
+ await ctx.send(f"DEFCON days updated; accounts must be {days} days old to join to the server")
+
+
+def setup(bot: Bot):
+ bot.add_cog(Defcon(bot))
+ log.info("Cog loaded: Defcon")
diff --git a/bot/constants.py b/bot/constants.py
index ce8fbb1cb..6433d068a 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -257,6 +257,7 @@ class URLs(metaclass=YAMLGetter):
site_idioms_api: str
site_names_api: str
site_quiz_api: str
+ site_settings_api: str
site_special_api: str
site_tags_api: str
site_user_api: str
diff --git a/config-default.yml b/config-default.yml
index b58666ca1..4402eb9f1 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -80,6 +80,7 @@ urls:
site_names_api: 'https://api.pythondiscord.com/bot/snake_names'
site_off_topic_names_api: 'https://api.pythondiscord.com/bot/off-topic-names'
site_quiz_api: 'https://api.pythondiscord.com/bot/snake_quiz'
+ site_settings_api: 'https://api.pythondiscord.com/bot/settings'
site_special_api: 'https://api.pythondiscord.com/bot/special_snakes'
site_tags_api: 'https://api.pythondiscord.com/bot/tags'
site_user_api: 'https://api.pythondiscord.com/bot/users'