aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/exts/filters/antispam.py1
-rw-r--r--bot/exts/filters/filtering.py7
-rw-r--r--bot/exts/moderation/modlog.py2
-rw-r--r--bot/exts/utils/bot.py17
-rw-r--r--bot/exts/utils/thread_bumper.py147
5 files changed, 156 insertions, 18 deletions
diff --git a/bot/exts/filters/antispam.py b/bot/exts/filters/antispam.py
index ddfd11231..bcd845a43 100644
--- a/bot/exts/filters/antispam.py
+++ b/bot/exts/filters/antispam.py
@@ -103,6 +103,7 @@ class DeletionContext:
mod_alert_message += content
await modlog.send_log_message(
+ content=", ".join(str(m.id) for m in self.members), # quality-of-life improvement for mobile moderators
icon_url=Icons.filtering,
colour=Colour(Colours.soft_red),
title="Spam detected!",
diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py
index 1f83acf9b..f44b28125 100644
--- a/bot/exts/filters/filtering.py
+++ b/bot/exts/filters/filtering.py
@@ -256,6 +256,7 @@ class Filtering(Cog):
)
await self.mod_log.send_log_message(
+ content=str(member.id), # quality-of-life improvement for mobile moderators
icon_url=Icons.token_removed,
colour=Colours.soft_red,
title="Username filtering alert",
@@ -423,9 +424,12 @@ class Filtering(Cog):
# Allow specific filters to override ping_everyone
ping_everyone = Filter.ping_everyone and _filter.get("ping_everyone", True)
- # If we are going to autoban, we don't want to ping
+ content = str(msg.author.id) # quality-of-life improvement for mobile moderators
+
+ # If we are going to autoban, we don't want to ping and don't need the user ID
if reason and "[autoban]" in reason:
ping_everyone = False
+ content = None
eval_msg = "using !eval " if is_eval else ""
footer = f"Reason: {reason}" if reason else None
@@ -439,6 +443,7 @@ class Filtering(Cog):
# Send pretty mod log embed to mod-alerts
await self.mod_log.send_log_message(
+ content=content,
icon_url=Icons.filtering,
colour=Colour(Colours.soft_red),
title=f"{_filter['type'].title()} triggered!",
diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py
index 54a08738c..32ea0dc6a 100644
--- a/bot/exts/moderation/modlog.py
+++ b/bot/exts/moderation/modlog.py
@@ -116,7 +116,7 @@ class ModLog(Cog, name="ModLog"):
if ping_everyone:
if content:
- content = f"<@&{Roles.moderators}>\n{content}"
+ content = f"<@&{Roles.moderators}> {content}"
else:
content = f"<@&{Roles.moderators}>"
diff --git a/bot/exts/utils/bot.py b/bot/exts/utils/bot.py
index 788692777..8f0094bc9 100644
--- a/bot/exts/utils/bot.py
+++ b/bot/exts/utils/bot.py
@@ -1,7 +1,6 @@
-from contextlib import suppress
from typing import Optional
-from discord import Embed, Forbidden, TextChannel, Thread
+from discord import Embed, TextChannel
from discord.ext.commands import Cog, Context, command, group, has_any_role
from bot.bot import Bot
@@ -17,20 +16,6 @@ class BotCog(Cog, name="Bot"):
def __init__(self, bot: Bot):
self.bot = bot
- @Cog.listener()
- async def on_thread_join(self, thread: Thread) -> None:
- """
- Try to join newly created threads.
-
- Despite the event name being misleading, this is dispatched when new threads are created.
- """
- if thread.me:
- # We have already joined this thread
- return
-
- with suppress(Forbidden):
- await thread.join()
-
@group(invoke_without_command=True, name="bot", hidden=True)
async def botinfo_group(self, ctx: Context) -> None:
"""Bot informational commands."""
diff --git a/bot/exts/utils/thread_bumper.py b/bot/exts/utils/thread_bumper.py
new file mode 100644
index 000000000..35057f1fe
--- /dev/null
+++ b/bot/exts/utils/thread_bumper.py
@@ -0,0 +1,147 @@
+import typing as t
+
+import discord
+from async_rediscache import RedisCache
+from discord.ext import commands
+
+from bot import constants
+from bot.bot import Bot
+from bot.log import get_logger
+from bot.pagination import LinePaginator
+from bot.utils import channel, scheduling
+
+log = get_logger(__name__)
+
+
+class ThreadBumper(commands.Cog):
+ """Cog that allow users to add the current thread to a list that get reopened on archive."""
+
+ # RedisCache[discord.Thread.id, "sentinel"]
+ threads_to_bump = RedisCache()
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+ self.init_task = scheduling.create_task(self.ensure_bumped_threads_are_active(), event_loop=self.bot.loop)
+
+ async def unarchive_threads_not_manually_archived(self, threads: list[discord.Thread]) -> None:
+ """
+ Iterate through and unarchive any threads that weren't manually archived recently.
+
+ This is done by extracting the manually archived threads from the audit log.
+
+ Only the last 200 thread_update logs are checked,
+ as this is assumed to be more than enough to cover bot downtime.
+ """
+ guild = self.bot.get_guild(constants.Guild.id)
+
+ recent_manually_archived_thread_ids = []
+ async for thread_update in guild.audit_logs(limit=200, action=discord.AuditLogAction.thread_update):
+ if getattr(thread_update.after, "archived", False):
+ recent_manually_archived_thread_ids.append(thread_update.target.id)
+
+ for thread in threads:
+ if thread.id in recent_manually_archived_thread_ids:
+ log.info(
+ "#%s (%d) was manually archived. Leaving archived, and removing from bumped threads.",
+ thread.name,
+ thread.id
+ )
+ await self.threads_to_bump.delete(thread.id)
+ else:
+ await thread.edit(archived=False)
+
+ async def ensure_bumped_threads_are_active(self) -> None:
+ """Ensure bumped threads are active, since threads could have been archived while the bot was down."""
+ await self.bot.wait_until_guild_available()
+
+ threads_to_maybe_bump = []
+ for thread_id, _ in await self.threads_to_bump.items():
+ try:
+ thread = await channel.get_or_fetch_channel(thread_id)
+ except discord.NotFound:
+ log.info("Thread %d has been deleted, removing from bumped threads.", thread_id)
+ await self.threads_to_bump.delete(thread_id)
+ continue
+
+ if thread.archived:
+ threads_to_maybe_bump.append(thread)
+
+ await self.unarchive_threads_not_manually_archived(threads_to_maybe_bump)
+
+ @commands.group(name="bump")
+ async def thread_bump_group(self, ctx: commands.Context) -> None:
+ """A group of commands to manage the bumping of threads."""
+ if not ctx.invoked_subcommand:
+ await ctx.send_help(ctx.command)
+
+ @thread_bump_group.command(name="add", aliases=("a",))
+ async def add_thread_to_bump_list(self, ctx: commands.Context, thread: t.Optional[discord.Thread]) -> None:
+ """Add a thread to the bump list."""
+ await self.init_task
+
+ if not thread:
+ if isinstance(ctx.channel, discord.Thread):
+ thread = ctx.channel
+ else:
+ raise commands.BadArgument("You must provide a thread, or run this command within a thread.")
+
+ if await self.threads_to_bump.contains(thread.id):
+ raise commands.BadArgument("This thread is already in the bump list.")
+
+ await self.threads_to_bump.set(thread.id, "sentinel")
+ await ctx.send(f":ok_hand:{thread.mention} has been added to the bump list.")
+
+ @thread_bump_group.command(name="remove", aliases=("r", "rem", "d", "del", "delete"))
+ async def remove_thread_from_bump_list(self, ctx: commands.Context, thread: t.Optional[discord.Thread]) -> None:
+ """Remove a thread from the bump list."""
+ await self.init_task
+
+ if not thread:
+ if isinstance(ctx.channel, discord.Thread):
+ thread = ctx.channel
+ else:
+ raise commands.BadArgument("You must provide a thread, or run this command within a thread.")
+
+ if not await self.threads_to_bump.contains(thread.id):
+ raise commands.BadArgument("This thread is not in the bump list.")
+
+ await self.threads_to_bump.delete(thread.id)
+ await ctx.send(f":ok_hand: {thread.mention} has been removed from the bump list.")
+
+ @thread_bump_group.command(name="list", aliases=("get",))
+ async def list_all_threads_in_bump_list(self, ctx: commands.Context) -> None:
+ """List all the threads in the bump list."""
+ await self.init_task
+
+ lines = [f"<#{k}>" for k, _ in await self.threads_to_bump.items()]
+ embed = discord.Embed(
+ title="Threads in the bump list",
+ colour=constants.Colours.blue
+ )
+ await LinePaginator.paginate(lines, ctx, embed)
+
+ @commands.Cog.listener()
+ async def on_thread_update(self, _: discord.Thread, after: discord.Thread) -> None:
+ """
+ Listen for thread updates and check if the thread has been archived.
+
+ If the thread has been archived, and is in the bump list, un-archive it.
+ """
+ await self.init_task
+
+ if not after.archived:
+ return
+
+ if await self.threads_to_bump.contains(after.id):
+ await self.unarchive_threads_not_manually_archived([after])
+
+ async def cog_check(self, ctx: commands.Context) -> bool:
+ """Only allow staff & partner roles to invoke the commands in this cog."""
+ return await commands.has_any_role(
+ *constants.STAFF_PARTNERS_COMMUNITY_ROLES
+ ).predicate(ctx)
+
+
+def setup(bot: Bot) -> None:
+ """Load the ThreadBumper cog."""
+ bot.add_cog(ThreadBumper(bot))