Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions redbot/cogs/mod/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections import defaultdict, deque

import discord
from redbot.cogs.mod.utils import create_new_cache
from redbot.core import i18n, modlog, commands
from redbot.core.utils.mod import is_mod_or_superior
from .abc import MixinMeta
Expand All @@ -18,21 +19,20 @@ class Events(MixinMeta):
"""

async def check_duplicates(self, message):
if not message.content:
return False

guild = message.guild
author = message.author
author = message.author.id
channel = message.channel

guild_cache = self.cache.get(guild.id, None)
if guild_cache is None:
repeats = await self.config.guild(guild).delete_repeats()
if repeats == -1:
return False
guild_cache = self.cache[guild.id] = defaultdict(lambda: deque(maxlen=repeats))

if not message.content:
return False
# Cache is dict (channel -> dict (author -> messages))
guild_cache = self.cache[guild.id] = await create_new_cache(self.config, guild)

guild_cache[author].append(message.content)
msgs = guild_cache[author]
guild_cache[channel.id][author].append(message.content)
msgs = guild_cache[channel.id][author]
if len(msgs) == msgs.maxlen and len(set(msgs)) == 1:
try:
await message.delete()
Expand Down
2 changes: 1 addition & 1 deletion redbot/cogs/mod/mod.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Mod(
"track_nicknames": True,
}

default_channel_settings = {"ignored": False}
default_channel_settings = {"ignored": False, "delete_repeats": 0}

default_member_settings = {"past_nicks": [], "perms_cache": {}, "banned_until": False}

Expand Down
107 changes: 92 additions & 15 deletions redbot/cogs/mod/settings.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import asyncio
import discord
from collections import defaultdict, deque
from typing import Optional
from datetime import timedelta

from redbot.core import commands, i18n, checks
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import box, humanize_timedelta, inline
from redbot.cogs.mod.utils import create_new_cache

from .abc import MixinMeta

Expand Down Expand Up @@ -59,6 +61,17 @@ async def modset_showsettings(self, ctx: commands.Context):
if delete_repeats != -1
else _("No")
)
custom = ""
channel_data = await self.config.all_channels()
for channel in ctx.guild.channels:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this string now has a non-determinant length due to this loop, the eventual send of this command should be modified to ensure the resulting message is under 2000 characters, and to properly pagify the message if it is not.

data = data = channel_data.get(channel.id, None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
data = data = channel_data.get(channel.id, None)
data = channel_data.get(channel.id, None)

If you aren't setting multiple different variables, there is no reason to use this syntax.

if data is not None and data["delete_repeats"] != 0:
custom += f"\t{channel.name} - {data['delete_repeats']}\n"
msg += _(
"Delete repeats custom channels: \n{delete_repeat_channels}".format(
delete_repeat_channels=custom if len(custom) != 0 else "None\n"
)
)
msg += _("Warn mention spam: {num_mentions}\n").format(
num_mentions=_("{num} mentions").format(num=warn_mention_spam)
if warn_mention_spam
Expand Down Expand Up @@ -283,33 +296,97 @@ async def mentionspam_ban(self, ctx: commands.Context, max_mentions: int):

@modset.command()
@commands.guild_only()
async def deleterepeats(self, ctx: commands.Context, repeats: int = None):
async def deleterepeats(self, ctx: commands.Context, repeats: int = None, *channels):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async def deleterepeats(self, ctx: commands.Context, repeats: int = None, *channels):
async def deleterepeats(self, ctx: commands.Context, repeats: int = None, *channels: discord.TextChannel):

As I suggested in my previous review, it is far easier to just type hint the channels argument so dpy automatically converts it than to attempt to process it after the fact.

"""Enable auto-deletion of repeated messages.

Must be between 2 and 20.

Set to -1 to disable this feature.

Add channels if you want to custom the value for the channels (#name_of_channel)

Set to 0 if you want to use the default value for the channel selected
Comment on lines +306 to +308
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Add channels if you want to custom the value for the channels (#name_of_channel)
Set to 0 if you want to use the default value for the channel selected
Channel specific overrides can be created by using this command with a passed in value for `channels`.
If one or more channels are provided, `repeats` can be set to 0 to make those channels use the server default.

I don't think this is perfect, but it's at least a bit more descriptive than what is currently there.


"""
test = 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test = 1

This line should not be here, I assume this is left over from your local debugging.

guild = ctx.guild
# Create cache if not created
guild_cache = self.cache.get(guild.id, None)
if guild_cache is None:
guild_cache = self.cache[guild.id] = await create_new_cache(self.config, guild)

fails = set()
success = set()
for channel_id in channels:
# Removing every non-digit number
channel_id_digits = "".join(ch for ch in channel_id if ch.isdigit())
channel_id_digits
channel = ctx.guild.get_channel(
int(channel_id_digits) if len(channel_id_digits) != 0 else 0
)
if channel is None:
fails.add(channel_id)
else:
success.add(channel)

if len(fails) > 0:
msg = _("Discord failed to recognize the following channels : (")
for channel in fails:
msg += _(f"{channel},")
await ctx.send(msg + ")")
Comment on lines +318 to +336
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire block is not needed if you use the converter,


if repeats is not None:
if repeats == -1:
await self.config.guild(guild).delete_repeats.set(repeats)
self.cache.pop(guild.id, None) # remove cache with old repeat limits
await ctx.send(_("Repeated messages will be ignored."))
if len(channels) != 0:
channels_string = ""
for channel in success:
channels_string += _("{name}, ").format(name=channel.name)
await self.config.channel(channel).delete_repeats.set(repeats)
msg = _("Repeated messages will be ignored for the following channels : ( ")

await ctx.send(_(msg + channels_string + ")"))
self.cache[guild.id][channel.id] = defaultdict(lambda: deque(maxlen=0))
Comment on lines +341 to +348
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
channels_string = ""
for channel in success:
channels_string += _("{name}, ").format(name=channel.name)
await self.config.channel(channel).delete_repeats.set(repeats)
msg = _("Repeated messages will be ignored for the following channels : ( ")
await ctx.send(_(msg + channels_string + ")"))
self.cache[guild.id][channel.id] = defaultdict(lambda: deque(maxlen=0))
channels_string = ", ".join(channel.name for channel in success)
for channel in success:
await self.config.channel(channel).delete_repeats.set(repeats)
self.cache[guild.id][channel.id] = defaultdict(lambda: deque(maxlen=0))
msg = _("Repeated messages will be ignored for the following channels: {channels_string}").format(channels_string=channels_string)
await ctx.send(msg)

You were only updating the cache for the last guild currently, since it was setting outside the loop.

Translation strings (_("Text to translate here")) should only be used on hardcoded strings or strings with .format applied outside of the _(), not on strings that are concatenated from multiple variables, and can thus be dynamic at runtime (IE _(msg + channels_string + ")") should not be a translated string).

Note that I kept success as the variable to loop over, this variable will need to be changed to channels in all places you're using it once you change the parameter to convert to channels automatically. I just don't want to add suggestions to every usage of it, since there's quite a few :P

else:
await self.config.guild(guild).delete_repeats.set(repeats)
await ctx.send(_("Repeated messages will be ignored."))
self.cache[guild.id] = await create_new_cache(self.config, guild)
elif 2 <= repeats <= 20:
await self.config.guild(guild).delete_repeats.set(repeats)
# purge and update cache to new repeat limits
self.cache[guild.id] = defaultdict(lambda: deque(maxlen=repeats))
await ctx.send(
_("Messages repeated up to {num} times will be deleted.").format(num=repeats)
)
if len(channels) != 0:
channels_string = ""
for channel in success:
channels_string += _("{name}, ").format(name=channel.name)
await self.config.channel(channel).delete_repeats.set(repeats)
self.cache[guild.id][channel.id] = defaultdict(
lambda: deque(maxlen=repeats)
)
msg = _(
"Messages repeated up to {num} times will be deleted for the following channels : ( "
).format(num=repeats)
await ctx.send(_(msg + channels_string + ")"))
Comment on lines +355 to +365
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
channels_string = ""
for channel in success:
channels_string += _("{name}, ").format(name=channel.name)
await self.config.channel(channel).delete_repeats.set(repeats)
self.cache[guild.id][channel.id] = defaultdict(
lambda: deque(maxlen=repeats)
)
msg = _(
"Messages repeated up to {num} times will be deleted for the following channels : ( "
).format(num=repeats)
await ctx.send(_(msg + channels_string + ")"))
channels_string = ", ".join(channel.name for channel in success)
for channel in success:
await self.config.channel(channel).delete_repeats.set(repeats)
self.cache[guild.id][channel.id] = defaultdict(
lambda: deque(maxlen=repeats)
)
msg = _(
"Messages repeated up to {num} times will be deleted for the following channels: {channels_string}"
).format(num=repeats, channels_string=channels_string)
await ctx.send(msg)


else:
await self.config.guild(guild).delete_repeats.set(repeats)
await ctx.send(
_("Messages repeated up to {num} times will be deleted.").format(
num=repeats
)
)
# purge and update cache to new repeat limits
self.cache[guild.id] = await create_new_cache(self.config, guild)

else:
await ctx.send(
_(
"Number of repeats must be between 2 and 20"
" or equal to -1 if you want to disable this feature!"
if repeats == 0 and channels is not None:
for channel in success:
await self.config.channel(channel).delete_repeats.set(repeats)
# reset the cache for this channel
self.cache[guild.id].pop(channel.id, None)
Comment on lines +379 to +382
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have any output in this branch.

else:
await ctx.send(
_(
"Number of repeats must be between 2 and 20"
" or equal to -1 if you want to disable this feature!"
)
)
)
else:
repeats = await self.config.guild(guild).delete_repeats()
if repeats != -1:
Expand Down
20 changes: 20 additions & 0 deletions redbot/cogs/mod/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import discord

from collections import defaultdict, deque

from redbot.core.bot import Red
from redbot.core.config import Config

Expand All @@ -11,3 +13,21 @@ async def is_allowed_by_hierarchy(
return True
is_special = mod == guild.owner or await bot.is_owner(mod)
return mod.top_role > user.top_role or is_special


def lambda_cache(repeats_channel):
return lambda: deque(maxlen=repeats_channel if repeats_channel != -1 else 0)


async def create_new_cache(config: Config, guild: discord.Guild):
repeats = await config.guild(guild).delete_repeats()
channel_data = await config.all_channels()
guild_cache = defaultdict(
lambda: defaultdict(lambda: deque(maxlen=repeats if repeats != -1 else 0))
)
# Create already keys for custom amount of repeated messages per channel
for channel in guild.channels:
data = channel_data.get(channel.id, None)
if data is not None and data["delete_repeats"]:
guild_cache[channel.id] = defaultdict(lambda_cache(data["delete_repeats"]))
return guild_cache
Comment on lines +18 to +33
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not certain, but this file might be part of the public API, and if so these functions should either not be in this file or be private.