[V3] Mod initial rewrite (#1034)

* Readd work due to redoing branch

* [modlog] Move to core and start work on separating it from cogs

* More work on modlog separation

* [Core] Finish logic for modlog, do docstrings, async getters

* [Core] Add stuff to dunder all

* [Docs] Add mod log docs

* [Core] Move away from dunder str for Case class

* [Docs] don't need to doc special members in modlog docs

* More on mod log to implement commands

* More work on Mod

* [Mod] compatibility with async getters

* [Tests] start tests for mod

* [Tests] attempted fix

* [Tests] mod tests passing now!

* [ModLog] update for i18n

* modlog.pot -> messages.pot

* [Mod] i18n

* fix getting admin/mod roles

* Fix doc building

* [Mod/Modlog] redo imports

* [Tests] fix imports in mod tests

* [Mod] fix logger problem

* [Mod] cleanup errors

* A couple of bug fixes

Async getters, some old `config.set` syntax

* Filter ignores private channels

* Fix softban

Was still relying on default channels

* Actually ignore private channels

* Add check for ignored channels

* Fix logic for ignore check

* Send confirm messages before making case

* Pass in guild when setting modlog

* Thanks autocomplete

* Maintain all data for case

* Properly ignore softbans in events

* [Mod] bugfixes

* [Mod] more changes

* [ModLog] timestamp change

* [Mod] split filter and cleanup to their own cogs + regen messages.pot

* [Cleanup] change logic

* [Cleanup] increase limit for channel.history

* [Mod] await getter in modset banmentionspam

* [Mod] attempt duplicate modlog message fix

* [Mod] get_user -> get_user_info

* [Modlog] change reason command so the case author can edit their cases (#806)

* [Modlog] make reason command guild only

* [Modlog] clarify the reason command's help

* [Mod] package path changes + numpy style docstrings for modlog

* [Mod] change ban and unban events to need view audit log perms to find/create a case

* [Modlog] refactoring

* [Filter] add autoban feature

* [Mod] update case types + event changes

* [Mod/Modlog] fix tests, fix permissions things

* [Docs] fix up modlog docs

* Regenerate messages.pot
This commit is contained in:
palmtree5
2017-10-22 17:02:16 -08:00
committed by Will
parent fe61ef167e
commit fb125ef619
20 changed files with 3190 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
from .cleanup import Cleanup
from redbot.core.bot import Red
def setup(bot: Red):
bot.add_cog(Cleanup(bot))

View File

@@ -0,0 +1,344 @@
import asyncio
import re
import discord
from discord.ext import commands
from redbot.core import checks
from redbot.core.bot import Red
from redbot.core.i18n import CogI18n
from redbot.core.utils.mod import slow_deletion, mass_purge
from redbot.cogs.mod.log import log
_ = CogI18n("Cleanup", __file__)
class Cleanup:
"""Commands for cleaning messages"""
def __init__(self, bot: Red):
self.bot = bot
@commands.group()
@checks.mod_or_permissions(manage_messages=True)
async def cleanup(self, ctx: commands.Context):
"""Deletes messages."""
if ctx.invoked_subcommand is None:
await self.bot.send_cmd_help(ctx)
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def text(self, ctx: commands.Context, text: str, number: int):
"""Deletes last X messages matching the specified text.
Example:
cleanup text \"test\" 5
Remember to use double quotes."""
channel = ctx.channel
author = ctx.author
is_bot = self.bot.user.bot
def check(m):
if text in m.content:
return True
elif m == ctx.message:
return True
else:
return False
to_delete = [ctx.message]
too_old = False
tmp = ctx.message
while not too_old and len(to_delete) - 1 < number:
async for message in channel.history(limit=1000,
before=tmp):
if len(to_delete) - 1 < number and check(message) and\
(ctx.message.created_at - message.created_at).days < 14:
to_delete.append(message)
elif (ctx.message.created_at - message.created_at).days >= 14:
too_old = True
break
tmp = message
reason = "{}({}) deleted {} messages "\
" containing '{}' in channel {}".format(author.name,
author.id, len(to_delete), text, channel.id)
log.info(reason)
if is_bot:
await mass_purge(to_delete, channel)
else:
await slow_deletion(to_delete)
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def user(self, ctx: commands.Context, user: discord.Member or int, number: int):
"""Deletes last X messages from specified user.
Examples:
cleanup user @\u200bTwentysix 2
cleanup user Red 6"""
channel = ctx.channel
author = ctx.author
is_bot = self.bot.user.bot
def check(m):
if isinstance(user, discord.Member) and m.author == user:
return True
elif m.author.id == user: # Allow finding messages based on an ID
return True
elif m == ctx.message:
return True
else:
return False
to_delete = []
too_old = False
tmp = ctx.message
while not too_old and len(to_delete) - 1 < number:
async for message in channel.history(limit=1000,
before=tmp):
if len(to_delete) - 1 < number and check(message) and\
(ctx.message.created_at - message.created_at).days < 14:
to_delete.append(message)
elif (ctx.message.created_at - message.created_at).days >= 14:
too_old = True
break
tmp = message
reason = "{}({}) deleted {} messages "\
" made by {}({}) in channel {}"\
"".format(author.name, author.id, len(to_delete),
user.name, user.id, channel.name)
log.info(reason)
if is_bot:
# For whatever reason the purge endpoint requires manage_messages
await mass_purge(to_delete, channel)
else:
await slow_deletion(to_delete)
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def after(self, ctx: commands.Context, message_id: int):
"""Deletes all messages after specified message
To get a message id, enable developer mode in Discord's
settings, 'appearance' tab. Then right click a message
and copy its id.
This command only works on bots running as bot accounts.
"""
channel = ctx.channel
author = ctx.author
is_bot = self.bot.user.bot
if not is_bot:
await ctx.send(_("This command can only be used on bots with "
"bot accounts."))
return
after = await channel.get_message(message_id)
if not after:
await ctx.send(_("Message not found."))
return
to_delete = []
async for message in channel.history(after=after):
if (ctx.message.created_at - message.created_at).days < 14:
# Only add messages that are less than
# 14 days old to the deletion queue
to_delete.append(message)
reason = "{}({}) deleted {} messages in channel {}"\
"".format(author.name, author.id,
len(to_delete), channel.name)
log.info(reason)
await mass_purge(to_delete, channel)
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def messages(self, ctx: commands.Context, number: int):
"""Deletes last X messages.
Example:
cleanup messages 26"""
channel = ctx.channel
author = ctx.author
is_bot = self.bot.user.bot
to_delete = []
tmp = ctx.message
done = False
while len(to_delete) - 1 < number and not done:
async for message in channel.history(limit=1000, before=tmp):
if len(to_delete) - 1 < number and \
(ctx.message.created_at - message.created_at).days < 14:
to_delete.append(message)
elif (ctx.message.created_at - message.created_at).days >= 14:
done = True
break
tmp = message
reason = "{}({}) deleted {} messages in channel {}"\
"".format(author.name, author.id,
number, channel.name)
log.info(reason)
if is_bot:
await mass_purge(to_delete, channel)
else:
await slow_deletion(to_delete)
@cleanup.command(name='bot')
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def cleanup_bot(self, ctx: commands.Context, number: int):
"""Cleans up command messages and messages from the bot"""
channel = ctx.message.channel
author = ctx.message.author
is_bot = self.bot.user.bot
prefixes = self.bot.command_prefix
if isinstance(prefixes, str):
prefixes = [prefixes]
elif callable(prefixes):
if asyncio.iscoroutine(prefixes):
await ctx.send(_('Coroutine prefixes not yet implemented.'))
return
prefixes = prefixes(self.bot, ctx.message)
# In case some idiot sets a null prefix
if '' in prefixes:
prefixes.remove('')
def check(m):
if m.author.id == self.bot.user.id:
return True
elif m == ctx.message:
return True
p = discord.utils.find(m.content.startswith, prefixes)
if p and len(p) > 0:
return m.content[len(p):].startswith(tuple(self.bot.commands))
return False
to_delete = [ctx.message]
too_old = False
tmp = ctx.message
while not too_old and len(to_delete) - 1 < number:
async for message in channel.history(limit=1000, before=tmp):
if len(to_delete) - 1 < number and check(message) and\
(ctx.message.created_at - message.created_at).days < 14:
to_delete.append(message)
elif (ctx.message.created_at - message.created_at).days >= 14:
too_old = True
break
tmp = message
reason = "{}({}) deleted {} "\
" command messages in channel {}"\
"".format(author.name, author.id, len(to_delete),
channel.name)
log.info(reason)
if is_bot:
await mass_purge(to_delete, channel)
else:
await slow_deletion(to_delete)
@cleanup.command(name='self')
async def cleanup_self(self, ctx: commands.Context, number: int, match_pattern: str = None):
"""Cleans up messages owned by the bot.
By default, all messages are cleaned. If a third argument is specified,
it is used for pattern matching: If it begins with r( and ends with ),
then it is interpreted as a regex, and messages that match it are
deleted. Otherwise, it is used in a simple substring test.
Some helpful regex flags to include in your pattern:
Dots match newlines: (?s); Ignore case: (?i); Both: (?si)
"""
channel = ctx.channel
author = ctx.message.author
is_bot = self.bot.user.bot
# You can always delete your own messages, this is needed to purge
can_mass_purge = False
if type(author) is discord.Member:
me = ctx.guild.me
can_mass_purge = channel.permissions_for(me).manage_messages
use_re = (match_pattern and match_pattern.startswith('r(') and
match_pattern.endswith(')'))
if use_re:
match_pattern = match_pattern[1:] # strip 'r'
match_re = re.compile(match_pattern)
def content_match(c):
return bool(match_re.match(c))
elif match_pattern:
def content_match(c):
return match_pattern in c
else:
def content_match(_):
return True
def check(m):
if m.author.id != self.bot.user.id:
return False
elif content_match(m.content):
return True
return False
to_delete = []
# Selfbot convenience, delete trigger message
if author == self.bot.user:
to_delete.append(ctx.message)
number += 1
too_old = False
tmp = ctx.message
while not too_old and len(to_delete) < number:
async for message in channel.history(limit=1000, before=tmp):
if len(to_delete) < number and check(message) and\
(ctx.message.created_at - message.created_at).days < 14:
to_delete.append(message)
elif (ctx.message.created_at - message.created_at).days >= 14:
# Found a message that is 14 or more days old, stop here
too_old = True
break
tmp = message
if channel.name:
channel_name = 'channel ' + channel.name
else:
channel_name = str(channel)
reason = "{}({}) deleted {} messages "\
"sent by the bot in {}"\
"".format(author.name, author.id, len(to_delete),
channel_name)
log.info(reason)
if is_bot and can_mass_purge:
await mass_purge(to_delete, channel)
else:
await slow_deletion(to_delete)

View File

@@ -0,0 +1,17 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2017-10-22 16:34-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=cp1252\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"